sábado, junio 16, 2007

Solución al Reto Hacking III por RoMaNSoFt

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
---> Solución al Reto Hacking III (www.elladodelmal.com) <---
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=


{ Por: Román Medina-Heigl Hernández (aka RoMaNSoFt) }

Versión TXT : http://www.rs-labs.com/papers/i64-retoIII-solve.txt


[25/May/07]


--[ Nivel 1 ]

Partimos de la página principal de entrada (login):

http://retohacking3.elladodelmal.com/Default.aspx

Después de probar lo típico de "SQL injection", analizar el código HTML/JS, probar a modificar algunos parámetros que se le envían al formulario de login (cambiar "true" por "false" o al revés, etc) y probar usuarios y URLs a lo tonto (intentar adivinar algún .aspx escondido, una hipotética página de administración oculta, usuarios típicos como "admin"...), no funciona nada.

Llama la atención el siguiente comentario HTML en la página principal:

[!-- XML Validation --]

Que junto a la 1ª pista ("No siempre hay Sistemas Gestores de Bases de Datos Relacionales y por tanto esta vez puedes ahorrarte el SQL ¿o no?"), hacen pensar en algo diferente a inyección SQL pero relacionado con XML. Seguimos sin encontrar nada.

Ya no sabía qué mirar hasta que se me ocurrió echarle un vistazo al índice del "OWASP_Testing_Guide_v2_spanish_doc" y éste me dio la idea: probé un par de cosas y una de ellas era la "inyección XPath", que funcionó a la primera:

User: abc' or 1=1 or 'a'='b
Pass: k


Así pasamos al nivel 2.

Más info sobre "XPath injection":

--[ Nivel 2 ]

[http://retohacking3.elladodelmal.com/banco/principal.aspx]

La segunda fase era más o menos evidente: aparece un panel del control del supuesto banco (cuentas, movimientos, transferencias, etc) y pinches lo que pinches aparece una ventana de "control de acceso" que pide un código numérico de 2 dígitos (si le das a enviar directamente te sale un alert-box que te dice que debes introducir un número entre 0 y 999, pero es un error:

la aplicación no te deja poner realmente más de 2 caracteres [1]).

Para hacernos una idea de las posibilidades que hay, escogemos sucesivas veces un código cualquiera ("00", por ej.) y nos fijamos que las coordenadas que te piden van cambiando al azar. Parece ser que la fila más alta es la 10 y la letra la J, lo que nos da 100 (=10*10) coordenadas diferentes. Cada coordenada debe tener un valor entre 00 y 99 (es decir, 100 valores posibles). En total, hay un máximo de 10000 (=100*100) combinaciones posibles que aparentemente hay que adivinar. ¿O no? ;-)

Nos fijamos en la pista: "La ciencia ha aprendido en muchos campos utilizando los sistemas de Ensayo y Error. Aprender es bueno.". Parece claro que la prueba va de brute-forcing. Comprobamos que no hay un límite de intentos por lo que es posible probar valores al azar y realmente sería posible encontrar los valores correspondientes a toda la matriz de coordenadas (de 10x10), sin más que hacernos un programa que vaya probando diferentes valores tomando nota en todo momento de la coordenada que nos haya pedido. Un primer problema es que no sabemos realmente qué pasa cuando introducimos el valor de coordenada correcto (¿te pide de nuevo una coordenada hasta llegar a, por ej, 3 coordenadas exitosas? En este caso, ¿cómo distinguimos un acierto de un fallo?). Un segundo problema es la pereza: hay que hacerse el programilla de marras, que aunque no muy complicado, es un palo :-)

Cambiamos de táctica: escogeremos un valor (por ej, el "00") y se lo mandamos indefinidamente al servidor, sin importar la coordenada que nos pida. En teoría tenemos un 1/100 de probabilidad de acertar, así que se supone que con 100 intentos debería bastar (de todas formas, nosotros enviamos muchos más, para curarnos en salud). Primera ventaja: el programa que nos tenemos que hacer es trivial (simplemente que envíe una misma petición varias veces, la lógica es mucho más sencilla que la propuesta anterior). Segunda ventaja: si hiciera falta acertar N veces para pasar de nivel no nos importaría (sería cuestión de dejarlo funcionando más tiempo).

