miércoles, abril 18, 2007

Solución al 2o Reto Hacking Web por Daniel Kachakil

Introducción

Este documento describe dos soluciones posibles al segundo Reto Hacking de Informática 64 que se publicó el 10 de febrero de 2007 en la siguiente dirección web:

http://retohacking2.elladodelmal.com

El reto consistía en conseguir acceso a la administración del sitio web.

Pistas

En esta ocasión, la única pista inicial a nuestra disposición era la siguiente frase (disponible en el blog www.elladodelmal.com, en la que se publicaba el reto):

Para ello tendrás que unir tres cosas: lo que se ve, lo que no se ve ni verás, y lo que ya sabes que está donde siempre.

Análisis inicial

Entrando en la página principal, inicialmente disponemos de un menú de navegación con las siguientes opciones:

• Zona privada: Formulario solicitando usuario y contraseña
• Eventos: Un listado de eventos (fecha, ciudad y nombre del evento)
• Regístrate: Formulario para obtener una nueva cuenta
• Ganadores del reto: Lista de ganadores que han superado el reto

Para conseguir la mayor información posible, el primer paso necesario es el de registrarse, rellenando un pequeño formulario con los siguientes datos: nombre de usuario, contraseña, dirección de correo electrónico y ciudad.

Una vez completado el registro, accedemos a la opción “Zona privada” e introducimos el usuario y contraseña que habíamos elegido en el paso anterior. El menú lateral cambia y se nos muestran las siguientes opciones:

• Recordar eventos: El listado de eventos anterior, más un botón “Recordar”
• Ver recordatorios: Listado de eventos en los que hemos solicitado recordatorio
• Ganadores del reto: La misma lista de ganadores

Lo que se ve

Cuando en la sección “Recordar eventos” marcamos un evento para recordar, se nos solicita un comentario opcional en un campo de texto multilínea. Una vez aceptado dicho texto, volvemos a la misma página en la que se nos mostrará resaltado el evento seleccionado. Por otro lado, el botón de “Recordar evento” cambia a “Eliminar evento”. Para borrar un recordatorio, simplemente pulsamos sobre dicho botón y se nos solicitará una confirmación en una nueva página.

Al entrar en la sección “Ver recordatorios” veremos un listado con los eventos que hayamos seleccionado en la sección anterior. Cada uno de los eventos aparece con un botón “Ver comentario” que muestra el comentario que hemos introducido (en caso de haberlo hecho) y un botón “Ver documentos” para descargar un PDF con la información del evento.

Estos botones nos llevan respectivamente a las siguientes páginas:

- http://retohacking2.elladodelmal.com/ZonaPrivada/ getComentario.aspx?id=XX
- http://retohacking2.elladodelmal.com/ZonaPrivada/descargar.aspx?id=XX


Hasta aquí podríamos decir que hemos cubierto la primera parte de la pista, es decir, lo que se ve. De momento tenemos varios candidatos para probar si son vulnerables a la inyección de SQL (parámetros, campos de texto, etc) y comprobamos que el parámetro “id” de la página de descargas admite inyección ciega de SQL.

Lo que no se ve ni verás

Una vez encontrada la primera vulnerabilidad, trataremos de explotarla inyectando SQL en el parámetro. Recordemos que una forma fácil de comprobar la existencia de un parámetro vulnerable es añadirle “AND 1=1” y “AND 1=0”. En el primer caso debería comportarse de forma normal y en el segundo caso la condición es siempre falsa, por lo que normalmente no devolverá nada o tomará un valor por defecto.

Llegados a este punto, podemos intentar la unión de los resultados con otra sentencia SELECT con los resultados que queramos. Usaremos un valor inexistente del campo “id” (por ejemplo, el cero) para asegurarnos que el único resultado de la unión sea el valor que hayamos inyectado.

- http://retohacking2.elladodelmal.com/ZonaPrivada/descargar.aspx?id=0

Podemos probar sustituyendo el parámetro “id” de la URL anterior con las siguientes consultas de unión:

- id=0 UNION SELECT 0
- id=0 UNION SELECT 1
- id=0 UNION SELECT null
- id=0 UNION SELECT ‘a’


