Buenas a todos!
Me presentaré brevemente: soy Secury, organizador y picateclas en Bitup Alicante.
A veces hago malware (pocas veces ya), pero, definitivamente, mi sitio esta los Red Team. Quiero dar las gracias a mi compañero Kalrong por dejarme un huequecito en su humilde morada y permitirme mostraros un caso muy interesante de un reto Web de un CTF.
Antes de nada, decir que este reto lo monté yo, y, curiosamente nadie consiguió resolverlo. A a mi parecer no es un reto muy difícil, pero si que tiene un final de estos de darle bastante al coco. Así que… Manos a la obra!
Lo primero que nos encontramos es una bonita vista donde se muestra una representación satírica, mezcla entre lenguajes de programación y algunos personajes de la serie de death note. Pero dejando atrás la comedia vamos a echar un vistazo al contenido de la página.
Recordamos (de los anteriores writeups) que lo primero que debemos hacer en un pentesting web es hacernos un mapa de la plataforma web, obteniendo cada una de las referencias a otras vistas y así ampliar el rango de páginas donde pondremos en práctica nuestro análisis de vulnerabilidades.
Buceando por el código de la página vemos referencias a ficheros de estilos y scripts comunes de Boostrap, nada extraño, lo necesario para hacer funcionar la template cover de Bootstrap. Así que descartamos, en esta vista, la posibilidad de obtener cualquier tipo de información útil de estos ficheros.
Si le probamos a preguntarle al robots.txt como hacíamos en el anterior reto, no muestra lo siguiente:
Ese disallow sospechoso que vamos a seguir, y que nos llevará a la vista, posiblemente más cutre de la historia.
Podemos ver que es una especie de lista de tareas, de las cuales estan todas tachadas menos una que dice algo así: «Despedir al becario de Front-End». Interesante…
Si observamos el código fuente descubrimos una tarea oculta que dice: «Esconder la intranet». Es decir, podemos pensar que hay una intranet en el Servidor. No encontramos nada interesante, de nuevo, ni en los estilos ni en los scripts. Así que llegado a este punto, donde sólo tenemos 2 vistas y con las que no parece que podamos interactuar, toca ampliar el abanico de forma manual, y con manual me refiero a que toca fuzzear un poco.
Oigo voces en la lejanía maldiciendo por haber escuchado la palabra «fuzzear», bueno para todos aquellos haters del descubrimiento de nuevas rutas aplicando la fuerza bruta, quiero transmitirles un mensaje de esperanza: con cualquier tool vas a dar con la solución.
Y esto es porque a mi también me parece que en un ctf, tirar de fuerza bruta es algo que no mola, a pesar de que en la vida real es otra cosa, pero… en la jungla todo vale ¿no?
Yo que soy un poco hipster, voy a utilizar el script de nmap http-enum, y sólo lo hago por llevar la contraria a todos esos fanboys de dirhunt, gobuster, photon, burp y otras hierbas. Pero que vamos… no hace falta que lloreis, es tan tan facil de sacar que podríais hasta hacerlo a manopla.
El nmap descubre 2 rutas nuevas: /l y /r (por dios! cuanta complejidad), entramos a la primera, por ejemplo, parece que se trata de un login, vamos a probamos a autenticarnos con datos por defecto completamente aleatorios = admin : patatas
Y después de intentarlo nos trae a la misma vista de nuevo, doy por hecho que ha fallado el intento de acceso.
Podemos suponer que puede que haya algún tipo de inyección sql, y con todo lo que hemos visto, sabiendo que estamos en una plataforma bajo Nodejs, todo apunta
a que, si hay una base de datos, lo más normal sería encontrarse un Mongodb (recordamos que es una base de datos NoSQL). Y por tanto vamos a probar a intentar
romper la «posible» query con medio de las inyecciones comunes a mongodb.
Y después de un rato intentando inyectar, y sin éxito y mucho dolor de cabeza, nos planteamos la posibilidad de que igual no es inyectable (bueno pero decirle al amigo sqlmapero que le tire a ver que saca).
Algún lumbreras, recordará del ToDo que salía la frase: «Plagarize of Tindermon«, y pensará: Pues igual se han plagiado de la prueba de Tindermon de Navaja Negra 2018, igual consiste en formar la inyección por medio de una corrupción de carácteres unicode
en la petición HTTP fruto de enviar determinados emojis (revisar el post de Ryan Kelly).
Y si alguien intenta esto y lo veo en los logs me pondré muy feliz, porque significa que los chicos de Ka0labs llegaron a romper muchas cabezas, además de la mía, aquel precioso día. Pero no, la respuesta no va por ahí, ni tampoco se haya en ningún javascript de la vista. Por tanto, pasemos a echar un vistazo a la otra ruta.
Y vaya… tanto rato peleandonos y todo por no haber mirado antes en el /r o /r(egister). Pues vamos a probar a registrarnos, como no, con un usuario y password completamente aleatorios = admin : patatas , y al darle a enviar nos sucede lo siguiente:
Nos lanza un curioso error «Error! Invalid password Format». Pero si nos hemos fijado habremos notado 2 cosas muy graves:
No ha llegado a enviar la petición, eso significa que el error es fruto de alguna validación por parte del Javascript de la página.
Venga seamos serios… ¿nadie esta flipando porque ese error da a presuponer que hay un formato base de las contraseñas?
En el código nos encontramos un único script con un curioso nombre “__.js” que tiene todas las papeletas de contener la función que esta evaluando el formato de la contraseña. Cuando lo abrimos nos encontramos con el contenido en formato mono linea, y al parecer ofuscado.
Se abre un frente claro, y es desofuscarlo e interpretar el código, pero estoy seguro que algún erudito ha visto la vía fácil, y es que en las primeras palabras del script, nos sale un bonito base64 y una palabra clave: “test”. Si decodeamos ese base64, obtenemos una cadena como la siguiente.
Que tras evaluarla rápidamente, podemos asegurar que se trata del pattern por el formato típico de una expresión regular. Y si hemos descubierto esto, no será difícil entender que ese “test” corresponde a la función test() de javascript para para comparar una cadena de entrada con la expresión regular.
Podríamos desofuscar el código (es más, yo lo haré en una sección de este writeup que estará publicado en gitlab también), pero kalrong me va a matar como me pase con la extensión. Así que nos dirigimos al registro a tratar de poner en uso lo que hemos averiguado.
Probamos la siguiente combinación= patatas:d4rkS0ul1234567
Y al intentar registrarnos nos saca el siguiente error: “You are not allowed to register. Maybe you want try to login”, que nos arroja que no podemos registrarnos, que probemos a iniciar sesión. Y eso haremos, pues sabiendo el formato de las contraseñas, podemos probar todas las combinaciones posibles hasta llegar a dar con la contraseña de un usuario, nada, son sólo 1 millon de combinaciones distintas, eso es pan comido. Pero.. ¿y qué usuario? Probemos con admin, porque tiene toda la pinta. (Y de ahí lo de despedir al de FrontEnd)
Y después de un ratillo obtnemos como resultado el paso con la contraseña “d4rkS0ul7777777”.
La insistencia nos ha llevado a un panel, al estilo del anterior reto (writeup-fbi). A priori nos atreveríamos a decir que viene a cumplir la misma función, es decir, hacer una petición HTTP, pero con la clara diferencia de que esto no es PHP, sino que es nodejs, y en node no vamos a contar con nuestros amigos los wrappers y otras fumadas, al igual que no habrá ningún escapeshellcmd que bypassear, pues la url va directamente a la función que realiza la petición. Si no me crees, adelante, puedes probarlo, y comprobarás que no hay posibilidad a un command injection, puesto que no ejecuta ningún comando.
Entonces, si descartamos un LFI, y el Command Injection, así como un Inyección NoSQL…
¿Qué nos queda?
Pues si no te había venido antes a la cabeza, es un caso típico de Server Side Request Forgery (SSRF) y Cross Site Port Attack (XSPA, de este no tenía ni idea que se llamaba así), la primera es una vulnerabilidad que nos permite forzar al servidor web a realizar peticiones a la destinación que se le precise, esto da paso al ataque XSPA que se utiliza para mapear los puertos y/o servicios que están corriendo en la máquina que realiza las peticiones, en base a las respuestas obtenidas con las diferentes solicitudes. Y no lo digo yo, lo dice Chema.
¿Y cómo realizamos este ataque?
Pues, por definición, si queremos hacer peticiones desde una máquina así misma, procederemos a utilizar el nombre reservado conocido como “localhost” o para los más posturetas “127.0.0.1” (pero seguro que todo desarrollador web lo sabía de antemano), que básicamente se apunta a sí misma. Y ahí la gracia del asunto; si el Servidor Web hace una petición a la dirección localhost estaría haciendo una petición HTTP a su propia máquina, y más concretamente, a su servidor Web local (por defecto en el puerto 80), dicho esto, una petición a localhost:22 le estaría haciendo una petición HTTP al puerto 22 (o por defecto al puerto donde suele correr el servicio SSH) y, en caso de interpretar el protocolo HTTP (con el que hacemos la petición), es posible que nos devuelva una respuesta.
Vale, pues vamos a ponerlo en acción!
Y de primeras nos encontramos que no nos esta devolviendo ninguna respuesta HTTP sino que nos muestra un peculiar error de: “bad format”.
Y no, aquí no hay javascript que valga, esto ya es cosa del backend. Pero entonces…
¿Cómo querrá que hagamos la petición?
Pues aquí lo que vamos haciendo es ir probando de diferentes formas, y después de un largo rato tenemos los siguientes casos que permiten que se haga la petición.
http://php.com
https://php.com
http://www.php.com
http://php.org
http://php.com/abcdef
http://bitupalicante.es
http://b1tuP4liCaNte.es
Y nos damos cuenta con todos estos casos, que satisfacen a un mismo patron que podemos determinar (con esta plataforma):
- La cadena debe comenzar por el protocolo http o https obligatoriamente
- Puede contener o no el subdominio www permite ningún otro, por tanto sólo apunta a dominios principales.
- El dominio o nombre, sólo puede estar compuesto por letras y números.
- Se requiere una extensión obligatoriamente, y sólo hemos dado con 3 válidas: [es,com,org]
- Permite añadir cualquier subcarpeta o directorio, siempre y cuando se cumplan las condiciones anteriores.
Y como hemos podido comprobar, hemos reconstruido a partir de casos que nos pasaban el control, el patron que posiblemente se acabe pareciendo a lo que hay detrás (y curiosamente si, se parece bastante). Aunque, permitirme hacer un apunte, y es que esto tiene toda la pinta de haber un WAF por en medio que nos esta aplicando ese filtrado
y nos esta jodiendo. (y de ahí que en el ToDo saliera lo del WaxFFFF).
Aunque cabe decir que conocer este patron de la regex no nos ha aportado mucho más que conocimiento de lo que esta pasando, pues si nosotros queremos que se haga un loopback en la petición tendríamos que conseguir mandar localhost o 127.0.0.1 y parece que el WAF esta haciendo bien su trabajo. Entonces…
¿Qué nos queda?
Bueno, la clave del reto o dificultad (por llamarlo de alguna forma) reside en este paso, en el cual es crucial tener una mente abierta y ser creativo. Atendiendo al propio funcionamiento de HTTP y fijandonos en las propias respuestas que nos daba a ejemplos de webs como: http://bitupalicante.es, nos daremos cuenta que no accede realmente a esa página, sino que finalmente acaba rehaciendo la petición al puerto 443. ¿Y por qué sucede esto? Pues porque lo que nos esta devolviendo el Servidor Web es un código de redirección, en cristiano, un HTTP 302.
Y aquí podríamos utilizar el comodín del administrador, y leer atentamente la pista que se nos dan:
«Kira, be carefull who you point… Remember that a 30x could cause you to shoot yourself.»
Que viene a decirnos que tengamos cuidado dónde apuntamos, pues un 30x puede hacer que nos disparemos a nosotros mismos. Y de eso se trata, tenemos que marcarnos un Froilán.
¿Y cómo nos disparamos en el pie?
Pues si controlamos el servidor al que se hace la petición, podemos devolver, ante una HTTP Request, una Respuesta HTTP con un Código de Redirección hacia una nueva dirección, ¿Y que metemos en esa nueva dirección?
Pues podríamos poner: localhost (a secas, a pelo, tal cual)
¿Y cómo puede ser que el WAF se lo coma?
Pues porque ese «Waf» no es más que un maldito middleware cutre en el router del nodejs, es decir, que valida sólo la entrada a la función que irá en la propia petición, y no los saltos que esta de lo que esta responda. Y de ahí que en la pista se diga: 30x y no 302, pues recordamos que los códigos 3XX son los de redirección (era más facil verlo con un 30X que con un 3XX).
Y de igual forma que se utiliza localhost, se puede acceder directamente a un puerto = localhost:22, o a la URL que nosotros queramos.
Entonces, manos a la obra, conociendo esto, que es la propia definición de XSPA, vamos a automatizarlo con un pequeño servidor en nodejs (puesto a hacerlo en node, lo hacemos todo en node), que dado un numero de puerto mande un codigo de redirección a localhost con ese puerto. Y con ello empezaremos a mapear todos los servicios que nos respondan a peticiones HTTP simples.
Recordad, que tenemos un rango de puertos del 1 al 65536 (máximo con esos 16 bits o 2 bytes que podemos especificar en el campo de puerto destino de las cabeceras TCP). Así que nada, un bonito bucle for con curl desde el 1 hasta el 65536 que enviará peticiones al recurso /death-note, pasandole por post la URL de nuestro dominio (.es, .org o .com) en el que estará escuchando nuestro server en nodejs que será el que reciba ese contador del bucle for, y devolverá la redirección hacia dicho puerto, mostrando tanto el contenido de la respuesta como el tamaño.
Y bueno, como podemos ver, nos encontramos el puerto 1999 con una pequeña vista levantada, donde en la respuesta HTTP a dicha página, nos encontramos con la FLAG del reto! Bang! en todo el pie.
Espero que os haya gustado el reto, que hayais aprendido a utilizar la nueva tecnica del Froilán y que os de la motivación necesaria para seguir haciendo CTFs y disfrutando mucho.
Feliz hacking!