¿Cómo implementar el ataque?

Pensemos antes en cómo reconocer qué hemos acertado la coordenada o pasado de nivel. No sabemos realmente qué pasará. Esto unido a que hay que buscar una forma sencilla (para ahorrar tiempo y neuronas) de hacer el ataque me lleva a programar un simple shell-script basado en Curl. El script deberá enviar siempre el mismo valor y loguear lo que devuelva el servidor en ficheros diferentes: "brute_X", donde X es el número de iteración. La idea es:

- comprobar el tamaño de dichos ficheros resultantes (se supone que cuando ocurra "algo" la respuesta debería variar y notaríamos la variación del tamaño del fichero).

- como chequeo extra, podemos buscar la palabra "Coordenada". En la página actual aparece siempre (te pregunta por la "Coordenada" X). Si superamos el control es de prever que no aparezca de nuevo.

Lanzamos nuestro script y a esperar. Se generan ficheros de resultados de mismo tamaño, con un margen de ± 1 byte, ya que en la coordenada te pueden pedir la fila 1-9 (1 caracter) o bien la 10 (2 caracteres). Nos damos cuenta de ello. También nos damos cuenta de que a partir de cierto número de iteraciones el servidor nos devuelve una cabecera "Set-Cookie" y que pasadas otras tantas interaciones el servidor te envía una redirección a la página de login (la sesión ha expirado). Modificamos nuestro script para que tenga en cuenta esto y que sea capaz de leer/actualizar las cookies (la opción "-c" de Curl nos facilita cumplir con este requerimiento), para evitar que la sesión caduque. Lo lanzamos y pasado un número razonables de peticiones (~10000) no vemos resultados. Así que o estamos teniendo muy mala suerte o hace falta acertar muchas coordenadas o hemos programado algo mal en el script o realmente nos deberíamos de haber logueado con un usuario dado (actualmente estamos logueados como "Usuario desconocido"; intenté adivinar el usuario con técnicas de "XPath injection" pero no fue posible; teóricamente es posible extraer la base de datos XML entera utilizando el algoritmo de "Blind XPath injection" descrito en un paper de Amit Klein del 2004, pero parecer ser que no existe una implementación pública del mismo; aunque es totalmente factible hacérnoslo descartamos que el reto obligue a ello, por el tiempo que llevaría)... o da la casualidad que el "00" no es valor válido para ninguna coordenada. Así que modificamos de nuevo el script para que pruebe diferentes valores en el rango permitido, con lo que solucionamos la última posibilidad.

El script final ("fase2_random.sh") queda:

============

#!/bin/bash
# Reto III de elladodelmal.com. (c) RoMaNSoFt, 2007.
# 24.05.2007.

### Variables
postdata1='__EVENTTARGET=&__EVENTARGUMENT=&__VIEWSTATE=%2FwEPDwULLTExMjIyMTQ1OTgPZBYCZg9kFgICAw9kFgICAQ9kFgICCA8PFgIeB1Zpc2libGVnZBYCAgMPDxYCHgRUZXh0BVJDb29yZGVuYWRhIGNvcnJlc3BvbmRpZW50ZSBhIGxhIGZpbGEgPHN0cm9uZz4zPC9zdHJvbmc%2BIHkgbGV0cmEgPHN0cm9uZz5GPC9zdHJvbmc%2BZGRkSYVIkDvElpfi8SgDyfFCMU6m42c%3D&ctl00%24ContentPlaceHolder1%24txtEntrada='
postdata2='&ctl00%24ContentPlaceHolder1%24btEnviar=Enviar&__EVENTVALIDATION=%2FwEWCgKtx%2B6xDQLfv9utAgLd6PvdDwL2wJ38BAKZ2vm1CgKm15b5DQKBoK32BwLe6KO9BQLauvOYCAKwu%2BOhDuM7VBfx98B7Xafx00b9oBV%2BIy%2Bv'
basefichero="brute"
cookies="cookies"

### Programa principal

c=0

for i in `seq 1 50000` ; do
echo -n " $i "
p=`printf "%02d" $c`
postdata="$postdata1$p$postdata2"
outfile="$basefichero""$i""_$p"
curl --silent --include --cookie "$cookies" --cookie-jar "$cookies"
"http://retohacking3.elladodelmal.com/banco/principal.aspx" --data
"$postdata" > $outfile
c=$(($c+1))
if [ $c -gt 99 ] ; then
c=0
fi
done

