BITSCTF 2026 — rusty_proxy Çözümü
Rust ile yazılmış bir ters proxy, /admin yolunu basit bir ön ek kontrolüyle koruyor. Yolu küçük harfe çeviriyor ama hiçbir zaman kodunu çözmüyor; bu yüzden yüzde kodlamalı bir bayt kontrolü atlatıp doğrudan bayrağı döndüren Flask rotasına ulaşıyor.
BITSCTF 2026 — rusty_proxy
Bu web sorusu, ters proxy dünyasının klasik hatası üzerine kurulu: erişim kontrolünü yapan katman ile asıl backend, aynı yolun ne anlama geldiği konusunda farklı düşünüyor.
- Yarışma: BITSCTF 2026
- Kategori: Web
- Bileşenler: Python Flask backend önünde Rust (tokio) ile yazılmış bir ters proxy
- Bayrak biçimi:
BITSCTF{...}
İki servis veriliyor. Rust ters proxy 80 numaralı portu dinliyor ve trafiği 8080 üzerindeki Flask backend'e yönlendiriyor. Backend bayrağı /admin/flag adresinde sunuyor. Proxy'nin görevi, oraya kimsenin ulaşmasını engellemek.
1. Proxy'yi okumak
Tüm kapı tek bir küçük fonksiyondan ibaret:
fn is_path_allowed(path: &str) -> bool {
let normalized = path.to_lowercase();
if normalized.starts_with("/admin") {
return false;
}
true
}Fonksiyon yalnızca yolu küçük harfe çeviriyor ve /admin ile başlayıp başlamadığına bakıyor. Yüzde kodlamasını hiç çözmüyor, nokta parçalarını da çözümlemiyor. Test edilen şey, istek satırında gelen ham metnin ta kendisi — ve daha da önemlisi, proxy aynı metni değiştirmeden yukarıya iletiyor:
let mut head = format!("{} {} {}\r\n", meta.method, meta.path, meta.version);Yani proxy ile backend, aynı bayt dizisini kendi yorumuyla okuyor. Bu fark, zafiyetin ta kendisi.
Backend ise Cheroot üzerinde çalışan sade bir Flask uygulaması:
@app.route('/admin/flag')
def vault():
return jsonify({"flag": FLAG})2. Başlangıç durumu
Önce engelin gerçekten çalıştığını, hem de yalnızca birebir ön ekte devreye girdiğini doğrulayalım:
$ curl -s -i http://127.0.0.1:8188/admin/flag | head -2
HTTP/1.1 403 Forbidden$ curl -s http://127.0.0.1:8188/api/status
{"status":"running","uptime":"operational"}/admin ile başlayan her şey reddediliyor, geri kalan her şey geçiyor. Soru şu: proxy ile backend, bir yolun /admin ile başlayıp başlamadığı konusunda anlaşmazlığa düşebilir mi?
3. İlk fikir çalışmıyor
Akla ilk gelen aday çift eğik çizgi: //admin/flag. Proxy bunu küçük harfe çeviriyor, //admin/flag görüyor ve birebir /admin ile başlamadığı için izin veriyor:
$ curl -s --path-as-is -i http://127.0.0.1:8188//admin/flag | head -3
HTTP/1.1 400 Bad Request
Content-Length: 50
Absolute URI not allowed if server is not a proxy.Proxy geçirdi, ama backend reddetti. Cheroot açısından //admin ile başlayan bir istek satırı, mutlak URI'nin yetki (authority) biçimine benziyor ve yönlendirmeden önce isteği tümden geri çeviriyor. Çıkmaz sokak — ama faydalı bir çıkmaz: bize yolun anlamı konusunda son sözün backend'de olduğunu, yalnızca proxy'nin "admin değil", backend'in ise "/admin/flag" diye okuyacağı bir gösterime ihtiyacımız olduğunu söylüyor.
4. Yüzde kodlaması uyuşmazlığı
a harfinin kodu %61. Proxy ham, yani hâlâ kodlanmış baytları karşılaştırıyor:
$ curl -s --path-as-is -i http://127.0.0.1:8188/%61dmin/flag
HTTP/1.1 200 OK
Content-Type: application/json
Server: Cheroot/11.1.2
{"flag":"BITSCTF{fake_flag_for_local_testing}"}İşte istismarın tamamı bu.
- Proxy
/%61dmin/flagyolunu görüyor. Küçük harfe çevrilince de/%61dmin/flagkalıyor,/adminile başlamıyor, dolayısıyla kontrol ona izin veriyor. - Proxy isteği
/%61dmin/flagbiçiminde olduğu gibi yukarıya iletiyor. - Backend, rotalamadan önce yolu yüzde çözüyor:
%61harfiaoluyor, sonuç/admin/flagve bu da Flask rotasıyla eşleşip bayrağı JSON olarak döndürüyor.
$ printf 'GET //admin/flag HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n' | nc -w3 127.0.0.1 8188 | head -c 200
HTTP/1.1 400 Bad Request
Absolute URI not allowed if server is not a proxy.Karşılaştırma için nokta parçası hileleri burada işe yaramıyor — Cheroot /./ ve /../ parçalarını dosya sistemi gibi sadeleştirmiyor, bu yüzden /foo/../admin/flag düz bir 404 döndürüyor. İstismar genel bir yol karışıklığı değil, özellikle kod çözme uyuşmazlığı.
5. Neden çalışıyor
Proxy politikayı ham istek hedefi üzerinde uyguluyor; backend ise rotalamayı çözülmüş istek hedefi üzerinde yapıyor. İki katman, iki farklı kanonik biçim, tek bir erişim kontrolü. Yüzde kodlaması, metinlerin ayrışmasına yetiyor.
Bu, çok daha geniş bir ailenin ters proxy versiyonu — yol normalizasyonu, Unicode aşırı uzun kodlamalar, harf katlama, sondaki noktalar, %2e ile . farkı — hepsi "güvenlik bileşeni ile koruduğu şey, aynı girdiyi farklı okuyor" temasının varyasyonları.
6. Bayrak
BITSCTF{fake_flag_for_local_testing}Bayrak backend'in FLAG ortam değişkeninden okunuyor; yukarıdaki değer, çalıştırmayı yeniden üretmek için kullanılan yerel test değeri. Gerçek bayrak, yarışmanın bu değişkene koyduğu değerdir.
7. Öğrenilen
- Yetkilendirmeden önce normalize et. Yolu kod çöz, nokta parçalarını çözümle, harf katlamasını uygula — sonra da erişim kontrolünü, ileteceğin tam bayt dizisi üzerinde çalıştır.
- Kararı veren katman ile rotalayan katmanın aynı olduğundan emin ol. Bir proxy yolu kırpıyor ya da yeniden yazıyorsa, kontrolü nihai biçim üzerinde yeniden çalıştırmalı.
- Bir ön ek kontrolü yayına çıktığında, onu kodlamalarla dene:
%61,%2f,%2e, karışık harf, sondaki boşluk, çift eğik çizgi. Fark neredeyse her zaman oradadır. - Son söz backend'de. Bir atlatma denemesi proxy yerine backend tarafından reddedilirse, bu başarısız bir deneme değil; hangi ayrıştırıcıyı şaşırtman gerektiğine dair bir ipucudur.
Ne düşünüyorsun?
Tepki bırakarak geri bildirim ver