Solución de Fetch the Flag CTF 2022: Disposable Message
Michael Aquilina
10 de noviembre de 2022
0 minutos de lectura¡Gracias por jugar Fetch con nosotros! Felicitaciones a los miles de jugadores que participaron enFetch the Flag CTF. Y muchas gracias a los Snykers que crearon, probaron y documentaron los desafíos!
En esta publicación, hablaremos del desafío Disposable Message del evento Fetch the Flag CTF 2022 de Snyk. Este desafío consistía en usar técnicas de inyección CSS para explotar una página web vulnerable y obtener la bandera. Disposable Message resultó particularmente difícil debido a una estricta política de seguridad de contenido (CSP). Aprovechar que los mensajes expiraban al visitarlos fue clave para superar esta política.
En esta publicación, explico cómo ideé un exploit funcional para este desafío. Te contaré el proceso, desde la fase de investigación hasta el uso de los hallazgos para crear un exploit funcional. El código del exploit está escrito en Python, así que asumiré que tienes cierta familiaridad con este lenguaje. También será útil conocer CSS, JavaScript y HTML, pero explicaré la mayoría de los conceptos mencionados.
Investigación
El desafío comienza con una descripción muy breve: “Este mensaje expiró”, junto con un enlace a una página web.
La descripción da a entender que la página web nos permite crear mensajes que se pueden visitar una sola vez antes de que el servidor los elimine. El hecho de que esto se mencione en la descripción probablemente indica que es importante (intenta tenerlo presente para más adelante).
Al visitar la página, encontramos lo siguiente:

La página tiene un cuadro de texto para escribir mensajes. Si escribimos un mensaje y hacemos clic en el botón ¡Enviar!, aparece la siguiente página:

Vemos una nueva URL con el formato /view/<message-id> (a partir de ahora, la llamaremos página de visualización del mensaje). También vemos un botón para copiar el mensaje al portapapeles y otro que dice Pedir al bot de administración que lo visite. Si abrimos la URL de visualización del mensaje en el navegador, podemos ver el mensaje que creamos. Si visitamos la misma página por segunda vez, el servidor web responde con el código de estado 404.

Volvamos al botón Pedir al bot de administración que lo visite. Podemos deducir que, al hacer clic, un bot de administración remoto ve el mensaje en un navegador. Tenemos bastante certeza porque, poco después de hacer clic, nuestro mensaje expira. Además, el nombre bot de administración da a entender que este navegador contiene la bandera que necesitamos.
Ahora revisemos el código fuente de la página para ver qué nos llama la atención.
Ubicación de la bandera
En la página de visualización del mensaje encontramos el siguiente fragmento de código HTML:
Según el comentario del código, este elemento div tendrá un atributo data-flag cuyo valor se obtiene de nuestras cookies. Podemos confirmarlo fácilmente abriendo las herramientas de desarrollo del navegador y editando las cookies para ver qué sucede.
La opción más obvia para el nombre de la cookie de la bandera sería “flag”. Si establecemos el valor “SNYK{hello-world}” en una cookie llamada “flag” y luego volvemos a abrir la página de visualización del mensaje, veremos que el atributo se completa correctamente:

Inyección CSS
Al revisar el script incluido en la página de visualización del mensaje, también encontramos el siguiente código JavaScript:
El script comprueba si existe un parámetro de consulta “color” en la URL. Si existe, coloca el valor de ese parámetro en una etiqueta style. Por último, la etiqueta style se inserta dinámicamente en nuestra página web.
Lo destacable de esta etiqueta style es que se construye mediante formato de cadenas. El formato de cadenas nos permite salir de la etiqueta style y ampliarla para incluir cualquier valor CSS que queramos. Esto se conoce como una vulnerabilidad de inyección CSS.
Podemos probar fácilmente si esto es vulnerable a la inyección CSS estableciendo el parámetro color en algo como color=00ff00;font-size:80px y confirmando que el tamaño de la fuente cambia correctamente al valor especificado:

La inyección CSS nos permite exfiltrar información de la página que está en el navegador de un usuario. Este ataque consiste en crear varios selectores CSS que activan URLs cuando los valores coinciden con selectores específicos.
Por ejemplo, si quisiéramos filtrar el atributo data-flag de un elemento HTML div, podríamos establecer el parámetro color en algo como:
El ^= selector anterior activará la URL asociada si el valor del atributo comienza con la cadena especificada.
Esto significa que, por ejemplo, si el atributo data-flag del div comienza con a, el navegador visitará http//evil.com/a . Si el valor comienza con b, visitará http//evil.com/b, y así sucesivamente…
Si controlamos el servidor evil.com, sabremos qué páginas se visitaron. Con suficientes intentos usando selectores CSS, podemos extraer el atributo data-flag carácter por carácter.
Política de seguridad de contenido
Los navegadores web modernos admiten una función llamada política de seguridad de contenido (CSP), que restringe el contenido que se puede usar en una página web. Este desafío incluye una CSP que nos impide usar directamente las técnicas de inyección CSS descritas anteriormente.
Podemos revisar la CSP con las herramientas de desarrollo del navegador para inspeccionar los encabezados de la página:

