İçeriğe geç
Tüm blog yazıları

Kavram ve yöntem · Blog

Related-Domain Saldırıları: Tarayıcıya Devredilen Güvenlik Ön Kabullerinin Çöküşü

Tarayıcıların origin ve site sınırları, kurbanın kendi alan adı ailesinin içinden gelen bir saldırganı durdurmamaktadır. Bu yazı, tek bir subdomain'in kontrolünden itibaren zincirlenen cookie, CSP, CORS ve framing tekniklerini, gerçek bir tarayıcıda doğrulanmış bir proof of concept ile anlatmaktadır.

Bu yazı, tamamen yetkilendirilmiş kırmızı takım (red team) çalışmaları ve kendi laboratuvar ortamımızdaki araştırmalar çerçevesinde hazırlanmıştır.

Bu yazıda anlatılan içeriğin beslendiği Github Repomuza buradan ulaşabilirsiniz: https://github.com/redinpulse/RinP-SubChain

Giriş

Modern tarayıcılar origin’ler arasına kalın bir çizgi çekmektedir. Same-Origin Policy, Site Isolation, HttpOnly, SameSite ve CSP; her katman bir sitenin verisini bir başkasından uzak tutmak için vardır.

Red team çalışmalarımızda ise bu çizgilerin hiçbirinin işe yaramadığı bir konuma düzenli olarak ulaşıyoruz. Nedeni basittir: kurum, saldırgana ailenin içinde bir yer vermektedir. Tek bir alt alan adının (subdomain) kontrolü bunun için yeterli olmaktadır.

Bu yazıda o konumdan itibaren kullandığımız zincirleme tekniklerini, origin çizgisinde durup kalan tarayıcı korumalarını ve sahada gerçekten tutan korumaları anlatıyoruz. Sonunda da kağıt üzerinde kalmayan, gerçek bir tarayıcıda uçtan uca doğrulanmış bir proof of concept sunuyoruz.

Related-domain saldırgan kimdir?

Bahsettiğimiz konumun literatürde bir adı bulunmaktadır: related-domain attacker. Squarcina ve arkadaşları bu konumu USENIX Security 2021’de yayımladıkları “Can I Take Your Subdomain?” çalışmasıyla resmileştirmiştir: kurbanın subdomain’lerinden herhangi birinin kardeşini (sibling) kontrol eden saldırgan.

Sektör bu konuma genellikle bir subdomain takeover ile ulaşmaktadır; ancak giriş yolları dangling CNAME kayıtlarından çok daha geniştir:

  • Hâlâ canlı olan alias’ların arkasındaki süresi dolmuş alan adları ve sahipliği yeniden doğrulamayan, kullanımdan kalkmış üçüncü taraf servisler (Shopify, Tumblr, GitHub Pages sınıfı).
  • Bir DNS kaydının hâlâ işaret ettiği, serbest bırakılmış bulut IP adresleri.
  • Bağlanan her cihaza FQDN dağıtan kurumsal ve roaming ağlar.
  • Apex’i Public Suffix List’te bulunmayan hosting ve dynamic-DNS sağlayıcıları. Bu durumda her müşteri sitesi diğerinin sibling’i hâline gelmektedir.

Bir operatör için asıl kavrayış şudur: blast radius’u ölçmek için gerçek bir takeover’a ihtiyacınız yoktur. Bir angajmandan önce sorulacak doğru soru bir hipotezdir: bu kurumun tek bir subdomain’ini kontrol etseydik, parent domain boyunca ne zincirlenirdi? Yanıtı dakikalar almakta ve yalnızca kurbanın kendi sibling’lerinin ne beyan ettiğini okumayı gerektirmektedir.

Tarayıcı korumaları bu konumda neden durur?

Çünkü tarayıcı korumalarının neredeyse tamamı ya origin ya da site kapsamındadır; bu konumdaki saldırgan ise her iki sınırın da içindedir.

Same-Origin Policy (SOP) origin’leri yalıtmaktadır. Saldırganın subdomain’i farklı bir origin’dir, yani SOP tam da tasarlandığı gibi çalışmaktadır; burada bir SOP bypass’ı bulunmamaktadır. Ancak hangi çerezlerin (cookie) bir istekle birlikte gideceğine karar veren mekanizma SOP değildir. Bu karar origin bazında verilmez; registrable domain (eTLD+1) bazında verilmektedir.