Comprobamos que en el primer caso intenta descargarse un fichero con el nombre “0”. En el segundo caso, intentará descargar un fichero con nombre “1” (y no el fichero con la id=1). En el tercer caso, la aplicación descargará un fichero con nombre “descargar.aspx”. Por último, en el cuarto caso, aparecerá un mensaje de “Fichero no encontrado”

Vamos a analizar los resultados, comprobando el contenido de cada uno de los ficheros descargados. Observamos que todos ellos tienen el mismo contenido: el texto HTML mostrándonos el típico mensaje de error de ASP.NET.

No obstante, todo indica que la página de descargas es capaz de volcar el contenido de cualquier fichero, pero a su vez parece que está filtrando las cadenas de texto (falla ante cualquier comilla simple). Entonces, ¿cómo nos podemos descargar el fichero que queramos? Pues en realidad basta con hacer la unión con una tabla en la que tengamos posibilidad de escribir y que conozcamos o cuyo nombre sea fácilmente deducible. En este caso, la tabla se llama “Comentarios”, el campo de texto se llama “Comentario” y está identificado por el campo “idComentario”.

El procedimiento es sencillo: añadimos un recordatorio para un evento cualquiera, e introducimos la ruta completa y el nombre del fichero que queramos descargar en el comentario. Nos vamos a “Ver recordatorios” y comprobamos el identificador autonumérico que se le ha asignado a nuestro comentario. Una vez hecho esto, simplemente añadimos la siguiente instrucción en el parámetro:

- id=0 UNION SELECT Comentario FROM Comentarios WHERE idComentario=XXX

Bien, ahora ya podemos descargarnos algunos ficheros del servidor comprometido (según cómo tenga configurados los permisos), pero en realidad ¿nos hemos parado a pensar qué fichero queremos? Podemos bajarnos los ASPX y muchos otros ficheros de la aplicación y del sistema operativo, pero no nos serán de gran utilidad. Hay que buscar algo más…

Recordemos que el reto consiste en administrar el sitio, pero en realidad tampoco sabemos desde dónde se hace esto. Podríamos deducir que existen roles y se accede desde la misma zona privada, pero descartamos esta opción tras analizar el fichero “web.config”. Haciendo pruebas con nombres de ficheros y URLs típicas, al final descubrimos que existe una URL predecible que nos solicita una contraseña:

- http://retohacking2.elladodelmal.com/ZonaPrivada/admin

Las credenciales se enviarán utilizando el método de autentificación básica, por lo que tratándose de un servidor Windows con IIS 6, podemos deducir que el usuario que habrá que introducir debe existir en el sistema operativo y no en una base de datos o en un fichero de configuración.

Lo que ya sabes que está donde siempre

Lo que está donde siempre es precisamente el fichero donde se encuentran todos los usuarios locales del sistema operativo en cuestión. Se trata del fichero SAM (Security Account Manager), que forma parte del registro de Windows y almacena toda la información que necesitamos para superar la última fase. Bueno, en realidad no es suficiente con este fichero, porque parte de dicha información se almacena cifrada con una clave (SysKey), que también se encuentra almacenada en el registro de Windows, pero en el fichero SYSTEM.

Los nombres de usuario están almacenados en el fichero SAM en forma de texto en claro, pero no así las contraseñas, ya que a pesar de estar cifradas con la SysKey, son simples hashes de la contraseña original.

¿Pero dónde se encuentran estos dos ficheros? En una instalación típica, en el directorio “c:\windows\system32\config”, pero esto nos servirá de poco ya que estos ficheros siempre están en uso por el propio sistema operativo y, por tanto, no podremos acceder a ellos directamente, que es precisamente lo que estamos buscando. Hay que acceder a la copia de seguridad, que se encuentra en “c:\windows\repair”

Juntando todas las piezas

Ahora que sabemos los ficheros que necesitamos, cómo conseguirlos y dónde introducir las credenciales, ya podemos rematar el trabajo.