echo
echo 'Ahora a buscar con: for i in brute* ; do grep -q Coordenada $i ||
echo $i ; done'
echo Done


============

El script genera muchos ficheros donde se incluye el número de iteración y el valor probado (esto último por simple curiosidad). Lo dejamos "DoSeando" el servidor del concurso, digo, trabajando. Veamos algunas muestras significativas:

[1] -rw-r--r-- 1 roman roman 12493 2007-05-24 22:09 brute1_00
...
[2] -rw-r--r-- 1 roman roman 12494 2007-05-24 22:09 brute21_20
...
[3] -rw-r--r-- 1 roman roman 418 2007-05-24 22:44 brute2831_30
...


La muestra 1 y 2 son respuestas normales que te preguntan por un coordenada. La 2 ocupa 1 byte más porque pregunta por la fila 10. Las de tipo 1 preguntan por las filas 1-9 (realmente hay más diferencias: por ejemplo cambia el timestamp de la respuesta, valores de variables como __VIEWSTATE, etc pero normalmente se mantendrá la longitud de esos campos/variables). ¿Y la muestra 3? Pues lógico, hemos debido acertar, veámoslo:

roman@jupiter:~/Hack/Wargames/informatica64-retoIII/brute$ cat brute2831_30
HTTP/1.1 302 Found
Connection: Keep-Alive
Content-Length: 138
Date: Thu, 24 May 2007 20:43:42 GMT
Location: /banco/faseFinal.aspx
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
X-AspNet-Version: 2.0.50727
Cache-Control: private

[html][head][title]Object moved[/title][/head][body]
[h2]Object moved to [a href="/banco/faseFinal.aspx"]here[/a][/h2]
[/body][/html]


Vemos que nos redirige a la "faseFinal" ;-)

Curiosidades:

- yo probé exactamente 15425 peticiones.

- para ver los aciertos:

roman@jupiter:~/Hack/Wargames/informatica64-retoIII/brute$ for i in brute*; do grep -q Coordenada $i || echo $i ; done
brute10545_44
brute1481_80
brute2831_30
brute3431_30
brute6381_80
brute6931_30
brute7581_80


- diferentes valores: 44, 80 y 30 (estos dos últimos repetidos: o se
repiten las coordenadas más de lo debido o simplemente se han rellado
repitiendo valores; lástima que no apuntara además del valor la coordenada, para salir de dudas).

Luego nuestra tasa de acierto es de 7/15425. Pequeñita pero suficiente para acertar 7 coordenadas en un tiempo razonable:

- la primera petición la lancé a las "2007-05-24 22:09"

- la última tiene timestamp "2007-05-25 01:18"

- el primer acierto: "2007-05-24 22:27"

Como se puede apreciar, en mi caso tenía la solución a los 18 minutos (con tasa de éxito de 1/1481, bastante mayor del 1/100 teórico [1]) y estuve "DoSeando" el servidor del concurso durante algo más de 3 horas (en realidad fuimos buenos chicos: podíamos haber enviado varias peticiones en paralelo...).

Por último, algunas utils que pensé en utilizar en primera instancia pero que luego descarté (preferí personalizar la solución con mi script):

* C-Force : http://carpetboy.deny.de/tut.htm

* Crowbar : http://www.sensepost.com/research/crowbar/

--[ Nivel 3 ]

[http://retohacking3.elladodelmal.com/banco/faseFinal.aspx]

El nivel más sencillo de todos. Es un "crackme" simplón como se deduce tanto de la pista ("El hacking ha muerto" -aunque obviamente sea una mentira-) como de la página. Esta última nos invita a bajarnos un ejecutable "genera-códigos" y finalmente te pide que lo introduzcas. Blanco y en botella... ¿la leche? Noooo, el crackme :-)