SameSite de kurtarmamaktadır. SameSite=Strict ve SameSite=Lax cross-site istekleri kısıtlar; oysa kurbanın her subdomain’i aynı site’a aittir. app.victim.com üzerinde set edilmiş bir Strict session cookie, evil.victim.com’a rahatça eşlik etmektedir; çünkü tarayıcının gözünde hiçbir site sınırı aşılmamıştır. Tek incelik schemeful same-site’tır: onu uygulayan tarayıcılarda http üzerindeki kontrollü bir subdomain, https sibling’lere göre cross-site sayılmaktadır.

Site Isolation da benzer biçimde site’ları ayırmaktadır. Tek bir site içindeki cross-origin saldırılar açıkça onun threat model’inde yer almaz; saldırgan ile hedef aynı renderer process’ini paylaşabilmektedir.

Peki ne tutar? Host-only cookie’ler, yani Domain attribute’ü taşımayan cookie’ler kendi host’unu asla terk etmemektedir. __Host- prefix’i ise Domain scoping’i tamamen yasaklamaktadır. Bu ikisi ile, Secure işaretli bir cookie’nin alınabilmesi için kontrol edilen host’ta TLS gerekliliği, bu konumdaki gerçek savunmalardır. Deneyimimize göre bu iki savunma sahada en az rastladığımız kontrollerdir; yani çoğu uygulama bu konuma hazırlıksız yakalanmaktadır.

Bu bulgu en çok kurum içi ortamlarda geçerlidir

Bu teknik, nadir bir subdomain takeover gerektirdiği için niş sanılmaktadır; pratikte tersi geçerlidir. Bulgunun en yüksek risk taşıdığı, bu yüzden önce kontrol edilmesi gereken zeminlerden biri kurum içi (internal) ortamlardır. Nedenleri şunlardır:

  • FQDN otomatik dağıtımı. Kurumsal DNS (AD entegre dynamic DNS, DHCP kaydı) ağa katılan her cihaza kurumsal zone altında bir hostname vermektedir (makine.corp.example). Ağdaki bir cihazı ele geçiren, ağa katılabilen veya bir isim kaydedebilen saldırgan, dışarıda bir subdomain takeover’a gerek kalmadan internal web uygulamalarının sibling’i hâline gelmektedir. related-domain konumu internal tarafta çoğu zaman hazır gelmektedir.
  • Wildcard TLS ve wildcard domain. Internal PKI sık sık tek bir *.corp.example wildcard sertifikayı onlarca servis için kullanmaktadır. Domain wildcard ise ve web uygulaması bir FQDN üzerinde barınıyorsa, kontrol edilen internal host bir sibling hostname için geçerli TLS sunabilmektedir. Secure işaretli cookie’leri almak ve geçerli bir https origin olmak için gereken tam olarak budur. Bu üçlü (wildcard, FQDN ve hostname) ciddi bir risk kapısı oluşturmaktadır.
  • Ağ zaten güvenli varsayımıyla zayıf web hijyeni. Internal uygulamalar sıklıkla domain-wide cookie kullanmaktadır (SSO tüm servislerde çalışsın diye Domain=.corp.example), __Host- veya host-only cookie kullanmamaktadır, gevşek ya da hiç CSP taşımamaktadır, lax CORS uygulamakta ve frame-ancestors koymamaktadır. Zincirin ihtiyaç duyduğu tüm ön koşullar internal tarafta daha yaygındır; çünkü ortam güvenlik duvarı arkasında kabul edilmektedir.
  • SSO ve OAuth yoğunlaşması. Internal kimlik çoğu zaman domain geneli tektir. Domain-wide session cookie’ye ya da internal bir OAuth callback’ine yapılan tek bir cookie toss, aynı anda birçok internal uygulamaya pivot sağlayabilmektedir. Path precedence ile IdP callback’ini hedefleyen dar kapsamlı bir toss yüksek değerlidir.
  • Düz güven ve geniş blast radius. Internal tarafta her şey aynı registrable domain’i ve domain-wide cookie’leri paylaştığından, saldırgan tarafından kaydedilmiş veya ele geçirilmiş tek bir hostname tüm internal uygulama filosu boyunca zincirlenebilmektedir.

Dürüst kayıt: bu, sıfır çaba anlamına gelmemektedir. Yine de ağda bir cihaz veya DNS kaydı konumu gerekmektedir; split-horizon DNS ya da tier başına ayrı zone kullanan ortamlarda sibling ilişkisi azalmaktadır; zero-trust internal mimariler (uygulama başına kimlik doğrulama, mTLS, host-only cookie) etkiyi kırmaktadır. Bu değişkenlik tam da “önce kontrol edilmeli” dememizin nedenidir: internal tarafta bu bulgunun geçerliliği ortamdan ortama uçtan uca değişmektedir, dolayısıyla varsayılmaz, ölçülür.

