martes, 29 de septiembre de 2015

Fuzzing usando software de código abierto alojado en GitHub

En muchas ocasiones cuando estamos realizando una auditoría de seguridad podemos encontrarnos con aplicaciones web para gestión de contenidos (CMS), monitoriación de sistemas, etc. de código abierto.

Estás aplicaciones siempre serán un buen punto de entrada para conseguir información útil o comprometer el sistema. Con el objetivo de realizar fuzzing para ver que archivos se encuentran en el servidor web he creado un pequeño script para recoger todos los nombres de directorios y archivos de un repositorio alojado en GitHub para posteriormente usarlos como path en las URL que pasaremos al fuzzer. En este caso usaremos wfuzz [1].

Dado que estamos buscando archivos que se encuentran y de los cuales existe el código fuente, podríamos analizar el mismo en busca de posibles agujeros de seguridad.



#!/usr/bin/env python

import sys
import requests
from bs4 import BeautifulSoup


def getData(url):
    requests.packages.urllib3.disable_warnings()
    r = requests.get(url, verify=False)
    soup = BeautifulSoup(r.text, "html5lib")
    data = soup.find_all('tr', attrs={'class': 'js-navigation-item'})
    for i in data:
        if (len(i.find_all('td', attrs={'class': 'icon'})) > 0):
            tipo = " ".join(i.find_all('td', attrs={'class': 'icon'})[0].span.get('class'))
            name = i.find_all('td', attrs={'class': 'content'})[0].a.get('title')
            if tipo == "octicon octicon-file-text":
                print(url.replace(f_url, "") + "/" + name)
            elif tipo == "octicon octicon-file-directory":
                print(url.replace(f_url, "") + "/" + name)
                getData(url + "/" + name)


if __name__ == "__main__": 
 f_url =  sys.argv[1] +"/tree/master/"
 getData(f_url)



Ejemplo si queremos probar las URL que existen en un servidor web que tiene el CMS de Drupal:

$ ./getRepoURLweb.py https://github.com/drupal/drupal > drupal_paths.txt

Y luego se lo pasamos a wfuzz, en este

$ wfuzz -c .z file,drupal_paths.txt http://site-with-drupal/FUZZ


Con esto además se podría crear alguna especie de crawler para poder recopilar y crear grandes diccionarios para fuzzing basandonos en repositorios de código abierto.


[1] https://github.com/xmendez/wfuzz

miércoles, 27 de mayo de 2015

Test Renegociación SSL/TLS manual con openssl

HTTP (HiperText Transfer Protocol) es un protocolo el cual según su especificación toda la información transmitida esta en claro. Posteriormente se creo una capa superior (SSL/TLS) para proporcionar un canal seguro, que nos proporciona confidencialidad y autenticación, derivando el protocolo HTTP en su variante segura HTTPS. Tanto los clientes como los servidores se pueden autenticar usando certificados.

A pesar de existir cifrados fuertes, normalmente por falta de desconocimiento o por pereza, muchos servidores contienen errores de configuración, o directamente no estan configurados, esto hace que permitan cifrados debiles, directamente ningún cifrado, lo que permite a un atacante tener acceso al canal y leer la comunicación mediante un ataque man in the middle

El ataque SSL/TLS Renegotiation consiste en aprovecharse de esta caracteristica de SSL/TLS mediante la cual un atacante en una posición entre el cliente y el servidor (MITM), el cliente pretende conectarse al servidor, pero con el que se conecta es con el atacante, antes de que el atacante complete el handshake con el cliente, el atacante se conecta al servidor y realiza el handshake, posteriormente realiza una renegociación con el servidor, pero esta vez se la devuelve al cliente, permaneciendo el atacante en una posición intermedia, y con la clave de sesión, por tanto, podrá leer todo lo que se transmita por ese canal "seguro".   Explicado con detalles en el RFC 5746

A continucación se van a explicar como detectar la vulnerabilidad de SSL/TLS Renegotiation manualmente mediante openssl