Añadiremos dos recordatorios, introduciendo en los comentarios los textos “c:\windows\repair\SAM” y “c:\windows\repair\SYSTEM”, respectivamente. Luego obtenemos sus identificadores asociados y los introducimos en la sentencia de unión SQL descrita anteriormente y descargamos ambos ficheros.

Una vez obtenidos los ficheros, usamos una herramienta como SamInside (www.insidepro.com) para extraer la información almacenada (usuarios y hashes). Asumiendo que no tenemos una licencia completa de SamInside, usaremos otras herramientas como Caín (www.oxid.it), o www.plain-text.info para obtener la contraseña mediante un ataque a la hash del usuario en cuestión. En caso de existir, optaremos siempre por la hash NT, ya que es mucho más débil que la LM.

No diré cual es el nombre del usuario que necesitaremos para acceder a la administración del sitio web, ya que es trivial deducirlo teniendo en nuestro poder el fichero SAM. Lógicamente, tampoco revelaré su contraseña, lo siento… ;-)

Una vez introducidas las credenciales, nos encontramos con una página que nos indica que no se permite el listado de dicho directorio, por lo que en realidad nos queda un último paso más: deducir el nombre del fichero concreto de la página de inicio. Esto también os lo dejo a vosotros. Ánimo, que tampoco necesita de mucha imaginación.

Otra solución: Un atajo para descargarnos los ficheros

La versión inicial del reto permitía ver los comentarios de los demás de una forma trivial (modificando el parámetro id de la página de comentarios, parámetro que además es secuencial y consecutivo). Pensé que esto podía dar demasiadas pistas al resto de concursantes, por lo que me planteé otra forma de obtener los ficheros sin que otros pudieran aprovecharse de ello y la encontré.

Solamente hay que obtener el valor ASCII de cada uno de los caracteres de la ruta y formar una cadena como la que muestro a continuación (teniendo en cuenta que hay que codificar el carácter “+” en su forma URL, es decir, “%2b”):

- http://retohacking2.elladodelmal.com/ZonaPrivada/descargar.aspx?
id=0 UNION SELECT char(99)+char(58)+char(92)+char(119)+char(105)+…


De esta forma tan sencilla de inyectar una cadena sin utilizar las comillas nos evitamos muchos pasos en el proceso descrito antes, como habréis podido comprobar. El resto de pasos, lógicamente son los mismos.

Comentarios y agradecimientos

En el último punto hablaba de la versión inicial del reto. Algunos os estaréis preguntando por qué existieron varias versiones y me parece justo aclararlo. Los primeros días no funcionaba la página de descargas, por lo que no era posible solucionar el reto por un problema de permisos mal configurados. No obstante, esto no era motivo suficiente para modificar el código de la aplicación.

En realidad el motivo fue otro, ya que el primer día encontré otros fallos de seguridad más importantes como la posibilidad de utilizar herramientas que permitían obtener toda la estructura e incluso los datos de la base de datos, incluyendo por ejemplo la tabla de participantes y la tabla de ganadores (en la que podría estar la solución detallada).

Ese día le mandé un e-mail a Chema Alonso detallando el problema que veía, así como el tema de poder ver los comentarios de los demás. Me confirmó que no estaba previsto y que se solucionaría. Por este motivo, el reto estuvo offline durante varias horas y se modificaron todos estos puntos. Aun así, seguía existiendo un fallo que impedía dar con la solución y que no se corrigió hasta un par de días más tarde (y además sin avisar). El fichero SAM no era el correcto y el fichero SYSTEM no se podía descargar. Una vez corregido, aproveché para comentar en el blog que había un fallo.

A pesar de todos los fallos iniciales, hay que reconocer el trabajo que hay detrás del reto, ya que su diseño y su programación indudablemente han costado tiempo y su alojamiento online también tiene su coste y sus posibles riesgos. Por todo ello, quiero terminar este documento agradeciendo el trabajo de Chema Alonso y de todo el equipo de Informática64 que nos ha hecho posible participar en el mismo. Y por supuesto, esperando que te haya sido útil y didáctico, gracias a ti que estás leyendo esto.

Saludos,
Daniel Kachakil
dani@kachakil.com