Zincirin yapı taşları

Cookie confidentiality

Domain=.victim.com ile set edilen her cookie, saldırganınki dahil her subdomain’e gönderilmektedir. HttpOnly bunu korumamaktadır: HttpOnly yalnızca JavaScript’in cookie’yi okumasını engeller; tarayıcı cookie’yi isteklere yine de eklemektedir. Saldırganın document.cookie’ye ihtiyacı yoktur. Kurbanın kendi tarayıcısı session’ı saldırganın sunucusuna teslim etmekte ve cookie orada hattan yakalanmaktadır.

Cookie tossing ve shadowing

Aynı scoping ters yönde de işlemektedir. Kontrol edilen subdomain, Domain=.victim.com ile cookie set ederek uygulamanın kendi değerlerini üzerine yazabilmekte (session fixation) veya gölgeleyebilmektedir (shadowing). Bir host-only cookie bile shadow’lanabilir: aynı adı taşıyan domain-wide bir cookie onun yanı sıra teslim edilmekte ve sunucu tarafındaki parser bir değeri seçmektedir.

OAuth ağırlıklı hedeflerde kullandığımız incelik path precedence’tır: daha derin Path’e sahip cookie’ler önce gönderilmektedir, dolayısıyla dar kapsamlı bir toss, başka hiçbir şeye dokunmadan tam olarak tek bir endpoint’i, örneğin bir OAuth callback’ini zehirleyebilmektedir. Zoom’da ve çeşitli identity provider’larda yayımlanmış account takeover’ların arkasındaki teknik budur.

CSP güven yükseltmesi

Bir sibling’in Content-Security-Policy’si script-src *.victim.com gibi bir wildcard içerdiğinde, kontrol edilen subdomain o uygulama için onaylı bir script host’u hâline gelmektedir. Bu, klasik anlamda bir CSP bypass’ı sayılmaz; policy tam da söylediği şeyi yapmaktadır ve söylediği şey, saldırganın host’una güvenildiğidir. Enjekte edilen script, uygulamanın kendi origin’i içinde; onun cookie’leri, storage’ı ve same-origin API’leriyle çalışmaktadır.

Credentials ile CORS

Bir sibling keyfi origin’leri reflect edip Access-Control-Allow-Credentials: true set ediyorsa, saldırganın JavaScript’i authenticated yanıtları cross-origin okuyabilmektedir. Otomatik taradığımız ilişkili bir bulgu sınıfı daha bulunmaktadır: Origin header’ını substring veya suffix kontrolüyle doğrulayan sunucular. endsWith("victim.com") biçiminde yazılmış bir kontrol, subdomain’e bile gerek kalmadan, tamamen farklı bir registrable domain olan evilvictim.com’u da kabul etmektedir.

Framing ve postMessage

Eksik X-Frame-Options veya frame-ancestors, kontrol edilen origin’in sibling’i frame’lemesine izin vermektedir. Same-site göndericilere güvenen bir postMessage listener’ı da sayfanın yanıtlamaya razı olduğu her şeyi teslim etmektedir. Legacy hedeflerde ayrıca, sibling’i tam same-origin erişimine çeviren document.domain relaxation’ını da kontrol ediyoruz.

Proof of concept: gerçek tarayıcıda doğrulanmış

Cookie jar hakkındaki iddialar tarayıcı düzeyinde kanıtı hak etmektedir; bu yüzden gerçekçi bir hedefi birebir yansıtan bir lab kurduk. Lab’da HttpOnly domain-wide bir session’a, host-only bir CSRF cookie’sine, localStorage’da bir JWT’ye, DOM’da kişisel veriye ve authenticated bir same-origin profile API’sine sahip bir uygulama dashboard’u bulunmaktadır. Buna ek olarak, naif bir postMessage listener’ı taşıyan ikinci bir sibling yer almaktadır. Aşağıdaki her teknik, header’lardan çıkarımla elde edilmemiş; Chromium’da fiilen çalıştırılıp gözlemlenmiştir.

Tartışmayı bitiren yakalama, CSP wildcard’ı üzerinden gerçekleşmektedir. Kontrol edilen subdomain’den serve edilen ve victim policy’sinin izin verdiği bir script, application origin’i içinde çalışmakta ve uygulamanın kendi verisini saldırganın sunucusuna beacon’lamaktadır:

{
  "cookie": "strictck=1; csrftoken=csrf-9f3a1",
  "localStorage_auth_token": "eyJhbGci.ATTACKER-SHOULD-NOT-SEE.b64",
  "dom_secret": "PII: Gedik Testuser, [email protected], TR",
  "profile_api": {"user": "Gedik Testuser", "mail": "[email protected]",
                  "role": "admin", "api_key": "sk-live-7742"}
}

