60 saniyede path normalizasyonu bypass’ları

Öyle bir kimlik doğrulama bypass (authentication bypass) kategorisi vardır ki; her iki bileşenin de kodunu okur, ikisinde de hiçbir hata bulamazsınız, ancak yine de admin paneline elinizi kolunuzu sallayarak girersiniz. Proxy yapılandırması doğrudur. Uygulamanın erişim kontrolü (access control) doğrudur. Hata, ikisinin iki farklı metne bakıyor olmasıdır.

İki ayrıştırıcı (parser), tek bir URL

Neredeyse her canlı ortam, bir isteği en az iki farklı yazılım parçası arasında böler: önde TLS’i sonlandıran ve yönlendirme (routing) kararları alan bir bileşen, arkada ise uygulamayı gerçekten çalıştıran bir servis. Her ikisi de URL yolunu (path) ayrıştırır. Ancak aynı fikirde olmak zorunda değillerdir.

Ön uç (front-end) izinli olup olmadığınıza karar verir. Arka uç (back-end) ise neyin çalışacağına karar verir. Ön uç, arka ucun daha sonra dönüştüreceği bir dizgiyle (string) eşleşme yaparsa; karar, handler çalıştığı sırada artık var olmayan bir yol hakkında verilmiş olur.

Tüm hata sınıfı bundan ibarettir. Geriye kalan her şey, hangi dönüşüme ulaşabileceğinizle ilgilidir.

İSTEK GET /public/..%2fadmin/users nginx ŞUNUNLA EŞLEŞTİRİR /public/..%2fadmin/users /admin/ öneki yok — İZİN VER AYNEN İLETİLDİ Tomcat DECODE EDER, SONRA YÖNLENDİRİR /admin/users admin rotasına ulaşıldı AYRIŞMA yetkilendirme kararı, handler çalıştığı sırada artık var olmayan bir yol hakkında verilmiştir
Her iki bileşen de dokümante edildiği gibi davranır. nginx, location eşleşmesinden önce asla percent-decode uygulamaz; konteyner ise yönlendirmeden önce decode eder ve yolu çözümler. Bypass bu boşlukta yaşar.

Bunu gösteren bir lab ortamı

Minimal kurulum: Önde nginx, arkasında Tomcat üzerinde çalışan bir uygulama. Proxy, admin ağacını bilinen klasik yöntemle engeller:

location /admin/ {
    return 403;
}

location / {
    proxy_pass http://127.0.0.1:8080;
}

Doğrudan istek, tasarlandığı gibi engellenir:

$ curl -si http://localhost/admin/users | head -1
HTTP/1.1 403 Forbidden

Şimdi aynı hedef, farklı yazılmış haliyle:

$ curl -si http://localhost/public/..%2fadmin/users | head -1
HTTP/1.1 200 OK

Burada nginx tarafında yanlış olan hiçbir şey yoktur. URI’yi location eşleşmesinden önce normalize eder — doğrudan bir /../ ifadesini çözer ve yinelenen slash’leri teke indirir — ancak eşleştirmeden önce percent-decode yapmaz. /public/..%2fadmin/users ifadesi /admin/ ile başlamadığı için deny bloğu hiçbir zaman uygulanmaz ve istek encoding bozulmadan proxy üzerinden iletilir.

Tomcat isteği alır, %2f ifadesini / olarak decode eder, .. segmentini çözer ve yönlendirir:

[http-nio-8080-exec-3] DispatcherServlet : GET "/admin/users", parameters={}

Her biri dokümante edildiği gibi davranan iki bileşen, birleşerek admin ağacının kimlik doğrulaması olmadan okunmasına (unauthenticated read) yol açar.

Denemeye değer dönüşümler

Encode edilmiş slash en bilinenidir. Ancak nadiren kullanılabilir olan tek dönüşümdür ve sıkılaştırılmış bir ortamda genellikle ilk kapatılan odur. Diğerleri de aynı kaynaktan beslenir — neyin “aynı yol” sayıldığına ilişkin farklılıklardan:

Path parametreleri. Servlet konteynerleri ;key=value parçalarını soyar. Düz dizgi eşleştirmesi yapan bir proxy, /admin;x=1/users yolunu /admin/ ile ilgisiz görür; Tomcat ise bunu admin rotası olarak görür.

Double encoding. Zincirdeki herhangi bir bileşen iki kez decode yapıyorsa — iletmeden önce bir kez decode eden bir proxy, artı bir kez daha decode eden bir konteyner — o zaman %252f ilk geçişten %2f olarak sağ çıkar ve ikincisinde / haline gelir.

Büyük/küçük harf duyarlılığı (Case). Ön uç location eşleştirmesi genellikle büyük/küçük harfe duyarlıdır. Büyük/küçük harfe duyarsız bir dosya sistemindeki arka uç ise duyarsızdır. /Admin/users, birisi için farklı bir metinken diğeri için tamamen aynı rotadır.

Sondaki biçimler (Trailing forms). /admin ile /admin/, /admin/. veya /admin%2e karşılaştırması. Tek bir biçim için yazılmış bir kural sıklıkla diğerlerini kaçırır; oysa framework’ler rutin olarak bu dört biçimi de aynı handler’a kanonikleştirebilir (canonicalise).

Bunu test etmenin verimli yolu bir payload listesi çalıştırmak değildir. Bilinen geçerli tek bir yol için ön uçtan hangi dönüşümlerin sağ çıktığını ve arka ucun hangilerini uyguladığını tam olarak tespit etmektir — ardından eşleşmesi yalnızca tek bir tarafın gerçekleştirdiği bir dönüşüme bağlı olan bir kural aramaktır.

Kanıt matrisi (Evidence matrix)

