İçeriğe atla
0 XP 0
15 dkderinleşmeyle 16 dk55 XP8 etkinlik

XSS: tarayıcıda çalışan enjeksiyon

Bu derste
  • Siteler arası betik çalıştırmayı (XSS) geliştirici gözüyle çözersin: yükün nerede durduğunu, hangi karakterin bağlamı kırdığını ve hangi çağrının korumayı kapattığını görürsün.
  • Altay Kargo'nun takip portalında üç türü ayırır, çıktıyı bağlama göre kodlar, katı CSP ve Trusted Types ile ikinci katmanı kurarsın.

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.

Önceki derslerde yorumlayıcı veritabanı, kabuk ya da şablon motoruydu. Bu derste yorumlayıcı tarayıcıdır. Kullanıcı girdisi sayfaya kodlanmadan basılırsa tarayıcı onu HTML ya da betik olarak okur. Siteler arası betik çalıştırma, kısaca XSS, bu noktada doğar.

Sayfaya sızan betik, sitenin kendi kodu gibi çalışır. Kurbanın oturumuyla istek atar, ekrandaki teslimat adresini okur, formları onun adına gönderir. Bu derste Altay Kargo'nun takip portalına geliştirici gözüyle bakarsın. Açığı satırında bulur, düzeltmeyi kodda yazar, ikinci katmanı yanıt başlığında kurarsın.

Yük nereden gelir, sayfaya kim yazar

XSS türlerini iki soru ayırır: yük nereden gelir ve sayfaya kim yazar. Yansıyan türde yük istekle gelir, sunucu onu aynı yanıtta geri basar. Takip portalının arama sayfası, aranan terimi hem başlıkta hem arama kutusunda tekrar gösterir. Saldırgan yükü bir bağlantıya gömer, kurban bağlantıyı açınca yük çalışır.

Depolanan türde yük önce kaydedilir, sonra başka bir sayfada gösterilir. Alıcının yazdığı teslimat notu veritabanına girer ve şube panelinde açılır. Not kodlanmadan basılırsa yük, paneli açan her çalışanın oturumunda çalışır. Tek bir kayıt, yetkisi yüksek pek çok kullanıcıya ulaşır.

DOM tabanlı türde sunucu yükü yanıta basmaz, yanıt temiz gelir. Açık, tarayıcıda çalışan sayfa betiğindedir. Betik adres çubuğundan bir değer okur ve onu HTML olarak sayfaya yazar. Değerin okunduğu yere kaynak (source), yazıldığı yere hedef (sink) denir.

Kaynak yalnız adres çubuğu değildir. document.referrer ile gelen önceki sayfa adresi, başka bir pencereden postMessage ile gelen mesaj ve tarayıcı deposundaki değerler de kaynak olabilir. Mesaj dinleyen bir betik, gönderenin kökenini denetlemeden mesajı sayfaya yazarsa herhangi bir site o sayfaya yük yollayabilir. Her kaynağı, kullanıcının yazdığı metin kadar güvenilmez sayarsın.

Kaynağın yeri, yükün nerede iz bıraktığını belirler. Değer bağlantı parçasındaysa, yani # işaretinden sonra geliyorsa, tarayıcı onu sunucuya hiç göndermez. Yük ne sunucu loglarında ne de güvenlik duvarında görünür. Değer sorgu dizesindeyse istekle sunucuya gider ve loglara düşer, ama yanıt yine temiz kalır; sayfaya yazan sunucu değil betiktir.

Etkinlik 1 / 8

Doğru mu, yanlış mı?

Takip sayfasının betiği değeri # işaretinden sonraki parçadan okuyorsa, yük sunucunun erişim loglarında görünmez.

Bu ayrım tespit yöntemini de değiştirir. Yansıyan ve depolanan türde yük yanıtın içindedir; yanıtı tarayan bir test onu yakalar. DOM tabanlı türde yanıtı taramak yetmez. Sayfa betiğini okur ya da sayfayı tarayıcıda çalıştırıp değerin nereye yazıldığını izlersin.

Yansıyan ve depolanan türde denemeler loglarda da iz bırakır. Tipik desen, tek bir kaynaktan aynı parametreye art arda gelen ve her seferinde biraz değişen isteklerdir: önce bir etiket, sonra bir tırnak, en sonda bir olay işleyici denenir. Sıradan bir kullanıcı arama kutusuna olay işleyici yazmaz. Bu desende güvenlik duvarının geçirdiği istekler, engellediklerinden daha önemlidir.

Etkinlik 2 / 8

Takip portalındaki her bulguyu XSS türüne göre ayır.