O script’in göremediği HttpOnly session cookie’si, kontrol edilen subdomain’e giden istekle yine de gelmiş ve sunucu tarafında yakalanmıştır. SameSite=Strict cookie’si de onunla birlikte gelmiştir. Kontrol edilen subdomain’den tek bir toss’un ardından uygulamanın kendisi şunu raporlamıştır:

strictck=1; csrftoken=csrf-9f3a1; session=FIXATED; csrftoken=EVILSHADOW

Tek bir istek, session değerini değiştirmiş ve host-only orijinalin yanına bir shadow cookie yerleştirmiştir. Tarayıcının tam da spec’te belirtildiği gibi reddettiği tek şey, Domain attribute’üyle teslim edilen __Host- prefix’li bir cookie olmuştur: geldiği anda reddedilmiştir. O ret, her savunmacının hedeflemesi gereken modeldir.

CSP host-source’ta port kuralı

Kampanya ayrıca, hiçbir yerde temizce belgelenmiş görmediğimiz ampirik bir kural üretmiştir: Chromium’da, port kısmı taşımayan bir CSP host-source yalnızca şemanın default port’larıyla eşleşmektedir. Dolayısıyla *.victim.com, subdomain’inize 443’te güvenmekte ama 8443’te reddetmektedir; meğer ki policy o portu ya da :* ifadesini listelesin. Beş policy varyantı, tek tarayıcı, bir öğleden sonra:

Policy source ifadesi Script’i :8901’de yükler mi
*.victim.com blocked
http://*.victim.com blocked
*.victim.com:8901 allowed
http://evil.victim.com:8901 allowed
http://*.victim.com:* allowed

Denetçiler için bu sonuç her iki yönde de önemlidir: portsuz bir wildcard, standart port’larda hâlâ canlı bir zincirdir; portlu bir source ise kontrol ettiğiniz host’un hangi portta serve etmesi gerektiğini tam olarak söylemektedir.

Araç: RinP SubChain

Metodolojiyi tek dosyalık, bağımlılıksız bir denetim aracı olarak paketledik. Araca kurbanın registrable domain’ini, bir sibling subdomain listesini ve kontrolünüzde olduğunu varsaydığınız subdomain’i verirsiniz; araç her sibling’i denetlemekte ve zincir sınıflarını, bağlı oldukları koşullarla birlikte raporlamaktadır: RECEIVABLE, TOSSABLE, SHADOW, PROTECTED, SCOPED-DEEPER, TRUSTED-CSP direktifleri, suffix-validation bulgusu dahil CORS zincirleri ve framable host’lar. Bir serve modu, kontrol ettiğiniz subdomain üzerine bir capture endpoint’i olarak deploy olmakta; fixation testleri için de bir toss yardımcısı gelmektedir.

İki tasarım kararı aracı dürüst tutmaktadır. Birincisi, discovery yoktur: kapsamı siz verirsiniz, araç kendi başına subdomain keşfetmez. İkincisi, classifier semantiği gerçek bir tarayıcıya göre kalibre edilmiştir ve bu karar hemen işe yaramıştır.

Büyük property’lere karşı yaptığımız canlı internet koşuları ince bir vaka göstermiştir: Cloudflare’in kendi __cf_bm cookie’si Domain=www.cloudflare.com ile set edilmektedir; yalnızca o host’un subdomain’lerine ulaşan, deep-scoped bir cookie. Cookie domain’ini registrable root’a karşı denetleyen bir araç bunu RECEIVABLE işaretlerdi; oysa gerçekten kontrol ettiğiniz host’a karşı denetlemek onu doğru biçimde SCOPED-DEEPER olarak sınıflamaktadır: hâlâ shadow’lanabilir, ama teslim edilmez. Buna karşılık GitHub’ın logged_in cookie’si Domain=.github.com ve HttpOnly’dir; varsayımsal bir kontrollü subdomain konumundan sunucu tarafında receivable ve tossable’dır. Aracın var olma amacı tam olarak bu bulgu sınıfını sıralamaktır.