Observa que la sección img-src de la política especifica que solo se permiten “self” y “data”. Esto significa que solo podemos usar URLs que pertenezcan al mismo dominio que la página de mensajes desechables. En concreto, no podemos usar un dominio de nuestra propiedad para comprobar qué URLs se visitaron mediante la inyección CSS. Sin embargo, podemos aprovechar los mensajes desechables del desafío para superar esta restricción.
Sabemos que los mensajes desechables solo se pueden ver una vez. Si ya se visitó un mensaje, este devolverá el código de estado 404. Por lo tanto, podemos usar URLs de mensajes desechables en nuestros selectores de inyección CSS y comprobar su código de estado para detectar si se visitaron.
Resumen de la investigación
Estos son los datos clave de la investigación que nos ayudarán a crear el exploit. Si no entendiste por completo la sección anterior (o solo la hojeaste), con entender esto bastará para avanzar a la siguiente sección:
La página de visualización del mensaje es vulnerable a la inyección CSS mediante el parámetro de consulta
?color=. Esto significa que podemos aplicar estilos arbitrarios a la página.La política de seguridad de contenido solo permite URLs del mismo dominio. Esto significa que el navegador bloqueará todas las URLs externas.
Un mensaje solo se puede ver una vez. Esto significa que recibiremos un código de estado 200 si nunca se visitó el mensaje y un código 404 si ya se visitó.
La bandera está en el atributo
data-flagde la página de visualización del mensaje. Este valor se completa con el parámetro de cookie “flag” del navegador. Es probable que el bot de administración tenga esta cookie configurada con la bandera correcta.
Veamos cómo podemos combinar todos estos detalles para crear un exploit.
El exploit
Podemos usar la inyección CSS para filtrar información sobre la bandera, carácter por carácter, mediante el atributo data-flag. Sin embargo, debemos usar una URL del mismo dominio controlado por el desafío. Al exfiltrar caracteres mediante la inyección CSS, lo importante es saber si se visitó o no una URL para identificar qué caracteres se están filtrando.
Por suerte, podemos generar mensajes dentro del dominio de mensajes desechables, uno por cada carácter. Podemos asociar cada URL con selectores CSS que incluyan cada carácter:
Podemos hacer todo esto en Python con algo como:
La función generate_payload debe crear un selector CSS que active la URL asociada si nuestra suposición es correcta. La función sería así:
La función generate_message debe crear un mensaje nuevo mediante el endpoint POST /new, que se activa con el botón ¡Enviar!. Una vez creado, debemos guardar las URLs del bot de administración y de visualización del mensaje que genera esta llamada. Podemos extraer estas URLs de la respuesta mediante una expresión regular:
Una vez generados nuestros mensajes y las cargas útiles de sus selectores CSS correspondientes, debemos crear un mensaje más para que lo visite el bot de administración. Este mensaje recibirá nuestra carga útil y activará las URLs de nuestros mensajes según si nuestra suposición coincide con el valor del atributo “data-flag”.
Después de activar el bot de administración, debemos esperar unos segundos para que recupere y renderice la página. Cuando terminemos de esperar, revisamos el código de estado de todas las URLs de los mensajes asociados a nuestras suposiciones para ver cuál devuelve 404. Si encontramos un código 404, sabremos que el bot de administración visitó la URL y podremos marcar la suposición como correcta.
Codificación de la cadena de consulta
Quizás notaste al leer el código anterior que usamos quote_plus para codificar toda la cadena de consulta, incluidos el nombre del parámetro y el delimitador de la cadena de consulta ?color=. Esto se debe a que se ignoran las cargas útiles que se pasan al bot de administración mediante la cadena de consulta.
Para resolver este problema, podemos engañar al bot de administración para que piense que la cadena de consulta forma parte de la ruta de la URL y, como resultado, la incluya en la URL de visualización del mensaje que visita.
Por ejemplo, si tenemos la siguiente ruta de URL: /admin-bot/f785781-55f4-4eca-8565-8511f12a4ffc**?color=**ffffff%7D…
Entonces, el bot de administración ignorará el parámetro de la cadena de consulta y solo visitará la ruta de URL /view/f785781-55f4-4eca-8565-8511f12a4ffc
Sin embargo, si codificamos por completo el parámetro de la cadena de consulta, pasará a formar parte de la ruta de la URL:
/admin-bot/f785781-55f4-4eca-8565-8511f12a4ffc**%3Fcolor%3D**ffffff%7D…
Al parecer, el servidor web decodifica este valor antes de enviárselo al bot para que lo visite. Esto significa que la URL de la página que visitará será:
/view/f785781-55f4-4eca-8565-8511f12a4ffc?color=ffffff}...
Cómo recuperar la bandera completa
Ahora que sabemos cómo hacer suposiciones correctamente, debemos repetir el proceso hasta que nuestra suposición coincida por completo con la bandera. Cuando lleguemos a un }, sabremos que podemos detenernos.
También sabemos, por los valores de banderas de desafíos anteriores, que las banderas de Snyk comienzan con 'SNYK{' y que el contenido entre llaves son UUID. Esto es útil porque podemos limitar nuestro alfabeto a los caracteres “a-f” y “0-9”. Así reduciremos considerablemente el tiempo necesario para probar nuestras suposiciones en cada iteración.
Código del exploit
Al combinar todo esto, el código fuente final queda así:
Al ejecutar este código, obtenemos la satisfactoria victoria de ver cómo se filtra la bandera SNYK carácter por carácter:

Mensaje desechado, desafío resuelto
Descubrimos que el desafío Disposable Message era vulnerable a la inyección CSS. Aunque una política de seguridad de contenido restrictiva complicaba las cosas, pudimos desarrollar un exploit para filtrar la bandera. Lo logramos aprovechando el código de estado 404 que se devuelve al visitar los mensajes.
¡Espero que hayas disfrutado de esta solución de CTF y que incluso hayas aprendido algo nuevo! Los desafíos CTF son una excelente forma de aprender sobre exploits del mundo real y, como resultado, tener más herramientas para defenderte de ellos en tus propios sistemas. ¿Quieres saber cómo encontramos las demás banderas? Visita nuestra página de soluciones de Fetch the Flag para descubrir cómo lo hicimos.