12 comentarios:

penyaskito dijo...

Genial descripción. Un día de éstos apruebo Bases de Datos y me aprendo el UNION, y me empiezo a pasar estos retos :P

Me quedé en la cuchara, y sabía el resto de lo que había que hacer.

Gracias Dani!

Paco dijo...

Increible la solución, ya estoy a la espera del tercer reto (aunque conseguiré tan poco como en los dos primeros supongo xD ).

Por cierto, una pregunta. Estos errores de inyección SQL no se podrían arreglar activando magic_quotes en PHP? (no se si ASP tendrá alguna directriz parecida)... es decir, magic_quotes no se supone que protege al 100% estos tipos de ataque?

saludosss y seguid así :)

Sirw2p dijo...

Paco, correcto escapan a algunos caracteres como pueden ser la tipica comilla ' la doble " y NuLL. Barras invertidas.. es decir cosas que creen los webmaster que son necesarios para guarrearle sus webs (que poco cuidan la id=!).

Es muy util como comentas para reducir el riesgo de esas extresantes inyecciones SQL pero tambien tiene problemas (metodos GET , Post_...).

Lo mejor es una buena configuración al .ini y santas pascuas!

Respecto a lo de ASP. ni idea , jamás programe hay :)

Dani Kachakil dijo...

En ASP.net el problema de la inyección se resuelve de forma fácil. Como norma general, usad siempre parámetros en todos los comandos que tengáis (es decir, OleDbParameter para BD bases de datos Access y SqlParameter para SQL Server)

Por cierto, aplicad el mismo principio incluso en aplicaciones de Windows, ya que también son susceptibles de inyectar SQL, al igual que las aplicaciones web.

Bueno, me alegro que os haya gustado el solucionario. A ver si para el tercer reto se anima más gente y Chema nos consigue alguna lista de premios que atraiga a más público. Venga Chema, que seguro que puedes sacarle algo de valor a los de Spectra... ;-)

Saludos!

RoMaNSoFt dijo...

Yo utilicé las siguientes queries:

1) http://retohacking2.elladodelmal.com/ZonaPrivada/descargar.aspx?id=1 and (select count(*) from comentarios where substring(comentario,1,1)=char(126))=1

2) http://retohacking2.elladodelmal.com/ZonaPrivada/descargar.aspx?id=1 and len((select substring(comentario,2,255) from comentarios where substring(comentario,1,1)=char(126)))=16

Reto 2.5 (a la espera del 3.0): ¿Alguien se anima a describir por qué lo hice así y qué tuve que poner como comentario para que me fueran útiles estas queries?

La respuesta es trivial (ruego no tomeis este mini-reto como un insulto a la inteligencia xD), pero aún así, preferiría que en primera instancia no contestara ninguno de los ganadores del reto.

¿Premios? Un beso de tornillo del "maligno" (más le vale que participe alguna fémina xD).

PD: Para todos, tengo una duda/reflexión: ¿qué sentido tiene, en estos tiempos, que el "+" no se filtre si se inyecta urlencodeado? No conozco .net pero en la mayoría de escenarios "realistas" (i.e. la mayoría de "engines" CGI) esto no tiene sentido, puesto que lo normal es que la rutina o engine CGI se encargue automáticamente de urldecodear (fea palabra) cualquier entrada de usuario antes de pasarla al código de la aplicación (que es donde normalmente estará el fallo de SQL injection). El programador incauto suele ser uno de los eslabones más débiles (junto con los usuarios...).

-Román

RoMaNSoFt dijo...

Ups, corrección al mini-reto 2.5. La 2a query se me escapó. La correcta es:

2)
http://retohacking2.elladodelmal.com/ZonaPrivada/descargar.aspx?id=100 union select substring(comentario,2,255) from comentarios where substring(comentario,1,1)=char(126)

Y tb se me olvidó dar la enhorabuena a Daniel por el excelente report de la solución. Muy bueno.

-Román

Maligno dijo...

Venga va...

daré un beso de Tornillo al que lo resuelva...

;)

SOS: Chicas... ¡por favor!