Önce öğeyi seç, sonra kutusunu tıkla.

Bağlamı kıran karakter

Kodlama tek bir işlem değildir, çıktının düştüğü bağlama göre değişir. Tarayıcı aynı sayfayı bölge bölge farklı kurallarla okur. Etiketler arasındaki metin, bir özniteliğin değeri, bir betik bloğu ve bir adres alanı ayrı bağlamlardır. Her bağlamdan çıkmak için başka bir karakter gerekir.

Etiketler arasındaki metinde yeni etiket açmak için açılı ayraç gerekir. Çift tırnakla sarılmış bir öznitelik değerinde açılı ayraca hiç gerek yoktur. Çift tırnak değeri kapatır, ardından yazılan her şey etiketin yeni bir özniteliği olur. Tırnaksız bir öznitelikte tek bir boşluk aynı işi görür.

Takip portalının teslimat düzenleme formu, alıcı adını kutuya önceden doldurur. Geliştirici bir kaçış yardımcısı yazmış, ama yardımcı yalnız &, < ve > karakterlerini çeviriyor. Metin bağlamında bu yeterli görünür. Öznitelik bağlamında ise kapı açık kalır.

Etkinlik 3 / 8

Alıcı adı alanına aşağıdaki değer yazılmış. Teslimat düzenleme formu açılınca ne olur?

Önce kodu oku, sonra çıktıyı seç.

