Yol geçişi ve güvenli depolama
- Yol geçişinde saldırgan, dosya adı alanına üst dizin ifadeleri yazar ve izin verilen dizinin dışındaki sistem dosyalarına ulaşır.
- Bu derste kodlama varyantlarını, kanonik yol denetimini ve gerçek bir CVE vakasını çözümlü örnekle incelersin.
Puan bütün etkinlikleri cevaplayınca hesaba yazılır. Bu dersi tamamlayınca +55 XP alacaksın. Giriş yapmadan ilerlersen puan yalnız bu tarayıcıda durur.
Bazı uçlar, kullanıcının verdiği bir dosya adını doğrudan diskteki bir yola ekler. Altay Kargo'nun fatura indirme ucu şöyle çalışıyor olsun: gelen ad değeri, sunucuda faturaların tutulduğu dizine eklenip dosya okunur. Kullanıcı ad parametresine üst dizine çıkan bir dizi nokta nokta yazarsa, birleşen yol faturalar dizininden çıkıp sistem dosyalarına uzanır.
İşletim sistemi bu yolu çözerken her nokta nokta ifadesini bir üst dizine çıkış olarak okur; sonuçta yol, hiç faturalar dizinine uğramadan başka bir dosyaya çıkar. Sunucu dosyayı olduğu gibi okuyup gövdeye yazarsa, saldırgan izin verilen dizinin tamamen dışındaki bir dosyayı indirmiş olur.
Dosya adı diske nasıl çıkar
Yol geçişini anlamak için işletim sisteminin bir yolu nasıl çözdüğünü izle. Sunucu, sabit bir kök dizinle kullanıcının verdiği adı uç uca ekler. Ortaya çıkan metin ham bir yoldur; işletim sistemi onu açmadan önce normalleştirir. Normalleştirme sırasında her nokta nokta ifadesi bir üst dizine çıkış olarak uygulanır.
Kullanıcı yeterince üst dizine çıkış koyarsa, çözülen yol kök dizinin tamamen dışına taşar. Kritik nokta şudur: sunucu adı düz metin olarak birleştirir, oysa işletim sistemi o metnin içindeki nokta nokta ifadelerini birer komut gibi okur. Bu uyumsuzluk, bütün yol geçişi ailesinin kaynağıdır.
Neden bir uç dosya adını kullanıcıdan alır? Çünkü çoğu zaman kolay görünür. Fatura, rapor, dışa aktarılan bir dosya; her birinin bir adı vardır ve bu adı adrese koymak hızlı bir çözümdür. Geliştirici adı bir etiket gibi düşünür, oysa işletim sistemi onu bir yol ifadesi olarak okur.
Nokta nokta, eğik çizgi ve mutlak yol, dosya sistemi için anlamlı komutlardır. Kullanıcı girdisi bu komutları taşıyabildiği sürece, ad bir etiket değil bir yönlendirme aracıdır. Yol geçişinin kökü, bu bakış farkında yatar.
// Gelen istek: /fatura?ad=../../etc/passwd const kok = "/veri/faturalar"; const yol = kok + "/" + ad; // /veri/faturalar/../../etc/passwd // İşletim sistemi her ../ ifadesini bir üst dizine çıkış olarak çözer: // /veri/faturalar/../../etc/passwd -> /etc/passwd
Burada sık yapılan hata, nokta nokta dizisini bir kez arayıp silmektir. Silme, yazımın tek bir biçimini kapatır; kök nedeni kapatmaz. Saldırganın kodlanmış, çift kodlanmış ya da iç içe yazımları filtrenin arasından geçer.
Doğru yaklaşım, girdinin yazımıyla değil, çözülen yolun gerçekte nereye çıktığıyla ilgilenmektir. Bu ders boyunca bütün savunma tek bir soruya iner: birleşen yol izin verilen dizinin içinde mi, dışında mı?
Etkinlik 1 / 6
Fatura adı parametresindeki satırlardan yol geçişi denemesi taşıyanları yakalayan bir desen yaz.
Yazdıkça eşleşen satırlar işaretlenir. Hedef: yalnız doğru satırlar eşleşsin.
Kodlama varyantları ve dosya dahil etme
CWE bu sınıfı CWE-22 olarak kaydeder. Kök neden, sunucunun dosya yolunu kurarken kullanıcı girdisini bir dizin sınırlayıcısı olarak değil, düz metin olarak birleştirmesidir. Saldırgan yalnız düz nokta nokta yazmaz; sunucu bunu engellemişse aynı anlamı taşıyan başka yazımlar dener: URL kodlanmış hâli, çift kodlama ya da doğrudan mutlak bir yol.
Her yazım, aynı kök nedeni farklı bir kılıkla sömürür. Windows sunucularında ayrıca ters eğik çizgi ve sürücü harfi de aynı işi görür; ayrım işletim sistemine göre değişse de mantık hep aynıdır.
Varyantları bir arada görmek işini kolaylaştırır. Düz nokta nokta en bilinenidir. URL kodlu biçimde eğik çizgi yüzde-iki-f, nokta ise yüzde-iki-e olur. Çift kodlamada yüzde işaretinin kendisi de kodlanır, böylece tek geçişli bir çözüm deseni açığa çıkarmaz.
Mutlak yol, kök dizini hiç kullanmadan doğrudan hedef dosyaya gider. Windows tarafında ters eğik çizgi ve sürücü harfi aynı işi görür. Hepsinin ortak noktası tektir: kullanıcı girdisi, dizin sınırını aşan bir yol ifadesine çevrilir. Savunma da tek olmalıdır: çözülmüş sonucun konumunu denetle, yazımla uğraşma.
Yol geçişi tek başına okuma yapar, ama bazı dillerde daha ileri gider. Dosya dahil etme açığında (CWE-98) uygulama, kullanıcının verdiği yolu yalnız okumaz, onu kod olarak da yürütür.
Yerel dosya dahil etmede saldırgan sunucudaki bir dosyayı uygulamanın koduna katar; uzak dosya dahil etmede ise dış bir adresten gelen içeriği çalıştırır. Böylece yol geçişi, dosya okumadan sunucuda kod çalıştırmaya kadar tırmanır. Savunma aynı kalır: kullanıcı girdisi hiçbir zaman doğrudan bir dosya yoluna ya da dahil etme çağrısına girmez.
Yerel dosya dahil etme neden bu kadar tehlikeli olabilir? Çünkü okuma, bazı koşullarda çalıştırmaya dönüşür. Uygulama kullanıcının verdiği yolu bir dahil etme çağrısına veriyorsa, saldırgan içeriğini önceden denetlediği bir dosyayı hedef gösterebilir. Örneğin kendi kontrol ettiği veriyi önce bir günlük ya da oturum dosyasına yazdırır, sonra o dosyayı dahil ettirir.
Okuma açığı, bu klasik köprüyle kod çalıştırmaya tırmanır. Buradan çıkan kural nettir: kullanıcı girdisi bir dahil etme çağrısına asla doğrudan girmez. Dosya seçimi her zaman sunucunun sabit bir eşlemesinden geçer.
Etkinlik 2 / 6
Sunucu, gelen adreste bir kez URL kod çözümü yapıp sonra ../ dizisini arıyor ve engelliyor. Çift kodlanmış %252e%252e%252f dizisi bu denetimi neden atlatır?
Tespit: erişim kaydı imzası
Sunucu bu denemeleri farklı biçimlerde kayda yazar. Bazı web sunucuları ve web uygulama güvenlik duvarları (WAF), nokta nokta desenini gördüğünde isteği kendisi 400 ile reddeder ve bunu erişim kaydına yazar. Önde böyle bir süzgeç yoksa istek uygulamaya kadar ulaşır ve dosya bulunamadı ya da izin reddedildi hatası döner; bu hata da normal bir fatura isteğinde görünmez. Aynı IP'den kısa sürede farklı üst dizin derinliğiyle gelen istekler bir yol keşfi aracının imzasıdır.
Tespitin değeri yalnız o anki saldırıyı görmek değildir. Reddedilen bir yol geçişi denemesi, aynı kaynağın ağda ne aradığını gösterir. Bu denemeleri izleyen bir ekip, saldırının keşif aşamasını erkenden görür ve hedef sertleşmeden önce önlem alır. Bu yüzden 400 dönen istekler bile değerli bir sinyaldir; başarısız bir deneme, bir sonraki başarılı denemenin habercisidir.
Etkinlik 3 / 6
Fatura ucunun erişim kaydında yol geçişi denemesine ait satırları işaretle.
Şüpheli satırlara tıkla. Birden fazla olabilir.
Savunma: kanonik yol denetimi
En dayanıklı çözüm, dosya adını hiç kullanıcıdan almamaktır. Fatura, veritabanında bir kayıt numarasıyla eşleşir; uç bu numarayı alır, gerçek dosya yolunu sunucu kendisi kurar. Kullanıcı adı hiç görmez, dolayısıyla hiçbir yol karakteri anlam taşımaz.
Dosya adı gerçekten gerekiyorsa, birleşen yol kanonik hâle getirilir (bütün üst dizin ifadeleri çözülür) ve sonucun izin verilen kök dizinin içinde kalıp kalmadığı denetlenir. Kök dizinin dışına çıkan her sonuç, adı ne olursa olsun reddedilir.
Kayıt numarasıyla eşlemenin de bir koşulu vardır. Numara bir yol ifadesi taşıyamaz, ama başka bir müşterinin faturasını gösterebilir.
Saldırgan numarayı bir artırıp komşu faturayı isterse, bu kez yol geçişi değil yetkisiz nesne erişimi (IDOR) doğar. Bu yüzden uç, numarayı kayda çevirirken kaydın oturumdaki kullanıcıya ait olup olmadığını da denetler. Yol güvenliği ile yetki denetimi birlikte çalışır; biri diğerinin yerini tutmaz.
Kanonik yol denetimi iki adımdır. Önce birleşen yolu çözersin: path.resolve bütün nokta nokta ifadelerini uygular ve gerçek bir mutlak yol üretir. Sonra bu mutlak yolun, izin verilen kök dizinin altında kalıp kalmadığını denetlersin.
Denetim, kök dizinin yoluyla başlayıp başlamadığına bakar; sona bir dizin ayracı da eklersin ki benzer adlı komşu bir dizin yanlışlıkla geçmesin. Çözülen yol kök dizinin dışına çıkıyorsa, adı ne olursa olsun reddedilir. Bu yöntem silmeye değil, çözülmüş sonucun gerçek konumuna bakar; bu yüzden her kodlama varyantını birlikte yakalar.
// Güvensiz: kullanıcı adını doğrudan yola ekliyor
const yol = path.join("/veri/faturalar", req.query.ad);
res.sendFile(yol);
// Güvenli: kanonik yol kök dizinin içinde mi diye denetleniyor
const kok = path.resolve("/veri/faturalar");
const yol = path.resolve(kok, req.query.ad);
if (!yol.startsWith(kok + path.sep)) return res.status(400).end();
res.sendFile(yol);Etkinlik 4 / 6
Yukarıdaki güvenli sürüme üst dizine çıkıp sistem dosyasına uzanan bir ad değeri gönderilirse ne olur?
Önce kodu oku, sonra çıktıyı seç.
const kok = path.resolve("/veri/faturalar");
const yol = path.resolve(kok, req.query.ad);
if (!yol.startsWith(kok + path.sep)) return res.status(400).end();Bu yüzden savunma, dizgeyi temizlemeye değil, çözülmüş yolun gerçek konumunu denetlemeye dayanır. Silme oyunu bitmez; konum denetimi ise tek bir kesin yanıt verir.
Etkinlik 5 / 6
Bir geliştirici, gelen ad parametresinden üst dizin dizgesini yalnız bir kez siliyor. Saldırgan iç içe geçmiş, silme sonrası yeniden aynı deseni kuran bir dize gönderirse ne olur?
Gerçek vaka ve kapanış
Bu modülün dört dersi aynı üç savunmayı farklı yorumlayıcılarda tekrarladı. İzin listesi, SSRF'te hedef adresini, yol geçişinde ise kök dizini sınırladı; her ikisinde de karar yasaklamak yerine onaylamakla verildi. Ayrıştırıcı sertleştirme XXE'de dış varlığı kapattı; burada kanonik yol denetimi aynı rolü üstlendi ve sorunu kaynağında kesti. Güvenli depolama, dosya yüklemede çalıştırılamayan bir depoyu ve rastgele adı, burada kayıt numarasıyla eşlemeyi kullandı; ikisi de kullanıcı girdisinin dosya yoluna dönüşmesini engelledi.
Bu üç ilkenin birlikte çalıştığını görmek önemlidir. Hiçbiri tek başına kusursuz değildir. İzin listesi unutulan bir yolu kaçırabilir, ayrıştırıcı ayarı bir bileşende atlanabilir, güvenli depolama yanlış yapılandırılabilir.
Katmanlar üst üste bindiğinde, bir katmandaki tek bir hata bütün sistemi açmaz. Güvenli tasarımın özü budur: tek bir kontrolün kusursuz olmasına değil, kontrollerin birbirini tutmasına güvenirsin. Dört ders boyunca gördüğün her savunma, bu derinlik mantığının bir örneğidir.
Derinleş:Gerçek bir vaka: Apache 2.4.49
2021'de Apache HTTP Server 2.4.49 sürümünde bir yol normalleştirme hatası ortaya çıktı (CVE-2021-41773). Kodlanmış üst dizin dizisi, adresleri Alias benzeri yönergelerle tanımlı dizinlerin dışındaki dosyalara eşledi. Bu dosyalar varsayılan require all denied kuralıyla korunmuyorsa istek başarılı oldu. CGI betikleri açıksa aynı hata, dosya okumadan uzaktan kod çalıştırmaya kadar uzandı.
Apache, açığın saldırılarda kullanıldığını duyurdu. İlk yama 2.4.50 sorunu tam kapatmadı; aynı sınıf ikinci bir kayıtla (CVE-2021-42013) döndü ve kesin düzeltme 2.4.51'de geldi. Ders iki yönlüdür: yol geçişi yalnız uygulama kodunda değil, altındaki sunucu yazılımında da yaşayabilir; ve bir yamanın yarım kapatması, aynı kök nedenin kolayca geri dönmesine yol açar. Bu yüzden kanonik yol denetimi hem kendi kodunda hem bağımlı bileşenlerin sürüm takibinde durur.
Aynı prensip yedekleme ve günlük dosyası indirme gibi başka uçlarda da geçerlidir. Altay Kargo'nun destek ekibi için hazırlanan günlük indirme özelliği, dosya adını sorgu parametresinden alıp doğrudan diske ekliyorsa, orada da aynı kanonik yol denetimi kurulmalıdır.
Son bir hatırlatma: yol geçişi savunması bir kez yazılıp bırakılmaz. Yeni bir dosya indirme, dışa aktarma ya da yedek ucu eklendiğinde, aynı kanonik yol denetimi oraya da kurulmalıdır. Her yeni uç, aynı sorunun yeni bir kapısıdır. Bu yüzden denetim, tek tek uçlarda değil, dosya okuyan ortak bir katmanda toplanırsa daha güvenilir olur.
Etkinlik 6 / 6
Doğru mu, yanlış mı?
Bir dosya sunucusu isteği 400 ile reddettiyse, saldırganın yol geçişi denemesi loglanmaya değmez.
Okumayı istediğin sırayla sürdürebilirsin. Ders, bütün etkinlikleri cevapladığında tamamlanır.