Path traversal, kodunuzun harici girdiyle bir yol (pathname) oluşturduğu ve bu yolun amaçladığınız dizinin dışına çözümlenmesini engellemediği her yerde ortaya çıkar. Bunun kaydı CWE-22 numarasıyla tutulur. Bu tür tüm noktaları bulun, kullanıcıların dosya adı vermesine izin vermeyi bırakın ve geriye kalan girdiler için çözümlenmiş yolu kontrol edin.
Saldırgan ne elde eder?
Dosya okumak yalnızca başlangıçtır. Kodun yolla ne yaptığına bağlı olarak saldırgan beklenmeyen dosyaları okuyup hassas verileri ifşa edebilir, program ve kütüphaneler oluşturup ya da üzerine yazıp kod çalıştırabilir veya dosyaları bozarak ürünün çalışmasını durdurabilir ya da kullanıcıları dışarıda bırakabilir. OWASP, erişilebilecek dosyalar arasında uygulama kaynak kodunu ve yapılandırma dosyalarını sayar.
Bir güvenlik mekanizmasının arkasındaki dosyalar en kritik hedeflerdir. Bir parola dosyasını okumak hesaplara yönelik brute force denemelerine imkân verir, o dosyaya yeni bir hesap eklemek ise kimlik doğrulamayı atlatabilir.
Erişim alanı platforma göre değişir. Windows'ta saldırgan yalnızca web root'u barındıran bölüm içinde dolaşabilir, Linux'ta ise diskin tamamında. İşletim sisteminin erişim kontrolleri bunların üzerinde yine geçerlidir.
Yol oluşturan noktaları bulmak
URL'den değil, sink'ten başlayın. Dosya açan, okuyan, sunan, include eden veya yazan her çağrıyı listeleyin, ardından her yol argümanını kaynağına kadar izleyin. İstekle (request) temas eden her şey listeye girer. Kendi wrapper fonksiyonlarınızın ve framework yardımcılarının arkasına gizlenmiş çağrılar da buna dahildir, bu yüzden onların çağıranlarını da kontrol edin.
Girdi, query string'den daha geniştir. OWASP örnekleri arasında get-files.jsp?file=report.pdf, get-page.php?home=aaa.html ve some-page.asp?page=index.html bulunur, bir de cookie örneği vardır. Aşağıdaki kod parçası, OWASP'ın (aslında Wikipedia'nın) örneğinden uyarlanmıştır:
$template = 'blue.php';
if ( isset( $_COOKIE['TEMPLATE'] ) )
$template = $_COOKIE['TEMPLATE'];
include ( "/home/users/phpguru/templates/" . $template );
TEMPLATE=../../../../../../../../../etc/passwd değerini taşıyan bir cookie, include() çağrısının kök dizine kadar çıkmasına ve parola dosyasını döndürmesine yol açar. file, page, template veya f adlı parametreler olağan şüphelilerdir, ancak isimlere güvenilmez. Veriyi izleyin.
Üç biçimi kontrol edin:
- Göreli traversal: Amaçlanan dizinden dışarı çıkan
../dizileri. - Mutlak yollar: Girdi yolun tamamı olarak kullanıldığında işe yarayan
?f=/etc/passwdveya?f=/var/www/html/get.php. - Uzak include'lar: Include mekanizması URL kabul ediyorsa
?page=http://other-site/....
Ayrıntılı hata mesajları saldırgana yardımcı olur, çünkü tahmin yürütebileceği gerçek yolları ele verir. Uygulamanızın eksik bir dosya için ne döndürdüğünü kontrol edin.
Blocklist'ler neden yetersiz kalır?
../ ifadesini silmek işe yaramaz, çünkü aynı traversal'ın pek çok yazımı vardır:
%2e%2e%2f,..%2fve%2e%2e/hepsi../anlamına gelir...%5cve%2e%2e%5c,..\anlamına gelir. Her iki ayırıcının da çalıştığı Windows'ta bu önemlidir.%252e%252e%255c,..\ifadesinin çift kodlanmış (double-encoded) biçimidir. Web container'lar tek seviye decoding yapar, dolayısıyla değer hâlâ kodlanmış olarak gelir. Kodunuz veya bir kütüphane onu bir kez daha çözerse, kontrol çalıştıktan sonra traversal'a dönüşür...%c0%afve..%c1%9c,../ile..\ifadelerinin başka kodlamalarıdır.?file=secret.doc%00.pdförneğindeki gibi bir null byte, bir Java uygulamasının.pdfgörmesine, işletim sisteminin ise.docgörmesine neden olabilir. Saldırganlar bunu doğrulamayı atlatmak için kullanır.
Filtreleme kendi mantığı içinde de ters teper. .../...// ifadesinden ../ dizilerini sırayla silerseniz, geriye kalan karakterler yeniden ../ oluşturur. Dosya sistemi \ kabul ediyorsa yalnızca / filtrelemek de anlamsızdır.
Birinci düzeltme: Yol kabul etmeyi bırakın
En iyi kontrol, kullanıcı girdisini yoldan çıkarmaktır. Dosya adlarının parçaları yerine indeksler kullanın. OWASP'ın örneği bir dil dosyasıdır: kullanıcı 5 gönderir ve sunucu bunu ilgili dile eşler, geri dönen bir ada güvenmez.
İndirmeler için dosyaları bir ID altında saklayın ve ID ile sunun; böylece kullanıcı hiçbir zaman ad veya uzantı vermez. Şablon örneği şu hale gelir:
$templates = [1 => 'blue.php', 2 => 'red.php'];
$id = (int) ($_COOKIE['TEMPLATE'] ?? 1);
$template = $templates[$id] ?? 'blue.php';
include '/home/users/phpguru/templates/' . $template;
Bir ad kabul etmek zorundaysanız, bilinen geçerli değerlere karşı doğrulayın ve gerisini reddedin. Katı bir karakter allowlist'i kullanın, dosya adında en fazla bir . bulunsun, dizin ayırıcı olmasın ve izin verilen uzantıların bir listesi olsun. Kötü girdiyi temizlemeye (sanitize) çalışmak yerine reddedin.
İkinci düzeltme: Önce canonicalize edin, sonra kontrol edin
Girdinin bir dosya API'sine ulaşması gereken yerlerde, önce bir kez decode edin, ardından doğrulamadan önce yerleşik bir canonicalization fonksiyonuyla çözümleyin:
| Dil | Fonksiyon |
|---|---|
| C | realpath() |
| Java | getCanonicalPath() |
| ASP.NET | GetFullPath() |
| Perl | realpath() veya abs_path() |
| PHP | realpath() |
Bu fonksiyonlar .. dizilerini ve sembolik bağlantıları (symbolic link) ortadan kaldırır. Çözümlemeden sonra sonucun hâlâ temel dizininizin içinde olduğunu doğrulayın, değilse isteği reddedin. $name değişkeninin allowlist kontrolünden geçtiği varsayılan bir PHP taslağı:
$base = realpath('/home/users/phpguru/templates');
$full = realpath($base . '/' . $name);
if ($full === false || strpos($full, $base . DIRECTORY_SEPARATOR) !== 0) {
http_response_code(404);
exit;
}
Tüm kontrolleri sunucuda tutun. İstemci tarafı doğrulama kolayca atlatılır.
Hasarı sınırlayın
Hassas yapılandırma dosyalarını web root'un dışında tutun. Windows IIS'te web root'u sistem diskinin dışında tutun; böylece traversal sistem dizinlerine ulaşamaz. Chroot jail'ler ve code access policy'ler dosyaların nerede okunup yazılabileceğini kısıtlar ve bir kontrol gözden kaçtığında hasarı sınırlar.
Test için yukarıdaki kodlamaları bulduğunuz her parametreye karşı, bir mutlak yol ve bir uzak URL ile birlikte tekrar oynatın. Bir sızma testi bunu sistematik biçimde yapar.
Nereden başlamalı?
- Kodunuzdaki her dosya işleme çağrısını listeleyin, çevresindeki wrapper'ları ve framework yardımcılarını ekleyin, her yol argümanını cookie ve header'lar dahil kaynağına kadar izleyin.
- Kullanıcının verdiği dosya adlarını, sunucuda dosyalara eşlenen ID'lerle değiştirin; indirmelerle ve şablon veya dil seçimiyle başlayın.
- Kalan her durum için bir kez decode edin, platform fonksiyonuyla canonicalize edin ve temel dizinin dışına çözümlenen her şeyi reddedin.
../veya/ifadelerini silen blocklist kodunu kaldırın ve yerine karakterler, uzantılar ve mümkünse değerler için bir allowlist koyun.- Hassas yapılandırmayı web root'un dışına taşıyın ve dosya yollarını ele veren hata mesajları döndürmeyi bırakın.
- Her parametreyi kodlanmış, çift kodlanmış, mutlak ve uzak varyantlarla test edin ve bu senaryoları regresyon testi olarak saklayın.
