En este post estaremos resolviendo el challenge web llamado «NextPath» de Hack The Box, calificado como Medium. Se trata de un challenge ya retirado. Es muy interesante, ya que nos permitirá ver el uso de blacklist para intentar resolver vulnerabilidades y cómo esto nunca es buena idea.
Comenzamos, como siempre, accediendo a la web que nos proporcionan:

No parece que haya nada interesante, salvo las imágenes de los usuarios. Si analizamos el acceso con Burp Suite, veremos cómo se obtienen estas imágenes:

Este tipo de situaciones suelen ser muy propicias para hacer ataques de Directory Traversal. Intentamos un payloads básicos:
/etc/passwd => Invalid format
/../../../../../etc/passwd => Invalid format

Parece claro que existen algunas restricciones a la hora de incluir valores en el parámetro. Para poder realizar un análisis en profundidad, analizamos el código fuente que maneja las llamadas a dicha API («app/pages/api/team.js») y empezamos por el apartado donde se comprueba el parámetro recibido frente a una expresión regular:
(...)
const ID_REGEX = /^[0-9]+$/m;
(...)
// Check format
if (!ID_REGEX.test(query.id)) {
console.error("Invalid format:", query.id);
res.status(400).end("Invalid format");
return;
}
En este caso parece que el desarrollador está buscando que solamente se incluyan números. Sin embargo, hay un matiz muy importante en la expresión regular y es la «m» final. Esta «m» indica que la búsqueda se hace multilínea, lo cual quiere decir que si hay dos o más líneas, basta con que una de ellas cumpla la expresión regular para que el formato sea adecuado.
Así las cosas, evaluamos un payload básico compuesto por dos líneas: la primera de ellas contendrá un número y un carácter de fin de línea; y la segunda línea contendrá un string aleatorio que no cumple la expresión regular. Accediendo a la bilbia de las expresiones regulares (https://regex101.com/) y sabiendo que NextJS está basado en JavaScript, evaluamos nuestro payload:

Ahora, lanzamos el siguiente payload en la petición comprobando que estamos en lo cierto y que el código, al recibir un payload con saltos de línea, simplemente comprueba que una de ellas coincida con la expresión regular. Esto es así porque probablemente el desarrollador pensó recibir un string simple, pero nosotros como atacantes podemos incluir cualquier cosa como valor del parámetro.
1%0dfoo

Bien, tenemos un nuevo error y es que no se encuentra el archivo. Vemos que la estructura del archivo tiene un nombre compuesto por nuestra primera línea «1» y la segunda «foo», además de añadir un prefijo y un sufijo relativos a rutas del servidor («team» y «png»). Volviendo al inicio, intentaremos incluir un payload básico de Directory Traversal:
1%0dfoo/../../../../../../../etc/passwd

Como vemos, tenemos un error diferente y es que la aplicación comprueba secuencias de Directory Traversal. Analicemos la parte de código asociada a dicha detección:
// Prevent directory traversal
if (query.id.includes("/") || query.id.includes("..")) {
console.error("DIRECTORY TRAVERSAL DETECTED:", query.id);
res.status(400).end("DIRECTORY TRAVERSAL DETECTED?!? This incident will be reported.");
return;
}
Como vemos, dado que «query.id» a priori es un string, lo que hace la función es buscar apariciones de «/» y «..», secuencias muy típicas en Directory Traversal. En nuestro caso, esto se cumple ya que el string será nuestro payload completo. Para hacer bypass a esta función utilizaremos una técnica muy común en web que es «Parameter Pollution». Esta técnica se basa en ver cómo responde el código ante la recepción de un mismo parámetro dos o más veces.
En este reto estamos ante una aplicación basada en NextJS que no es más que una extensión de React que proporciona no solo front-end (como React) sino también back-end. Este back-end, eso sí, está basado en Node.js. En cualquier caso, le preguntamos a nuestro nuevo amigo ChatGPT qué es lo que ocurre cuando hacemos Parameter Pollution en NextJS y nos deja clara la respuesta:

Como siempre, ChatGPT es una ayuda y no debemos tomarlo como la bilbia, pero vamos a probarlo. Lo primero que tenemos que tener en cuenta, antes de lanzar el ataque, es qué pasará con la validación. Volvamos al código y a las dos comprobaciones:
query.id.includes("/")
query.id.includes("..")
Teniendo en cuenta que ahora «query.id» es un array con dos valores, lo que estará haciendo la función es lo siguiente (pseudocódigo):
array.includes("/")
array.includes("..")
Y esto es muy relevante, porque cambia por completo el enfoque. El desarrollador hizo la función pensando que recibiría un string y busca apariciones de los caracteres que desea filtrar. Sin embargo, el pseudocódigo «array.includes()» buscará dentro del array elementos cuyos valores sean «/» o «..». Lo importante es que no buscará apariciones dentro de cada elemento, sino elementos concretos con dichos valores exactos. Por dejarlo claro:
query.id = ["1%0d","abc","/"] => query.id.includes("/") => TRUE
query.id = ["1%0d","ab/c","fo..fo"] => query.id.includes("/") => FALSE
Así que, visto esto, no debemos preocuparnos de incluir cadenas prohibidas siempre y cuando consigamos hacer Parameter Pollution. Fíjate en el siguiente payload:
1%0d&id=/../../../../../../../etc/passwd
Como vemos, pasamos un parámetro «id» dentro del propio payload, haciendo Parameter Pollution. Lanzamos la petición y vemos cómo hemos bypasseado regex y filtro.

Ahora, nos fijamos en el último obstáculo y es que debemos ajustar nuestro payload para evitar que se añada el «png» final. Para ello, vemos la siguiente línea que trunca el valor del parámetro a 100 caracteres:
const content = fs.readFileSync(filepath.slice(0, 100));
Así que, ajustando el payload para que cumpla justo con el tamaño, podemos hacer una prueba de concepto inicial y explotar Directory Traversal:
1%0d&id=/../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../etc/passwd

Ahora necesitamos leer el flag, pero como está en «/flag.txt», no logramos ajustar nunca el tamaño de nuestro payload para que cumpla con los 100 bytes:

Para hacer un bypass de este punto, utilizaremos una técnica basada en el uso de links simbólicos en Linux a través de los procesos. Aunque no es muy común su uso, cada proceso tiene asociados una serie de enlaces simbólicos entre los que destacan las siguientes estructuras:
/proc/[PID]/root => /
/proc/[PID]/task/[TID]/root => /
Tal como indicamos, ambas mapean a «/». En el primer caso, es un enlace simbólico a la ruta raíz para cada proceso, mientras que el segundo caso es lo mismo pero para cada hilo dentro de un proceso. Haciendo uso de la segunda estructura, podemos generar el siguiente payload para obtener el flag, cumpliendo con los 100 caracteres para eliminar el trailing «.png».
1%0d&id=/../../../../../../../../../../../../../../../../../../../../../../../../../../proc/1/task/10/root/flag.txt

En nuestro caso, justamente el proceso con ID 1 y con el hilo 10 están activos. Gracias a ello, obtenemos el flag. Si al lanzar la petición obtienes un error, debes lanzar un Burp Suite Intruder para localizar un ID de proceso y su ID de hilo asociados que estén activos y que, justamente, cumplan con las longitudes necesarias (por ejemplo, no puedes usar ID de proceso 20 e ID de hilo 10, porque entonces el truncamiento de 100 caracteres hace que no accedas al flag.txt.
~km0xu95
Comments are closed.