[manu@Golgota ~]$ openssl s_client -connect 212.106.221.247:443
CONNECTED(00000003)
depth=2 C = US, O = GeoTrust Inc., CN = GeoTrust Global CA
verify error:num=20:unable to get local issuer certificate
---
Certificate chain
 0 s:/C=US/ST=California/L=Mountain View/O=Google Inc/CN=google.com
   i:/C=US/O=Google Inc/CN=Google Internet Authority G2
 1 s:/C=US/O=Google Inc/CN=Google Internet Authority G2
   i:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
 2 s:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
   i:/C=US/O=Equifax/OU=Equifax Secure Certificate Authority
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIfuDCCHqCgAwIBAgIIEXx6isl3p58wDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UE
...
pQV0WWxK4updoi/O+LrPrGB3e+FhFCAeDsBQRPx3F1IhBsrp7fHPrJ6BS1/9+jQR
fedZfkQ7fvLosu690/gw8MLe6PgqluNR39yww3sXmMqNN2hOJIvTjfD2+bmetg4U
ffJ/iFbsgw5VhL6oYFNbudk+3K8fPyp6uTdmXHfxGRvQUrtdEne3Bx6Wqm6CZsEC
odzgrMG5AhqPX2P8
-----END CERTIFICATE-----
subject=/C=US/ST=California/L=Mountain View/O=Google Inc/CN=google.com
issuer=/C=US/O=Google Inc/CN=Google Internet Authority G2
---
No client certificate CA names sent
Peer signing digest: SHA512
Server Temp Key: ECDH, P-256, 256 bits
---
SSL handshake has read 10697 bytes and written 474 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-AES128-GCM-SHA256
    Session-ID: 7CA87792A48A9868A750640D78BC22510AABE40A6BA7D01A505097F237E79C9A
    Session-ID-ctx:
    Master-Key: 9EF1F5390E309B1BEA63A37D7F06532CDF231709718745B3B4EC646A5A68FB795A8682807DC7AE2AC7B6295D0C889234
    Key-Arg   : None
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    TLS session ticket lifetime hint: 100800 (seconds)
    TLS session ticket:
    0000 - 7e ed 7c e5 94 33 1f 6c-57 89 e9 77 37 aa 7d 1f   ~.|..3.lW..w7.}.
    0010 - 8b 10 64 05 4c ba dc 45-39 92 4c b3 41 eb 3e 74   ..d.L..E9.L.A.>t
    0020 - 19 58 ad 92 db 46 37 2e-0c 0d d4 00 b3 51 29 36   .X...F7......Q)6

    0030 - 0a 16 24 1d 3c c1 82 ef-68 18 20 79 85 f9 28 1b   ..$.<...h. y..(.
    0040 - 27 6f 48 e1 cf be f4 c4-f2 ae f0 f5 6c a6 fc 70   'oH.........l..p
    0050 - 4f d5 e9 0e c5 41 8e d3-aa a2 9a cf 2b 26 e1 94   O....A......+&..
    0060 - 6d 71 a0 1a 1f 1a 6f 0c-43 3b 7d c0 47 24 2c 76   mq....o.C;}.G$,v
    0070 - b8 56 ce dc 4e fd dd 17-49 26 4a c7 60 ca 62 02   .V..N...I&J.`.b.
    0080 - 3e 79 f1 12 30 53 62 a6-2e be 94 d2 dc f4 8f 1c   >y..0Sb.........
    0090 - 15 ed b1 a9 60 e8 c4 25-11 83 28 08 eb 0c 2a 0a   ....`..%..(...*.
    00a0 - ab 2f b2 b2                                       ./..

    Start Time: 1432760387
    Timeout   : 300 (sec)
    Verify return code: 20 (unable to get local issuer certificate)
---
GET / HTTP/1.0
R
RENEGOTIATING
140514982606488:error:1409E0E5:SSL routines:ssl3_write_bytes:ssl handshake failure:s3_pkt.c:645: 


 
En este caso el servidor sobre el que se ha probado no es vulnerable, ya que al solicitar la renegociación, no lo ha permitido. Sin embargo, si lo hubiese permitido, podríamos a hacer una petición GET y nos devolvería la página web.


:)

domingo, 25 de enero de 2015

