¡Hola a todos! He participado en la creación del CTF de Var Group en RootedCon2025, celebrada en marzo, con un reto web de dificultad difícil llamado The Var Store y en este post voy a enseñaros su resolución.
Al abrir la URL de la descripción del reto, encontramos un inicio de sesión que enlaza a un registro.
Podemos crear un usuario y usar estas credenciales para iniciar sesión en la tienda.
En la respuesta de la petición de registro vemos que se devuelven los atributos del usuario: username y role.

Al entrar con el usuario creado, podemos ver que hay disponible un formulario para introducir un cupón de descuento y la compra de la flag. Por ahora el intento de compra nos indica que el sistema de pagos está en mantenimiento y que sólo las compras gratis (con 100% de descuento) podrán ser procesadas.

Al intentar introducir un código de descuento aleatorio veremos que el servidor devuelve un mensaje de error, y si intentamos algún tipo de inyección, indicará igualmente que el código no es válido, por lo que aparentemente está bien sanitizado.
Ya que parece que no podemos avanzar en la parte autenticada, volvemos a revisar la petición de registro.
Añadiendo un parámetro role, tal como devolvía el servidor en la respuesta legítima, comprobamos que la aplicación está usando el valor que enviamos.
Con el error de la petición anterior podemos ver que se permiten los roles "ADMIN" y "USER", por lo que podemos intentar crear un administrador.
Los roles que pueden crearse son ADMIN y USER, y dado que al enviar "admin" hay un bloqueo, probamos a crear un usuario normal para comprobar que usa correctamente nuestro valor.
En base a las peticiones que hemos enviado, sabemos que:
- La aplicación usa el parámetro role enviado.
- Los roles se están validando.
- Se asignan al usuario los roles USER y ADMIN, en mayúscula.
- Los valores en minúscula son aceptados y validados.
Conociendo esto, una de las posibilidades del comportamiento del backend que podemos deducir es:
- Se recibe el parámetro role.
- Se valida que el valor no sea "admin", por ejemplo, con una expresión regular, con cualquier variación de mayúsculas y minúsculas (pudiendo comprobar esto enviando distintos valores como "Admin", "aDMin", etc) para poder bloquear la petición si la fuente no es confiable.
- Se convierte el valor a mayúscula.
- Se comprueba que es un rol existente.
- Se crea el usuario con el rol indicado.
Si esta hipótesis es correcta, el valor que introducimos se ve modificado al convertirlo a mayúscula, dando posibilidad a explotar una colisión de case mapping.
Esta técnica se basa en que dos caracteres distintos se convierten al mismo caracter al hacer un lower/upper (en este caso, upper). Hay herramientas para comprobar estas colisiones, como unicollider, con la que podemos generar una variación de "ADMIN".
Para confirmar que la hipótesis anterior es real, enviamos el rol "admin" sustituyendo la "i" normal por esta "i" en unicode. Se puede comprobar que la conversión se realiza correctamente con una ejecución en local.
Con el usuario que acabamos de crear, podemos iniciar sesión de nuevo y comprobar que ahora tenemos acceso a varios códigos de descuento que suman un 80%.
Dado que los códigos no suman el 100% necesario, podemos intentar explotar una race condition para canjear dos veces el mismo código, pero debido al rate limit y al comportamiento del backend, veremos que no es posible explotarla enviando las peticiones como secuencia o en paralelo mediante last-byte sinc.
Ahora podemos comprobar en la web que se ha aplicado correctamente un descuento del 100% y que podemos comprar la flag.
Si estuvisteis en Rooted y participasteis en el CTF, gracias por la participación y espero que lo disfrutaseis tanto como nosotros, y si no pudisteis estar, nos vemos en la siguiente edición ;)
No hay comentarios:
Publicar un comentario