function kacir(s) {
  return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
html += '<input name="alici" value="' + kacir(alici) + '">';

// alici: " autofocus onfocus=alert(1) x="

Önüne konan bir güvenlik duvarı da bu açığı kapatmaz. Kural açılı ayracı engelliyorsa öznitelik bağlamındaki yük ona hiç takılmaz, çünkü açılı ayraç kullanmaz. Kural yalnız bildiği kalıpları tutar. Bağlamın sınırını çizen karakter listede yoksa saldırgan kuralın yanından geçer.

Düzeltmenin iki parçası var. Öznitelik değerini her zaman tırnakla sararsın ve kodlayıcıya çift ve tek tırnağı da eklersin. OWASP'ın HTML bağlamı için önerdiği kodlama & < > " ' karakterlerinin beşini de HTML varlığına çevirir. Daha sağlam yol, bu işi elle değil, bağlamı tanıyan bir şablon motoruna yaptırmaktır.

Betik bağlamı başka kurallarla işler. Değeri bir <script> bloğundaki dizgeye gömersen tarayıcı </script> dizisini görür görmez bloğu kapatır, dizgenin ortasında olsa bile. Olay işleyici özniteliğinde HTML kodlaması da korumaz; tarayıcı varlıkları betik çalışmadan önce çözer ve tırnak geri gelir. Güvenli yol, değeri betik koduna hiç gömmemektir.

Değeri kodlanmış bir data özniteliğine yazar, betikte dataset üzerinden metin olarak okursun. Adres bağlamında sorun karakter değil şemadır. Bir bağlantının href değerine kullanıcının verdiği adresi yazarsan javascript: ile başlayan değer tıklanınca betik çalıştırır. Adresi ayrıştırıp şemayı http ya da https ile sınırlayan bir denetim bu kapıyı kapatır.

// Öznitelik bağlamı: beş karakter kodlanır, değer tırnak içinde durur
const ozn = (s) => String(s)
  .replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
  .replace(/"/g, "&quot;").replace(/'/g, "&#x27;");
html += '<input name="alici" value="' + ozn(alici) + '">';

// Betik bağlamı: değer koda gömülmez, data özniteliğinden okunur
html += '<div id="form" data-alici="' + ozn(alici) + '"></div>';
// istemcide: const alici = document.getElementById("form").dataset.alici;

// Adres bağlamı: şema denetlenir
const u = new URL(adres, "https://takip.altaykargo.example");
const href = ["http:", "https:"].includes(u.protocol) ? u.href : "#";
Aynı değer üç bağlamda üç ayrı önlemle korunur. Elle yazılan kodlayıcı yerine şablon motorunun bağlama duyarlı kodlaması daha az hata bırakır.

Etkinlik 4 / 8

Her çıktı bağlamını, o bağlamdan çıkmaya yeten karakterle eşle.

Soldan bir tane seç, sağdan eşini seç. Kullanılmış bir seçeneği yeniden seçersen yeni öğeye taşınır.

Kod incelemesi: korumayı kapatan çağrılar

Güncel şablon motorlarının çoğu çıktıyı varsayılan olarak kodlar. EJS'te <%= etiketi değeri HTML kodlamasından geçirir, <%- etiketi ise olduğu gibi basar. Handlebars'ta üçlü süslü ayraç, Jinja ve Django'da safe süzgeci kodlamayı kapatır. React'te dangerouslySetInnerHTML, Vue'da v-html aynı kapıyı açar.

Kod incelemesinde önce bu çağrıları ararsın, çünkü her biri korumanın bilerek kapatıldığı yerdir. Her bulguda tek bir soru sorarsın: bu değer kullanıcıdan gelebilir mi? Gelebiliyorsa ham çıktıyı kaldırırsın. Zengin metin gerçekten gerekiyorsa değeri izin listesiyle çalışan güvenilir bir temizleyiciden, örneğin DOMPurify'dan geçirirsin.

Kodlamanın yeri de önemlidir. Girdiyi kaydederken kodlarsan değer, hangi bağlamda gösterileceğini henüz bilmez. Aynı teslimat notu hem şube panelinde hem bir JSON yanıtında hem de bir kısa mesajda kullanılır; erken kodlama birinde bozuk metin, öbüründe açık bırakır. Doğru yer çıktı anıdır: veri ham saklanır, her gösterimde o bağlamın kodlaması uygulanır.

Kod incelemesini otomatik testle tamamlarsın. Her çıktı noktasına aynı deneme dizgesini verirsin, örneğin & < > " ' karakterlerinin hepsini taşıyan bir değer. Test, yanıtta bu karakterlerin bağlama uygun biçimde kodlandığını denetler. Yeni bir alan eklendiğinde test de genişler, böylece sonradan açılan bir delik sessizce yayına çıkmaz.

Etkinlik 5 / 8

Teslimat notunu gösteren şablonlardan hangileri notu kodlamadan, HTML olarak basar?

Birden fazla doğru var.

DOM tabanlı türde aranacak çağrılar istemci kodundadır. innerHTML, outerHTML, insertAdjacentHTML ve document.write değeri HTML olarak ayrıştırır. eval, Function yapıcısı ve setTimeout'a verilen dizge ise değeri doğrudan betik olarak çalıştırır. textContent ve innerText değeri yalnız metin olarak yazar.

Kaynaklocation.hashSayfa betiğideğeri okurHedefinnerHTMLHTML ayrıştırıcıetiketi kurarOlay işleyicibetik çalışırSunucu yanıtı temizdir; değeri sayfaya tarayıcıdaki betik yazar.
Değer kaynaktan okunur, sayfa betiği onu bir hedefe yazar. Hedef HTML ayrıştırıyorsa etiket kurulur ve içindeki olay işleyici çalışır.

Portal, müşteriye giden mesajdaki bağlantıya takip numarasını #no= parçasıyla ekliyor. Sayfa betiği bu numarayı okuyup başlığa yazıyor. Saldırgan aynı biçimde bir bağlantı hazırlar ve numaranın yerine yük koyar. Aşağıdaki kodda kaynağı tehlikeli hedefe bağlayan satırı bul.

Etkinlik 6 / 8

DOM tabanlı XSS'i doğuran satırı bul.

Zafiyetli satıra tıkla.

İkinci katman: katı CSP, Trusted Types ve HttpOnly

Kodlama kök nedeni kapatır, ama büyük bir kod tabanında bir satır er geç gözden kaçar. İçerik güvenlik politikası (CSP) o satır için ikinci bir engel kurar. Sunucu yanıt başlığında tarayıcıya hangi betiğin çalışabileceğini bildirir. Politikanın tanımadığı betik sayfaya girse bile çalışmaz.

Eski yaklaşım izinli alan adlarını sıralamaktı. Bu liste kolay delinir: izinli bir alanda eski bir kütüphane ya da geri çağrı adını dışarıdan alan bir uç varsa, saldırgan betiğini oradan yükler. Güncel öneri nonce tabanlı katı politikadır. Sunucu her yanıtta tahmin edilemez yeni bir değer üretir ve yalnız bu değeri taşıyan betik etiketleri çalışır.

Content-Security-Policy: script-src 'nonce-4kP9xQ2mZ7aV1bN8' 'strict-dynamic'; object-src 'none'; base-uri 'none'

<script nonce="4kP9xQ2mZ7aV1bN8" src="/js/takip.js"></script>   <!-- çalışır -->
<input name="alici" value="" autofocus onfocus="alert(1)">       <!-- onfocus engellenir -->
<a href="javascript:alert(1)">Takip et</a>                      <!-- engellenir -->
Nonce her yanıtta yeniden üretilir. Olay işleyici özniteliği ve javascript: adresi nonce taşıyamaz; 'strict-dynamic' ise güvenilen betiğin yüklediği betiklere de güveni taşır.

Katı politika XSS'in en sık yollarını birden kapatır. Olay işleyici öznitelikleri ve javascript: adresleri nonce taşıyamadığı için çalışmaz. Politikaya eski tarayıcılar için 'unsafe-inline' eklense bile güncel tarayıcılar nonce gördüğünde onu yok sayar. Sayfanın kendi satır içi betikleri ise ya nonce alır ya da ayrı dosyaya taşınır.

Nonce'u sunucu her yanıtta güvenli bir rastgele sayı üretecinden alır ve en az 128 bit uzunlukta tutar. Sabit ya da tahmin edilebilir bir nonce politikayı boşa çıkarır, çünkü saldırgan aynı değeri kendi etiketine yazar. Önbellekten sunulan bir sayfada her ziyaretçi aynı nonce'u alır. Böyle sayfalarda nonce yerine betiklerin özet (hash) değeri kullanılır.

Politikayı doğrudan açmak sayfanın meşru betiklerini de durdurabilir. Önce Content-Security-Policy-Report-Only başlığıyla yayınlarsın; tarayıcı hiçbir şeyi engellemez, yalnız ihlali rapor eder. Raporlar iki şey söyler: hangi meşru betiğin düzeltilmesi gerektiğini ve sahada hangi yüklerin denendiğini. Raporlar temizlenince politikayı engelleyen moda alırsın.

Etkinlik 7 / 8

Takip portalına katı CSP'yi sayfaları kırmadan getirmek için adımları sırala.

Sürükleyip doğru sıraya koy.

Bir öğeye odaklan, yukarı ve aşağı ok tuşlarıyla taşı. Home tuşu en üste, End tuşu en alta alır.

  • 1Politikayı Report-Only başlığıyla yayınla
  • 2Politikayı engelleyen moda al
  • 3İhlal raporlarını incele, kalan meşru betikleri düzelt
  • 4Satır içi betikleri ve olay işleyicilerini nonce'lu betiğe ya da ayrı dosyaya taşı

DOM tabanlı XSS için ikinci katman Trusted Types'tır. Politikaya require-trusted-types-for 'script' yönergesini eklersen innerHTML gibi tehlikeli hedefler düz dizgeyi reddeder. Bu hedeflere yalnız senin tanımladığın bir politikadan geçmiş değer yazılabilir. Kod incelemesi de yüzlerce innerHTML satırından birkaç politika fonksiyonuna iner.

Etkinlik 8 / 8

Trusted Types açıkken takip sayfasındaki yük hangi durakta durdurulur?

Diyagramda doğru kutuyu tıkla.

Kaynaklocation.hashSayfa betiğideğeri okurHedefinnerHTMLHTML ayrıştırıcıetiketi kurarOlay işleyicibetik çalışırSunucu yanıtı temizdir; değeri sayfaya tarayıcıdaki betik yazar.

Çerez bayrakları zararı sınırlar ama açığı kapatmaz. HttpOnly işaretli oturum çerezini betik document.cookie ile okuyamaz, saldırgan oturumu kendi tarayıcısına taşıyamaz. Yine de betik kurbanın sekmesinde çalışmayı sürdürür. Kurbanın oturumuyla istek atar, ekrandaki adresi okur ve formu onun adına gönderir.

Derinleş:Trusted Types politikası nasıl kurulur

Trusted Types, tehlikeli hedeflere yazılan değerin türünü denetler. Denetim açıkken innerHTML'e düz dizge atamak hata fırlatır. Değer önce trustedTypes.createPolicy ile kurulmuş, adı olan bir politikadan geçer; politikanın createHTML fonksiyonu değeri temizler ya da kodlar. Dönen TrustedHTML nesnesi hedefe yazılabilir, düz dizge yazılamaz.

Başlıkta iki yönerge birlikte çalışır. require-trusted-types-for 'script' denetimi açar, trusted-types yönergesi ise hangi adlarla politika kurulabileceğini sınırlar. Böylece sonradan eklenen bir kütüphane kendi gevşek politikasını sessizce kuramaz. Geçişte önce Report-Only başlığını kullanır, düz dizge yazan satırları raporlardan tek tek çıkarırsın.

Bu yönergeleri tanımayan bir tarayıcı onları yok sayar; sayfa çalışır ama ek koruma da gelmez. Bu yüzden Trusted Types kodlamanın yerine geçmez. Doğru kodlanmış bir sayfada gözden kaçan tek satırı yakalayan bir emniyet kemeri gibi çalışır.

Bu derste bir hata mı gördün? Bildir