Il path traversal esiste ovunque il Suo codice usi input esterno per costruire un percorso e non impedisca che quel percorso venga risolto al di fuori della directory prevista. CWE-22 è la voce che lo descrive. Individui ogni punto di questo tipo, smetta di far fornire agli utenti i nomi dei file e, per l'input che resta, controlli il percorso risolto.
Cosa ottiene un attaccante
La lettura è solo l'inizio. A seconda di ciò che il codice fa con il percorso, un attaccante può leggere file inattesi ed esporre dati sensibili, creare o sovrascrivere programmi e librerie ottenendo così l'esecuzione di codice, oppure corrompere file fino a rendere il prodotto inutilizzabile o a bloccare l'accesso agli utenti. OWASP include tra i file raggiungibili il codice sorgente e la configurazione dell'applicazione.
I file protetti da un meccanismo di sicurezza sono gli obiettivi peggiori. La lettura di un file di password consente attacchi di brute force contro gli account, e l'aggiunta di un nuovo account allo stesso file può aggirare l'autenticazione.
La portata dipende dalla piattaforma. Su Windows un attaccante può muoversi solo all'interno della partizione che contiene la web root, su Linux sull'intero disco. I controlli di accesso del sistema operativo continuano comunque ad applicarsi.
Individuare i punti che costruiscono percorsi
Parta dal sink, non dall'URL. Elenchi ogni chiamata che apre, legge, serve, include o scrive un file, quindi risalga da ciascun argomento di tipo percorso fino alla sua origine. Tutto ciò che tocca la richiesta va inserito nell'elenco, comprese le chiamate nascoste dietro le Sue funzioni wrapper e gli helper del framework: controlli quindi anche i loro chiamanti.
L'input è più ampio della query string. Gli esempi di OWASP comprendono get-files.jsp?file=report.pdf, get-page.php?home=aaa.html e some-page.asp?page=index.html, e anche un cookie. Questo frammento è adattato dall'esempio di OWASP (originariamente di Wikipedia):
$template = 'blue.php';
if ( isset( $_COOKIE['TEMPLATE'] ) )
$template = $_COOKIE['TEMPLATE'];
include ( "/home/users/phpguru/templates/" . $template );
Un cookie con TEMPLATE=../../../../../../../../../etc/passwd fa risalire include() fino alla directory root e restituire il file delle password. I parametri chiamati file, page, template o f sono i soliti sospetti, ma i nomi non sono una guida affidabile. Segua il flusso dei dati.
Verifichi tre forme:
- Traversal relativo: sequenze
../che risalgono fuori dalla directory prevista. - Percorsi assoluti:
?f=/etc/passwdo?f=/var/www/html/get.php, che funzionano quando l'input viene usato come percorso completo. - Remote include:
?page=http://other-site/...quando il meccanismo di include accetta URL.
I messaggi di errore dettagliati aiutano l'attaccante, perché rivelano percorsi reali su cui fare tentativi. Controlli cosa restituisce la Sua applicazione quando un file manca.
Perché le blocklist falliscono
Rimuovere ../ non funziona, perché lo stesso traversal ha molte grafie:
%2e%2e%2f,..%2fe%2e%2e/significano tutti../...%5ce%2e%2e%5csignificano..\, aspetto rilevante su Windows, dove entrambi i separatori funzionano.%252e%252e%255cè la forma a doppia codifica di..\. I web container eseguono un solo livello di decodifica, quindi il valore arriva ancora codificato. Se il Suo codice o una libreria lo decodifica una seconda volta, diventa traversal dopo che il controllo è già stato eseguito...%c0%afe..%c1%9csono ulteriori codifiche di../e..\.- Un byte nullo, come in
?file=secret.doc%00.pdf, può far vedere a un'applicazione Java l'estensione.pdfmentre il sistema operativo vede.doc. Gli attaccanti lo usano per aggirare la validazione.
Il filtraggio si ritorce contro se stesso anche per un altro motivo. Se si rimuove ../ in modo sequenziale da .../...//, i caratteri rimasti formano di nuovo ../. Filtrare solo / è inutile se il filesystem accetta \.
Prima correzione: smettere di accettare percorsi
Il controllo migliore è eliminare l'input dell'utente dal percorso. Usi indici anziché parti di nomi di file. L'esempio di OWASP è un file di lingua: l'utente invia 5 e il server lo associa alla lingua, invece di fidarsi di un nome restituito.
Per i download, memorizzi i file sotto un ID e li serva tramite ID, in modo che l'utente non fornisca mai un nome o un'estensione. L'esempio del template diventa:
$templates = [1 => 'blue.php', 2 => 'red.php'];
$id = (int) ($_COOKIE['TEMPLATE'] ?? 1);
$template = $templates[$id] ?? 'blue.php';
include '/home/users/phpguru/templates/' . $template;
Se deve proprio accettare un nome, lo validi rispetto a valori noti e legittimi e rifiuti tutto il resto. Usi una allowlist rigorosa di caratteri, al massimo un . nel nome del file, nessun separatore di directory e un elenco di estensioni consentite. Rifiuti l'input errato anziché tentare di ripulirlo.
Seconda correzione: canonicalizzare, poi controllare
Quando l'input deve raggiungere un'API per i file, lo decodifichi una sola volta, poi lo risolva con una funzione di canonicalizzazione integrata prima di validarlo:
| Linguaggio | Funzione |
|---|---|
| C | realpath() |
| Java | getCanonicalPath() |
| ASP.NET | GetFullPath() |
| Perl | realpath() o abs_path() |
| PHP | realpath() |
Queste funzioni eliminano le sequenze .. e i link simbolici. Dopo la risoluzione, verifichi che il risultato si trovi ancora all'interno della directory di base e rifiuti la richiesta in caso contrario. Un esempio in PHP, con $name già passato dalla allowlist:
$base = realpath('/home/users/phpguru/templates');
$full = realpath($base . '/' . $name);
if ($full === false || strpos($full, $base . DIRECTORY_SEPARATOR) !== 0) {
http_response_code(404);
exit;
}
Mantenga ogni controllo sul server. La validazione lato client si aggira con estrema facilità.
Limitare i danni
Tenga i file di configurazione sensibili fuori dalla web root. Su Windows IIS, tenga la web root lontano dal disco di sistema, così che il traversal non possa raggiungere le directory di sistema. I chroot jail e le code access policy limitano dove i file possono essere letti o scritti e riducono i danni quando un controllo viene dimenticato.
Per i test, riproduca le codifiche descritte sopra su ogni parametro individuato, insieme a un percorso assoluto e a un URL remoto. Un penetration test lo farà in modo sistematico.
Da dove iniziare
- Elenchi ogni chiamata di gestione dei file nel Suo codice, aggiunga i wrapper e gli helper del framework che le circondano e risalga da ciascun argomento di tipo percorso fino alla sua origine, compresi cookie e header.
- Sostituisca i nomi di file forniti dall'utente con ID associati ai file sul server, partendo dai download e dalla selezione di template o lingua.
- Per ogni caso residuo, decodifichi una sola volta, canonicalizzi con la funzione della piattaforma e rifiuti qualsiasi cosa che si risolva fuori dalla directory di base.
- Elimini il codice a blocklist che rimuove
../o/e lo sostituisca con una allowlist di caratteri, estensioni e, ove possibile, valori. - Sposti la configurazione sensibile fuori dalla web root e smetta di restituire messaggi di errore che rivelano percorsi di file.
- Testi ogni parametro con le varianti codificate, a doppia codifica, assolute e remote, e conservi i casi come regression test.