Le pasamos el PEiD (http://peid.has.it) al ejecutable: lo detecta como "MASM32 / TASM32" (lo cual tiene su lógica: para un reto que se supone que es de hacking no van a poner un crackme complicado -con empaquetado, técnicas anti-debug, etc- sino algo sencillo). Lo abrimos por tanto con un debugger: OllyDbg (otra buena opción aunque no gratuita es "IDA Pro"). Y finalmente lo crackeamos.

No pretendo dar un curso de cracking (hay muchos tutoriales por la red) ni de manejo de OllyDbg (misma razón). Resumo lo que hice:

- abrimos el ejecutable con OllyDbg.

- inspección ocular y breve análisis del mismo. El código es pequeño y claro.

- posibles vías de ataque (a priori): buscar y entender el algoritmo que genera el código, buscar el punto donde se chequea el user/password introducido, interrumpir la ejecución (breakpoint) cuando se llamen a las funciones que se usan para el "tratamiento" o chequeo de strings (kernel32.lstrcmpA y kernel32.lstrlenA), guiarme por los mensajes de error (user32.MessageBoxA), etc

- opto por la última opción. Me sitúo en la parte del código que imprime en pantalla el mensaje de "Datos Incorrectos":

004011A2 |> \6A 10 PUSH 10 ;
/Style = MB_OK|MB_ICONHAND|MB_APPLMODAL
004011A4 |. 68 14304000 PUSH 00403014 ;
|Title = "Reto Hacking #3"
004011A9 |. 68 31304000 PUSH 00403031 ;
|Text = "Datos Incorrectos"
004011AE |. 6A 00 PUSH 0 ;
|hOwner = NULL
004011B0 |. E8 A9020000 CALL ;
\MessageBoxA


- situando el cursor (y pinchando una vez) sobre la primera línea (004011A2), OllyDbg te indica desde qué otra parte del código hay un salto hacia dicha dirección. En nuestro caso, el salto está en la dirección 00401189.

- miramos el código cercano a esa dirección:

00401182 |. E8 B2000000 CALL 00401239
00401187 |. 0BC0 OR EAX,EAX
00401189 |. 75 17 JNZ SHORT 004011A2


Se está llamando a una rutina en 00401239 y posteriormente según sea el resultado de la misma se salta o no a 004011A2. En otras palabras, se llama a la rutina de comprobación de user/password y si este chequeo no es correcto aparecerá en pantalla el mensaje de "Datos Incorrectos".

- eliminamos el salto condicional (00401189), esto es, lo sustituimos por instrucciones NOP. Para ello nos situamos en la línea del salto y luego: botón derecho -> Binary -> Full with NOPs. A partir de ahora, sea la comprobación de usuario/contraseña correcta o no, nunca se salta a la parte del código que imprime el mensaje de error sino que siempre se asume que la comprobación es correcta (otra opción sería haber NOPeado la rutina de comprobación, por ejemplo).

- por último, pulsamos F9 (o bien seleccionamos: Debug -> Run), con lo cual OllyDbg ejecutará el programa (con nuestros NOPs). El botón de "generar código de activación" está desactivado.

- introducimos cualquier usuario y contraseña (user: a / pass: b). Pinchamos en "Validar". Se activa el botón de generación de código. Pinchamos este último y nos dice que el usuario debe contener al menos 5 caracteres.

- introducimos de nuevo un usuario y contraseña pero esta vez nos aseguramos de que el nombre de usuario tenga más de 5 caracteres. Validamos y obtenemos el código de activación.

- opcionalmente (y de manera adicional) podemos decirle a OllyDbg que salve el binario modificado. De esta forma ya tendríamos un generador "trucado" (crackeado). Y si se quiere rizar el rizo (y ser un profesional del cracking) pues nos hacemos el "patcher" correspondiente (lo que vulgarmente se conoce como "crack" -aunque hay otros tipos de cracks-).

Una vez obtenido el código volvemos a la aplicación web ("Maligno Bank") y lo introducimos.

¡Reto superado!

=-=-[ EOF ]-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

[1] Actualización 26/05/07: Al final ha resultado que había un error de programación en la página por el cual en Firefox sólo te dejaba introducir valores de coordenadas entre 00-99 mientras que en IExplorer te permitía 000-999. Por tanto, los que utilizamos Firefox pensamos que el mensaje del alert-box era erróneo cuando lo que realmente estaba mal era el código de la página. Resumiendo: el rango de valores correcto es 000-999. Y por tanto, se obtiene una tasa de acierto teórica del 1/1000, mucho más cercana a la que obtuvimos nosotros (1/1481).

=-=-[ EOF-2 :-) ]-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