Python, Basic http authentication y un ejemplo útil.

Basic HTTP authentication es un método mediante el cual nos autenticamos en un servidor web usando usuario y contraseña, siendo está enviada en las cabeceras HTTP. Viene especificado en el RFC 2617 http://tools.ietf.org/html/rfc2617


El funcionamiento es el siguiente, accedemos a una página web, en la respuesta del servidor añade la cabecera: 
                            WWW-Authenticate: Basic realm=""




posteriormente el cliente deberá incluir en la cabecera HTTP:
                 Authorization: Basic dXNlcmVqZW1wbG86cGFzc3dvcmRlamVtcGxv
Siendo dXNlcmVqZW1wbG86cGFzc3dvcmRlamVtcGxv el usuario:contraseña codificado en base64. Obviamente, este es un sistema de autenticación muy básico, el cual no se recomienda usar a día de hoy. Un atacante mediante un ataque MITM podría obtener las credenciales del usuario que se esta autenticando en el sistema fácilmente. Simplemente decodificando la cadena en base64, obtendría su contraseña.

Bien, el ejemplo que he realizado, es para autenticarnos de esta forma en un router de Movistar, que usa este sistema de autenticacón. La herramienta en cuestión esta creada para que además de autenticarse, cambie la IP pública actual que tenemos, osea, nos desconectará de la Red y nos volverá a conectar, obteniendo una nueva dirección IP (siempre y cuando tengamos una IP dinámica, obviamente).


#!/usr/bin/env python

import requests
from requests.auth import HTTPBasicAuth
from bs4 import BeautifulSoup
import time

class RouterMovistar:
 def __init__(self,user,password):
  self.user = user
  self.password = password

 def getActualIp(self):
  req = requests.get('http://192.168.1.1/admin/status.asp', auth=HTTPBasicAuth(self.user, self.password))
  soup = BeautifulSoup(req.text)
  tabla = soup.find('table',width=600).find_all('tr')
  data = []
  for i in tabla:
          tds = i.find_all('td')
          for x in tds:
                  test = x.text
                  data.append(test)
  return data[19]

        def getActualGateway(self):
                req = requests.get('http://192.168.1.1/admin/status.asp', auth=HTTPBasicAuth(self.user, self.password))
                soup = BeautifulSoup(req.text)
                tabla = soup.find('table',width=600).find_all('tr')
                data = []
                for i in tabla:
                        tds = i.find_all('td')
                        for x in tds:
                                test = x.text
                                data.append(test)
                return data[20] 

 def disconnectIp(self):
  payload = { 'submitppp0':'Disconnect',
        'submit-url':'/admin/status.asp'}
  headers = {"Content-type": "application/x-www-form-urlencoded","Accept": "text/plain","User-Agent": 'Sinkmanu :)'}
  req = requests.post('http://192.168.1.1/goform/admin/formStatus',auth=HTTPBasicAuth(self.user, self.password),headers=headers,data=payload)
  

        def connectIp(self):
                payload = { 'submitppp0':'Connect',
                             'submit-url':'/admin/status.asp'}
                headers = {"Content-type": "application/x-www-form-urlencoded","Accept": "text/plain","User-Agent": 'Sinkmanu :)'}
                req = requests.post('http://192.168.1.1/goform/admin/formStatus',auth=HTTPBasicAuth(self.user, self.password),headers=headers,data=payload)
                

  


# Main 

test = RouterMovistar('adsl','realtek') # set username and password (by default. WTF)

actualIP = test.getActualIp()
print "[*] IP Actual: \t\t",actualIP
print "[*] Gateway: \t\t",test.getActualGateway()
print "[*] Discconecting..."
test.disconnectIp()
print "[*] Connecting..."
test.connectIp()
newIP = test.getActualIp()
time.sleep(2)
while actualIP == newIP:
 test.disconnectIp()
 print "[*] Refreshing.."
 time.sleep(2)
 test.connectIp()
 newIP = test.getActualIp()
print "[*] New IP: \t\t",test.getActualIp()
print "[*] New Gateway: \t",test.getActualGateway()

 



 
:-)