Pedro Laguna dijo...

Texto en el comentario: ~c:\windows\repair\SAM

Con la primera query compruebas nombre de la tabla y campo, y con la segunda te traes la ruta del fichero, que esta en el primer comentario con un ~ al inicio.

Anónimo dijo...

Una preguntilla: ¿Os tardó mucho tiempo el Cain en obtener la clave?

Lo llevo toda la mañana y esto sigue....

Muchas gracias.

Saludos!!

RoMaNSoFt dijo...

Hola Pedro,

Casi casi. El contenido del comentario es correcto. Pero la 1a query realmente la hice para asegurarme de que no había más entradas en la tabla que cumplieran la condición de comenzar con ~. Si fuera sólo para comprobar que la tabla y campo existen no me habría complicado tanto y habría hecho una query más sencilla :-)

Pero bueno, te has ganado el beso "maligno" de tornillo (jejeje), aunque sea 3/4 de rosca sólo (y no completa).

Por cierto, a mi duda del final nadie ha contestado. ¿Qué sentido tiene qué cuele el "+" urlencodeado y no si va sin urlencodear? (ver pregunta más detallada unos comentarios atrás).

PD: Para el anónimo: tiene toda la pinta de que Syskey te la está jugando ;-) En Cain tienes una opción para obtener el syskey del fichero de sistema de Windows. Y por otro lado, al crackear (que es donde estás), tienes la opción de introducir la syskey, junto con el hash. Apostaría a que no estás usando el syskey.

-r

Anónimo dijo...

Muchas gracias Romansoft, os comento lo que hago para ver si alguien puede guiarme :S.

Como ya habréis podido deducir, no estoy muy suelto con el Cain (lo usé para probar hace ya unos cuantos años y luego cayó en el olvido... vamos que no sacaba tiempo para andar "trasteando"). Me he bajado la última versión del Cain y lo que hago es lo siguiente:

Pincho en Cracker y luego en LM & NTLM Hashes

Hago click con el botón derecho (debajo de User Name por ejemplo) y selecciono Add to list.

En la nueva ventana que sale marco la opción "Import Hashes from a SAM database". En SAM Filename selecciono la ruta en la que tengo el fichero SAM bajado del ordenador/servidor que contiene el reto. Para Boot Key(HEX) selecciono el fichero SYSTEM y copio la key que se obtiene al seleccionar dicho fichero.

Al pinchar sobre NEXT me aparece una lista con 7 usuarios (omito los nombres de usuario al igual que se hizo en la solución), selecciono el que me parece obvio, hago click con el botón derecho, despliego Brute-Force Attack y selecciono NTLM Hashes (selecciono ésta opción porque no he visto suelta la opcion de NT Hashes que, según explican en el solucionario, es más débil que la opción LM Hashes.)

El charset lo dejo en predefined, y la longitud a buscar con los valores por defecto, entre 1 y 16 caracteres. Le dí caña esta mañana y me cansé de esperar (estaría unas 4 horas o más buscando la contraseña), creo que estoy haciendo algo mal, pero no sé qué :S

Bueno, perdón a todos por esta parrafada.

Muchas gracias a Chema por los retos (y sus charlas y el blog, etc...), a Daniel Kachakil por la solución (está todo muy bien explicado) y a tod@s en general porque gracias a vosotros voy aprendiendo :D

Respuesta a la pregunta Romansoft: bueno, como habrás podido observar todavía no tengo mucha idea y experiencia en este mundillo, pero yo tampoco le encuentro mucho sentido a que se filtre el "+" y no se filtre el "%2b"

Anónimo dijo...

Buenas, soy el anónimo de nuevo... En fin, que no sé porqué intento aprender estas cosas, creo que se me darían mejor las chapas... El caso es que soy un cenutrio, estaba intentando conseguir la clave del usuario que no era... El nombre de uno (o dos) usuarios me había despistado, pero desde el Cain me he fijado ahora en un pequeño detalle de otro usuario y ya está la clave!!

En fin, que perdón por el tocho de post anterior y muchas gracias a tod@s!!

Entradas populares