Madde okumak yerine zinciri doğrudan görmek isterseniz, depo kanıtladığımız lab’ın kendisini de içermektedir. Tek komut, python3 lab/server.py, kendi tarayıcınızda kurban dashboard’unu, naif sibling’i ve bir saldırgan konsolunu başlatmaktadır: kontrol edilen subdomain’e tarayıcınızın teslim ettiği her şeyin canlı capture log’u, toss, shadow ve __Host- denemeleri için düğmeler, port kuralını canlı gösteren bir CSP varyant anahtarlayıcısı ve bir postMessage oyun alanı. lab/GUIDE.md içindeki walkthrough, bu yazıdaki her iddiayı kendi makinenizde izlediğiniz bir şeye dönüştürmektedir.

Bu, kurumlar için ne anlama geliyor?

Savunma listesi kısa ve tavizsizdir:

  • Hassas cookie’ler: bir session veya CSRF kararının verildiği her yerde host-only (Domain attribute’ü olmadan) ve __Host- prefix’li olmalıdır.
  • CSP: tam olarak kontrol etmediğiniz subdomain’leri kapsayan wildcard’lar bulunmamalıdır. strict-dynamic’in kuralları değiştirdiği ve port’ların da güvenin bir parçası olduğu unutulmamalıdır.
  • CORS: birebir origin allowlist’leri kullanılmalı, suffix veya substring kontrolü yapılmamalıdır; her iki origin de sizin olmadıkça credentials kapalı tutulmalıdır.
  • Framing: her sibling’de frame-ancestors bulunmalı, postMessage listener’ları origin kontrolü için denetlenmeli ve document.domain emekliye ayrılmalıdır.
  • DNS hijyeni: dangling kayıtlar temizlenmeli ve bir subdomain’e binding yapan her üçüncü taraf servis, gerçekte olduğu şey olarak, bir güven devri (trust delegation) olarak ele alınmalıdır.
  • Internal’ı dış yüzeyden daha sıkı denetleyin: wildcard sertifika, domain-wide cookie ve dynamic DNS üçlüsü internal tarafta normdur; bu yüzden related-domain zinciri çoğu zaman en yüksek gerçek etkiyi burada üretmektedir.

Subdomain’lerinizden yalnızca biri bir dış aktör tarafından ele geçirilebiliyorsa, sorulacak soru onun kullanıcılarınıza phishing yapıp yapamayacağı değildir. Asıl soru şudur: diğer subdomain’leriniz ona ne teslim edecektir?

Kendi blast radius’unuzu ölçün

Aynı denetim kendi alan adlarınıza karşı da çalışmaktadır: RinP SubChain’e registrable domain’inizi, subdomain listenizi ve varsayımsal bir kontrollü host’u verin. Zincir raporu, bir dış aktör bu soruyu sizin yerinize yanıtlamadan önce, aile boyunca hangi cookie’lerin, hangi CSP direktiflerinin ve hangi CORS endpoint’lerinin sızacağını tam olarak söylemektedir.

Bu denetimi özellikle internal alan adlarınız için de çalıştırın. Wildcard sertifika, domain-wide cookie ve dynamic DNS bir arada olduğunda en büyük sürprizler oradan çıkmaktadır.

Yazıda Geçen Kavramlar ve Kısaltmalar

Related-domain attacker

Kurbanın alan adı ailesinden bir subdomain'i kontrol eden saldırgan konumudur. Hedefle aynı registrable domain'i paylaştığı için cookie ve site kapsamlı kontrollerin içinde yer almaktadır.

Registrable domain (eTLD+1)

Public Suffix List'teki bir sonektin bir üstündeki kaydedilebilir alan adıdır. SameSite ve Site Isolation bu birim üzerinden çalışmakta, cookie kapsamı da bu birimden türemektedir.

Host-only cookie ve __Host- prefix'i

Domain attribute'ü taşımayan bir cookie yalnızca kendi host'una gitmektedir. __Host- prefix'i ise Domain kullanımını tümüyle yasaklamakta ve Secure ile kök Path zorunlu kılmaktadır.

Cookie tossing ve shadowing

Kontrol edilen bir subdomain'den domain-wide cookie set ederek uygulamanın değerini üzerine yazma (fixation) veya aynı adla ikinci bir cookie teslim ettirme (shadowing) tekniğidir.

Blast radius

Bir başlangıç konumundan ulaşılabilen toplam etki alanıdır. Bu yazıda tek bir subdomain kontrolünün parent domain boyunca ne kadar ilerlediğini ölçmek için kullanılmaktadır.

// BLOG

Alan adı ailenizin blast radius'unu ölçtürün

Subdomain envanterinizi ve varsayımsal bir kontrollü host'u birlikte netleştirerek, aile boyunca hangi cookie ve direktiflerin sızacağını kontrollü bir test çalışmasıyla doğrulayabiliriz.

Tarayıcı Yanılgısı: Related-Domain Saldırıları | RinP · Offensive Security