4 comentarios:

Pedro Laguna dijo...

Bueno, hacer mi aclaracion sobre el reto...
La primera y la tercera fase las pase con casi exactamente los mismos razonamientos, aunque yo ya conocia de antemano el XPath Injection.

Harina de otro costal es la segunda fase... Me ahorrare mis divagaciones sobre cual era la manera de pasarla y comentare como lo hice yo.

Mi primera idea fue la de programarme una pequeña aplicacion en C# que iterase sobre el formulario para sacar una coordenada valida, pero me lie con las sesiones y demas e intente algo mas comodo.
Encontre una extension para Firefox llamada iMacros en la cual defini la siguiente macro:
TAB T=1
TAG POS=1 TYPE=INPUT:BUTTON FORM=NAME:aspnetForm ATTR=ID:bt5&&VALUE:5
TAG POS=1 TYPE=INPUT:BUTTON FORM=NAME:aspnetForm ATTR=ID:bt2&&VALUE:2
TAG POS=1 TYPE=INPUT:BUTTON FORM=NAME:aspnetForm ATTR=ID:bt2&&VALUE:2
TAG POS=1 TYPE=INPUT:SUBMIT FORM=NAME:aspnetForm ATTR=ID:ctl00_ContentPlaceHolder1_btEnviar&&VALUE:Enviar

Como Romansoft tambien tuve problemas con el tema del limite de los dos caracteres, cosa que en su script daba igual porque no hacia uso del navegador, pero que yo tuve que solventar.
Para eso programe un pequeñisimo script para Greasemonkey que me ampliase el tamaño permitido en el textbox:
// ==UserScript==
// @name Reto3
// @namespace http://www.equilibrioinestable.com/
// @include http://retohacking3.elladodelmal.com/banco/principal.aspx
// ==/UserScript==

var x = getElementById("ctl00_ContentPlaceHolder1_txtEntrada");
x.setAttribute("maxlength",3);

Con estos dos trozos de codigo puse al iMacro a iterar hasta que tras unos 20 minutos me encontro una coordenada valida para el valor 522.

Un saludo y mi enhorabuena a los 4 maquinas que lo sacaron antes que yo :)

tonio dijo...

Bien he leido todos los retos así como sus soluciones y demas. (Los descargare para estudiarlos) pero ahora me surgen unas cuantas preguntas y reflesiones::

1-Debo de ser más tonto que capirote, pues reto a reto solo sabía flipar con las resoluciones expuestas.

2-Si en realidad soy panfilo perdido porque coño me han dejado terminar una carrera cuando me tenian que haber echado a freir puerros. ((De verdad esta tan mal la Uvigo???))

3-Bien es verdad y el Sr.Maligno lo sabe pues le he pedido consejo que llevo muy poco tiempo preocupado por aprender temas de seguridad (repito fui tan panfilo que crei que en la carrera me iluminarían con sabiduría y conocimientos); donde coño saca esta gente tiempo para aprender todo eso, y de donde lo aprende???

4-Visto lo visto, cuando presente mi PFC lo tirare a la puta basura pues será un coladero eso sí los memos del tribunal lo valorarán positivamente. (Pero que pasa no saben o tratan a los alumnos de imbeciles).

5-Alguien me puede explicar para que sirve estudiar en una universidad??

//Si sé que puedo parecer un poco drástico pero tengo la moral por los suelos :-(

//Procurare curarla con unos lingotazos a la noche y un par de cigarritos que me pongan sonriente

Saludos y mi enhorabuena a todos los figuras que resolvieron los distintos retos :-D

Emili dijo...

Hola,

he conseguido pasar la primera fase con la inyecion xml XPath. Sin embargo, en la segunda fase a mi no me aparece ninguna ventana. Si hago click en cualquier botón me envía a la página principal. Tal vez sea algún problema con el navegador (firefox) o con el linux pero al final he venido a ver cual era la solución porque ya no sabía que hacer. Me he encontrado con que os aparece una ventana :(

Maligno dijo...

Hola Emili,

acabo de probarlo y sigue funcionando, el cuadro sale cuando haces click en cualquier link de gestión de dinero. ¿Qué versión de navegador usas?

Entradas populares