Path traversal występuje wszędzie tam, gdzie kod buduje ścieżkę z danych zewnętrznych i nie zapobiega temu, by ta ścieżka wskazała miejsce poza zamierzonym katalogiem. Opisuje to wpis CWE-22. Znajdź wszystkie takie miejsca, przestań pozwalać użytkownikom podawać nazwy plików, a dla pozostałych danych wejściowych sprawdzaj ścieżkę po jej rozwiązaniu.
Co zyskuje atakujący
Odczyt to dopiero początek. W zależności od tego, co kod robi ze ścieżką, atakujący może czytać nieoczekiwane pliki i ujawnić wrażliwe dane, tworzyć lub nadpisywać programy i biblioteki, a tym samym wykonać kod, albo uszkodzić pliki tak, że produkt przestanie działać lub zablokuje użytkowników. OWASP wymienia wśród osiągalnych plików kod źródłowy aplikacji i konfigurację.
Najgorsze cele to pliki chronione przez mechanizm bezpieczeństwa. Odczyt pliku z hasłami umożliwia zgadywanie haseł do kont metodą brute force, a dopisanie do niego nowego konta może obejść uwierzytelnianie.
Zasięg zależy od platformy. W Windows atakujący może poruszać się tylko w obrębie partycji, na której leży katalog główny serwera WWW, w Linuksie - po całym dysku. Kontrola dostępu systemu operacyjnego nadal obowiązuje dodatkowo.
Jak znaleźć miejsca budujące ścieżki
Zacznij od sink, a nie od adresu URL. Wypisz każde wywołanie, które otwiera, czyta, serwuje, dołącza (include) lub zapisuje plik, a następnie prześledź każdy argument ze ścieżką do jego źródła. Na listę trafia wszystko, co ma styczność z żądaniem, także wywołania ukryte za własnymi funkcjami opakowującymi i helperami frameworka, więc sprawdź również ich wywołania.
Dane wejściowe to coś więcej niż query string. Przykłady OWASP obejmują get-files.jsp?file=report.pdf, get-page.php?home=aaa.html i some-page.asp?page=index.html, a także ciasteczko. Poniższy fragment jest adaptacją przykładu z OWASP (pierwotnie z Wikipedii):
$template = 'blue.php';
if ( isset( $_COOKIE['TEMPLATE'] ) )
$template = $_COOKIE['TEMPLATE'];
include ( "/home/users/phpguru/templates/" . $template );
Ciasteczko TEMPLATE=../../../../../../../../../etc/passwd sprawia, że include() wspina się do katalogu głównego i zwraca plik z hasłami. Parametry o nazwach file, page, template czy f to zwykli podejrzani, ale nazwy nie są wskazówką. Śledź dane.
Sprawdź trzy formy:
- Ścieżki względne: sekwencje
../, które wychodzą poza zamierzony katalog. - Ścieżki bezwzględne:
?f=/etc/passwdlub?f=/var/www/html/get.php, które działają, gdy dane wejściowe są użyte jako cała ścieżka. - Zdalne include'y:
?page=http://other-site/..., gdy mechanizm include akceptuje adresy URL.
Rozbudowane komunikaty o błędach pomagają atakującemu, bo ujawniają prawdziwe ścieżki, które można zgadywać. Sprawdź, co zwraca Twoja aplikacja, gdy pliku brakuje.
Dlaczego blocklisty zawodzą
Usuwanie ../ nie działa, ponieważ ta sama sekwencja traversal ma wiele zapisów:
%2e%2e%2f,..%2fi%2e%2e/oznaczają../...%5ci%2e%2e%5coznaczają..\, co ma znaczenie w Windows, gdzie działają oba separatory.%252e%252e%255cto podwójnie zakodowana forma..\. Kontenery webowe wykonują jeden poziom dekodowania, więc taki ciąg dociera nadal zakodowany. Jeśli Twój kod lub biblioteka zdekoduje go ponownie, stanie się traversalem już po wykonaniu kontroli...%c0%afi..%c1%9cto kolejne kodowania../i..\.- Bajt null, jak w
?file=secret.doc%00.pdf, może sprawić, że aplikacja w Javie zobaczy.pdf, podczas gdy system operacyjny zobaczy.doc. Atakujący używają tego do obchodzenia walidacji.
Filtrowanie obraca się też przeciwko sobie. Jeśli z .../...// usuniesz ../ sekwencyjnie, pozostałe znaki znów tworzą ../. Filtrowanie samego / jest bezużyteczne, jeśli system plików akceptuje \.
Poprawka pierwsza: przestań przyjmować ścieżki
Najlepszą kontrolą jest usunięcie danych od użytkownika ze ścieżki. Używaj indeksów zamiast fragmentów nazw plików. Przykład OWASP to plik językowy: użytkownik przesyła 5, a serwer mapuje to na język, zamiast ufać zwróconej nazwie.
W przypadku pobrań przechowuj pliki pod ID i serwuj je po ID, by użytkownik nigdy nie podawał nazwy ani rozszerzenia. Przykład z szablonem wygląda wtedy tak:
$templates = [1 => 'blue.php', 2 => 'red.php'];
$id = (int) ($_COOKIE['TEMPLATE'] ?? 1);
$template = $templates[$id] ?? 'blue.php';
include '/home/users/phpguru/templates/' . $template;
Jeśli musisz przyjmować nazwę, waliduj ją względem znanych dobrych wartości i odrzucaj resztę. Użyj ścisłej allowlisty znaków, co najwyżej jednej kropki . w nazwie pliku, zakazu separatorów katalogów i listy dozwolonych rozszerzeń. Odrzucaj złe dane zamiast próbować je oczyszczać.
Poprawka druga: kanonizuj, potem sprawdzaj
Tam, gdzie dane muszą trafić do API plikowego, zdekoduj je raz, a przed walidacją rozwiąż je wbudowaną funkcją kanonizacji:
| Język | Funkcja |
|---|---|
| C | realpath() |
| Java | getCanonicalPath() |
| ASP.NET | GetFullPath() |
| Perl | realpath() lub abs_path() |
| PHP | realpath() |
Funkcje te usuwają sekwencje .. i dowiązania symboliczne. Po rozwiązaniu ścieżki upewnij się, że wynik nadal leży w Twoim katalogu bazowym, a jeśli nie, odrzuć żądanie. Szkic w PHP, z $name już po przejściu przez 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;
}
Wszystkie kontrole wykonuj po stronie serwera. Walidację po stronie klienta obejdzie się bez trudu.
Ogranicz szkody
Trzymaj wrażliwe pliki konfiguracyjne poza katalogiem głównym serwera WWW. W Windows z IIS umieść katalog główny serwera poza dyskiem systemowym, aby traversal nie sięgnął katalogów systemowych. Środowiska chroot i polityki dostępu kodu ograniczają miejsca, z których można czytać i do których można zapisywać pliki, a także zmniejszają szkody, gdy jakaś kontrola zostanie pominięta.
Do testów odtwórz opisane wyżej kodowania dla każdego znalezionego parametru, razem ze ścieżką bezwzględną i zdalnym adresem URL. Test penetracyjny zrobi to systematycznie.
Od czego zacząć
- Wypisz każde wywołanie obsługujące pliki w swoim kodzie, dodaj otaczające je wrappery i helpery frameworka, a następnie prześledź każdy argument ze ścieżką do źródła, wliczając ciasteczka i nagłówki.
- Zastąp nazwy plików podawane przez użytkownika identyfikatorami mapowanymi na pliki po stronie serwera, zaczynając od pobrań oraz wyboru szablonu lub języka.
- W każdym pozostałym przypadku zdekoduj dane raz, skanonizuj je funkcją platformy i odrzucaj wszystko, co rozwiązuje się poza katalog bazowy.
- Usuń kod blocklist, który wycina
../lub/, i zastąp go allowlistą znaków, rozszerzeń oraz, gdy to możliwe, samych wartości. - Przenieś wrażliwą konfigurację poza katalog główny serwera WWW i przestań zwracać komunikaty o błędach ujawniające ścieżki plików.
- Przetestuj każdy parametr wariantami zakodowanymi, podwójnie zakodowanymi, bezwzględnymi i zdalnymi, a te przypadki zachowaj jako testy regresji.
