=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= ---> Solución al Reto Hacking III (www.elladodelmal.com) <--- =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= { Por: Román Medina-Heigl Hernández (aka RoMaNSoFt) } [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: 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": http://palisade.plynt.com/issues/2005Jul/xpath-injection/ --[ Nivel 2 ] ------- [http://retohacking3.elladodelmal.com/banco/principal.aspx] La segunda fase era más o menos evidente: aparece un panel de 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 razonable 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 parece 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 Object moved

Object moved to here.

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 rellenado 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 :-) ]-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=