SinyalNeyi kanıtlarNegatif kontrolSavunucu doğrulaması
Proxy log’u, uygulamanın farklı çözümlediği encode edilmiş bir yolu korurGüvenlikle ilgili iki ayrıştırıcı (parser), farklı temsilleri yetkilendirmektedirKanonik korumalı yolu gönderin ve proxy’nin bunu engellediğini doğrulayınHam istek hedefini ve nihai uygulama rotasını aynı izleme (trace) kaydında yakalayın
Mutasyona uğratılmış yol, beklenen kimlik olmadan korunan handler’a ulaşırAyrıştırıcı uyumsuzluğu bir yetkilendirme sınırını aşmaktadırHer iki katmanda da özdeş şekilde normalize olan yakın bir mutasyon gönderin ve reddedilmesini bekleyinYalnızca bileşen bazında birim testler değil, canlı proxy/uygulama ikilisinde entegrasyon testleri ekleyin
Politika değerlendirmesinden önceki tek bir kanonikleştirme adımı tüm eşdeğer varyantları kapatırDüzeltme tek bir payload yerine temsil sınıfını adreslerDouble encoding, ayırıcılar, nokta segmentleri ve harf varyantlarını tekrar çalıştırınPolitika ve yönlendirme kodunun kullandığı normalize edilmiş yol baytlarını karşılaştırın
Doğrudan uygulama erişimi dışarıdan erişilebilir değildirProxy, tek hedeflenen denetim giriş noktası olarak kalırGüvenilmeyen bir segmentten arka uca karşı aynı isteği deneyin ve rota bulunamamasını bekleyinAyrıştırıcı davranışının yanı sıra ağ politikasını ve dinleyici (listener) maruziyetini doğrulayın

Sürekli gördüğüm model

Bu zafiyet, yetkilendirmenin uygulamayı değiştirmekten daha kolay geldiği için sınıra (edge) sonradan eklendiği her yerde ortaya çıkar. Bir proxy’deki path-prefix kuralı, bir WAF deseni, bir gateway rota eşleştiricisi — bir URL’yi bir dizgiyle karşılaştırarak izin verme/reddetme kararı alan ve bunu URL’yi çözümleyen koddan farklı bir katmanda yapan her şey.

Aynı iki ayrıştırıcı ayrışması (two-parser divergence), OAuth redirect doğrulamasını bozan şeydir — provider bir URL’yi yetkilendirir, tarayıcı bir başkasına gider — tıpkı OAuth notunun adım adım anlattığı gibi. Bu, birçok farklı kılığa bürünen tek bir hata sınıfıdır ve hepsinde düzeltme aynıdır.

Bulgunun yanlış önem derecesinde (severity) raporlanma eğiliminde olmasının nedeni de budur. Genellikle atlatılmış tek bir endpoint olarak kaleme alınır; bu da tek bir kurala yönelik tek satırlık bir düzeltmeyi davet eder. Oysa sorun hiçbir zaman o endpoint olmamıştır. Sorun, yetkilendirme sınırının uygulamanın gördüğü isteği göremeyen bir yerde durmasıdır ve bu boşluğu istismar etmenin genellikle birden fazla yolu vardır.

“Encoding’i engellemek” neden yanlış düzeltmedir?

Refleks haline gelmiş düzeltme, sınıra %2f geldiğinde reddetmektir. Bu, gösterdiğiniz isteği kapatır. Ancak hata sınıfını kapatmaz; çünkü bir sonraki dönüşüm hâlâ kullanılabilirdir ve yetkilendirme kararı hâlâ arka ucun yeniden yazacağı bir metin üzerinden verilmektedir.

Gerçek anlamda işe yarayan iki düzeltme vardır:

Kararı dönüşümün arkasına taşıyın. Yetkilendirmeyi uygulamada, framework rotayı çözümledikten sonra ve ham yola karşı değil, gerçekten seçtiği handler’a karşı zorunlu kılın. Proxy kuralları asıl kontrol olmak yerine derinlemesine savunma (defence in depth) katmanı haline gelir.

İki ayrıştırıcıyı uzlaştırın. Eğer bir ön uç kuralının belirleyici (authoritative) kalması gerekiyorsa, kural çalışmadan önce tam ve özdeş bir şekilde normalize edin — bir kez decode edin, göreli segmentleri çözümleyin, path parametrelerini soyun, arka uç yapıyorsa harfleri küçültün — ve normalizasyondan sonra hâlâ biçim değiştiren her şeyi iletmek yerine reddedin.

İkincisi göründüğünden daha zordur ve raporda açıkça belirtilmeye değerdir: İki ayrıştırıcıyı sonsuza kadar uyum içinde tutmak sürekli bir bakım taahhüdüdür; birinci seçenek ise buna olan ihtiyacı tamamen ortadan kaldırır.

Savunuculara teslim edilmesi gerekenler

Faydalı olan çıktı (artefact), çalışan tek bir payload değildir. Neden çalıştığını açıklayan iki gözlemdir: İşte proxy’nin eşleştirdiği dizgi, işte uygulamanın üzerinde çalıştığı dizgi ve işte ikisinin ayrıştığı nokta. Bu iki satırla düzeltme, altyapının sahibine son derece açık hale gelir. Bunlar olmadan elinize yalnızca %2f için yamalanmış bir sınır kuralı geçer ve aynı bulgu gelecek yıl farklı bir yazımla yeniden karşınıza çıkar.

Kaynaklar ve güncellik

Bu teknik not ne kadar güncel?

Kontrol edilen kaynaklar16 Ocak 2025

En son kaynak incelemesi, içerik güncellemesi veya yayın tarihi gösterilmektedir.

İnceleme DurumuYazar incelemesi tamamlandı

Yazar teknik incelemeyi tamamladı. Bu etiket tek başına laboratuvar yeniden üretimi iddiası taşımaz.