XSS: sayfada betik çalıştırmak
- Yansıyan, depolanan ve DOM tabanlı XSS'in nasıl doğduğunu, bağlama göre kaçışı ve içerik güvenlik politikasını çözümlü örneklerle öğrenirsin.
- Üç tür ayrılır: depolanan, yansıyan ve DOM tabanlı.
- Savunma bağlama göre kaçış yapmaktır; kara liste kalıcı çözüm vermez.
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.
Tarayıcı, bir sayfadaki her betiğin o sitenin kaynağına ait olduğunu varsayar. Aynı köken kuralı bu varsayımın üstüne kurulur. Saldırgan sayfaya kendi betiğini sokabilirse, betik sitenin yetkisiyle çalışır: oturum çerezine, form gönderimine ve ekrandaki her veriye erişir.
Siteler arası betik çalıştırma, kısaca XSS, tam bu noktada doğar. Bu derste açığın kaçışsız çıktıdan nasıl doğduğunu, üç türünün nerede durduğunu, bağlama göre kaçışı ve içerik güvenlik politikasının neyi kestiğini çözümlü örneklerle göreceksin.
Betik neden sitenin yetkisiyle çalışır
Açık, kaçışsız çıktıyla doğar. Uygulama kullanıcı girdisini HTML'e olduğu gibi basar. Tarayıcı gelen metni ayrıştırırken açılı parantezi bir etiketin başlangıcı sayar.
Girdi metin olarak değil, kod olarak yorumlanır. Sorun girdinin alınmasında değil, gösterilmesindedir. Aynı girdi bir kez zararsız kaydedilir, sonra kaçışsız gösterildiği anda çalışır.
Betik sayfaya girdiğinde tarayıcı onu sayfanın geri kalanından ayırmaz; ikisi de aynı kökene ait sayılır. Bu yüzden betik, kullanıcının o site üzerindeki bütün yetkisini devralır. XSS'in gücü buradan gelir: saldırgan kendi kodunu kurbanın oturumunda çalıştırır.
Şemadaki her katman girdiyi bir kez işler. Sunucu şablonu değeri HTML'e basar, tarayıcı gelen metni ayrıştırır ve etiketleri yorumlar. Kaçış adımı bu iki katman arasında durur: sunucu, göndereceği metindeki açılı parantez ve tırnak gibi karakterleri kodlarsa tarayıcı onları etiket değil, düz metin sayar.
Kaçış eksikse tarayıcı, kullanıcı girdisini sayfanın geri kalanıyla aynı yetkiye sahip kod olarak okur. Bu yüzden şablon motorunun hangi bağlamı kaçırdığını bilmek, açığın nerede durduğunu anlamanın ilk adımıdır. Aşağıdaki örnekte bu adımın atlandığı görülüyor.
XSS'in adı yanıltıcıdır. Siteler arası ifadesi saldırının kaynağını anlatır, asıl sorun ise çıktı kaçışıdır. Adına takılmadan mekanizmaya bakmak, savunmayı doğru yere koymana yarar. Betiğin sayfada çalışması bir sonuçtur; asıl soru, kullanıcı girdisinin hangi noktada kod olarak yorumlandığıdır.
O noktayı bulabilirsen, hem açığı hem de çözümü aynı anda görürsün. Çünkü açık, girdinin veriyken koda dönüştüğü tek bir sınırda saklanır. Tarayıcıya da güvenme: güncel tarayıcılar sayfaya giren etiketi kendiliğinden ayıklamaz. Koruma uygulamanın kendi çıktısında kurulur; sınırı bulmak, kaçışın nereye konması gerektiğini de gösterir.
Derinleş:XSS teriminin doğuşu ve Samy solucanı
Siteler arası betik çalıştırma terimini Microsoft güvenlik ekibi 2000 yılının ocak ayında ortaya attı; ekip 16 Ocak günü birkaç ad denedi ve ertesi gün cross-site scripting üstünde anlaştı. Terim yerleşti ama adı olayı tam anlatmaz; asıl sorun siteler arası değil, çıktı kaçışıdır. Açığın etkisini en çarpıcı gösteren olay 2005'te yaşandı. Samy Kamkar, bir sosyal ağ profilindeki depolanan XSS'i kullanarak kendini otomatik yayan bir betik yazdı.
Profili gören her kullanıcı, farkında olmadan betiği kendi profiline kopyaladı. Yaklaşık yirmi saat içinde bir milyondan fazla hesap etkilendi; bu, o güne dek görülen en hızlı yayılan koddu. Kamkar hakkında sonradan yasal işlem yapıldı. Olay, depolanan XSS'in neden en ağır tür olduğunu tek örnekte gösterir.
Etkinlik 1 / 7
Yorum alanına <img src=x onerror=alert(1)> yazılırsa sayfada ne olur?
Önce kodu oku, sonra çıktıyı seç.
<div class="yorum"><%- yorum.metin %></div> <!-- EJS ham çıktı, kaçış yok -->
Üç tür: yükün nerede durduğu
Üç tür arasındaki fark, betiğin nerede durduğudur. Depolanan türde yük veritabanına yazılır ve o kaydı açan herkeste çalışır; bir yorum, bir profil adı ya da bir ürün açıklaması kalıcı taşıyıcı olur. Yansıyan türde yük istekle gelir ve aynı yanıtta geri basılır; kurbanın hazırlanmış bir bağlantıya tıklaması gerekir, bu yüzden oltalamayla birleşir. DOM tabanlı türde ise sunucu yükü yanıta basmaz; yanıt temiz gelir.
Açık sayfadaki betiktedir: betik adres çubuğundaki ya da bağlantı parçasındaki bir değeri okur ve doğrudan sayfaya yazar. Değer bağlantı parçasındaysa (# sonrası) tarayıcı onu sunucuya hiç göndermez, yük loglarda iz bırakmaz. Sorgu dizesindeyse yük loglarda görünür, ama yanıt yine temiz kalır. Türü doğru adlandırmak, savunmayı doğru katmana koymanın ilk adımıdır.
DOM tabanlı türü ayrı düşünmek gerekir, çünkü kaynak ile hedef arasındaki yol tümüyle tarayıcıda kurulur. Kaynak, betiğin okuduğu kullanıcı kontrollü değerdir: adres çubuğu, bağlantı parçası ya da mesaj olayı. Hedef, o değerin yazıldığı tehlikeli noktadır, örneğin HTML yorumlayan innerHTML alanı. Kaynaktan hedefe giden değer arada temizlenmezse betik çalışır.
Sunucu tarafı kaçış burada işe yaramaz, çünkü değeri sayfaya sunucu değil tarayıcıdaki betik yazar. Savunma da istemci koduna, yani güvenli yazma yöntemlerine taşınır. textContent ile yazmak değeri metin olarak sabitler; innerHTML ile yazmak onu koda çevirebilir.
Etki türe göre değişir. Depolanan XSS bir yönetici panelinde en ağır biçimini alır; panele giren her yetkili kullanıcı yükü çalıştırır ve saldırgan onların yetkisini devralır. Yansıyan XSS bağlantı tıklatmayı gerektirdiği için hedefli oltalamayla birleşir. DOM tabanlı XSS'te yanıt temiz kaldığı için yanıtı tarayan sunucu tespiti onu görmez; yük bağlantı parçasındaysa loglarda da iz kalmaz.
Bir yükün test sırasında çalışmaması da seni yanıltmasın; neden çoğu zaman içerik güvenlik politikası, farklı bir kaçış katmanı ya da yanlış yazılan yüktür. Aynı yükün farklı bir bağlamda çalışması, açığın orada durduğunu gösterir. Etkiyi anlatırken somut ol: betiğin ne yapabileceğini karar vericiye göstermek, bulguyu ciddiye aldırır.
Etkinlik 2 / 7
Her senaryoyu XSS türüne göre ayır.
Önce bir seçeneğe, sonra aşağıdaki uygun kutuya bas. İstersen seçeneği kutuya sürükleyip bırak. Yerini değiştirmek için aynı işlemi tekrarla.
0/5 seçenek yerleşti.
Türü ayırt etmek yetmez; açığı doğuran satırı da görebilmelisin. DOM tabanlı XSS'te bu satır neredeyse her zaman aynı deseni taşır: kullanıcı kontrollü bir kaynaktan okunan değer, HTML yorumlayan bir hedefe hiç temizlenmeden yazılır. Bu deseni tanımak, koda bakar bakmaz akışı yakalamana yarar. Tarayıcıda da aynı akışı izleyebilirsin.
Adres çubuğuna benzersiz bir sözcük yazar, geliştirici araçlarının öğe panelinde o sözcüğü ararsın. Sözcük bir etiketin içinde beliriyorsa, değerin hangi hedefe yazıldığını bulmuşsundur. Aşağıdaki kısa betikte kaynağı hedefe bağlayan satırı bul.
Etkinlik 3 / 7
XSS'i doğuran satırı bul.
Bağlam ve kaçış
Kaçış tek bir işlem değildir; hangi bağlamda yaptığına bağlıdır. Aynı girdi HTML gövdesinde, bir etiketin özniteliğinde, bir JavaScript dizisinde ya da bir adres alanında farklı kurallarla yorumlanır. HTML gövdesinde açılı parantezleri kodlarsın, böylece etiket açılmaz. Öznitelik içinde tırnağı kodlarsın, yoksa girdi özniteliği kapatıp yeni bir olay özniteliği ekleyebilir.
JavaScript bağlamında girdiyi hiçbir zaman doğrudan koda gömmezsin; veri olarak, ayrı bir değişkende tutarsın. Adres bağlamında yalnız güvenli şemalara izin verirsin, javascript şemasını dışarıda bırakırsın. Tek bir kaçış fonksiyonu bütün bu bağlamlara yetmez. Doğru bağlamı seçmeden yapılan kaçış görünürde çalışır, sonra başka bir yükle yeniden delinir.
Çıktı kodlaması, girdiyi tarayıcının veri olarak okuyacağı biçime çevirmektir. HTML bağlamında küçük ve büyük işareti varlık koduna dönüştürürsün. Öznitelik bağlamında çift ve tek tırnağı kodlarsın. Modern şablon motorları bu işi çoğu zaman kendiliğinden, varsayılan olarak yapar.
Sorun, geliştiricinin ham HTML basan özel bir çağrı kullandığında çıkar; EJS'teki <%- etiketi böyle bir çağrıdır ve korumayı devre dışı bırakır. Kod incelemesinde tam da bu çağrılar aranır. Tek bir arama, saatlerce süren elle denemeye denk sonuç verir, çünkü açığın kaynağını doğrudan gösterir.
Kaçış ile temizleme birbirine karıştırılır ama farklıdır. Temizleme, girdiden tehlikeli parçaları atmaya çalışır; kaçış ise girdiyi olduğu gibi tutup tarayıcıya veri olarak okutur. Zengin metne izin vermen gereken az sayıda yerde, güvenilir bir ayrıştırıcıyla izin listesine dayalı temizleme kullanılır.
Geri kalan her yerde tercih kaçıştır, çünkü daha öngörülebilir. Kendi elinle yazdığın temizleme kuralları, en sık atlanan yüklerin geçtiği yerdir; olay öznitelikleri ve farklı kodlamalar böyle kuralları tek tek deler.
Etkinlik 4 / 7
Arama parametresi yanıt gövdesine kaçışsız basılıyor. Zararsız bir kanıt yükü yaz.
İsteği tamamla ve gönder.
Kanıt yükünü kurmak bir yanı; onu doğru bağlama göre etkisiz kılmak öbür yanı. Aynı girdi HTML gövdesinde, bir öznitelikte ya da bir betik dizisinde farklı kaçış ister. Yanlış bağlamın kaçışını uygularsan, yük başka bir biçimde yeniden çalışır.
Somut bir örnek bunu gösterir. Bir değeri yalnız HTML kaçışıyla korur, sonra onu tırnaksız bir öznitelik içine basarsan, saldırgan boşluk ekleyip yeni bir olay özniteliği yazabilir; açılı parantez hiç gerekmez. Aynı değer bir betik dizisinin içine gömülürse, HTML kaçışı hiçbir işe yaramaz; orada tırnak ve ters bölü kuralları işler. Bu yüzden önce çıktının hangi bağlama gittiğini belirler, kaçışı ona göre seçersin.
Etkinlik 5 / 7
Her bağlamı, girdiyi güvenli kılan doğru kaçış yöntemiyle eşle.
Ek katman: CSP, çerez ve kod incelemesi
İçerik güvenlik politikası ikinci savunma katmanıdır. Sunucu, tarayıcıya hangi kaynaktan betik çalıştırabileceğini söyleyen bir başlık gönderir. script-src self gibi bir kural, satır içi betiği ve dış alanlardan gelen betiği engeller. Belirli betiklere izin vermek için nonce ya da hash kullanılır: yalnız sunucunun işaretlediği betik çalışır.
Politika doğru kurulduğunda birçok XSS yükü etkisiz kalır, çünkü enjekte edilen betik izin listesinde değildir. Ama yanlış yapılandırma korumayı boşa çıkarır; unsafe-inline değeri satır içi betiğe yeniden kapı açar. Bu yüzden içerik güvenlik politikasını kaçışın yerine değil, yanına koyarsın. Tek katman değil, üst üste katman güvenlik verir.
Çerez bayrakları da etkiyi sınırlar. HttpOnly işaretli bir çerez, betiğin document.cookie ile okumasını engeller. Bu, oturum çerezinin çalınmasını zorlaştırır ama XSS'i durdurmaz; betik yine çalışır ve kullanıcı adına istek atabilir. Yani HttpOnly tek savunma değil, zararı azaltan bir önlemdir.
En hızlı savunma çoğu zaman koda bakmaktır. Şablonlarda ham çıktı basan çağrıları aramak, farklı bağlamlar için ayrı testler yazmak ve bu testleri otomatik koşuma eklemek, ileride açılan yeni bir alanın sessizce delinmesini önler.
Aynı kaçış mantığı web dışına da taşınır. E-posta şablonu, PDF üreteci ya da sohbet arayüzü, kullanıcı girdisini gösterdiği her yerde aynı soruyu sorar: bu değer hangi bağlamda yorumlanacak? Çerçeve kullanmak riski azaltır ama sıfırlamaz; varsayılan kaçış yalnız doğru işlev çağrıldığında devrededir. Test ederken görünen alanlarla yetinme.
Dosya adı, yönlendiren başlığı, kullanıcı aracısı ve hata mesajı da sayfaya basılabilir. Görünmeyen bir alandan sokulup yönetici panelinde çalışan yükler, en sık atlanan senaryolardır. Sonuçta XSS bir çıktı sorunudur; girdiyi nereye yazdığını ve orada nasıl yorumlanacağını bilmeden yazılan hiçbir kod güvenli sayılmaz. Bağlamı doğru okuyan geliştirici, açığı ortaya çıkmadan kapatır.
Derinleş:Tarayıcı XSS süzgeçleri neden kaldırıldı
Bir zamanlar tarayıcılar yansıyan XSS'i yakalamak için yerleşik bir süzgeç taşırdı. Internet Explorer, Chrome ve Safari bu filtreyi X-XSS-Protection başlığıyla açardı. Fikir iyi görünse de filtre hem atlatılabiliyor hem de güvenli sayfalarda yeni açıklar üretebiliyordu; süzgeç sayfadan zararsız betiği yanlışlıkla söküp davranışı bozabiliyordu. Bu yüzden Chrome süzgeci kaldırdı, Firefox hiç eklemedi ve başlık bugün kullanımdan kalkmış sayılır.
Modern öneri net: yansıyan XSS'i tarayıcı süzgeciyle değil, sunucu tarafı kaçışla ve içerik güvenlik politikasıyla durdurursun. Yeni bir katman da olgunlaşıyor. Güvenilir Türler (Trusted Types), require-trusted-types-for yönergesiyle DOM tabanlı XSS'in tehlikeli yazma noktalarını kapatır; bu özellik 2026 başından bu yana güncel tarayıcıların hepsinde çalışır. Savunmanın yönü tarayıcı hilesinden yapısal önlemeye kaydı.
Etkinlik 6 / 7
HttpOnly çerez bayrağı XSS karşısında neyi engeller?
HttpOnly'nin sınırı, savunmanın neden katmanlı olması gerektiğini hatırlatır. Tek bir bayrak, tek bir başlık ya da tek bir filtre XSS'i tümüyle kapatmaz. Çerezde HttpOnly betiğin oturum değerini okumasını keser, ama betik yine sayfada çalışır; kullanıcı adına form gönderir, ekrandaki veriyi dışarı taşır.
İçerik güvenlik politikası birçok yükü durdurur, ama yanlış yapılandırılırsa delinir. Asıl çözüm çıktıyı bağlama göre kaçırmaktır; öbür önlemler bunun yerine değil yanına gelir. Şimdi kaçış ile kara liste dayanıklılığını karşılaştır.
Etkinlik 7 / 7
Doğru mu, yanlış mı?
Çıktıyı bağlama göre kaçırmak, kara liste filtresinden daha dayanıklıdır.
Okumayı istediğin sırayla sürdürebilirsin. Ders, bütün etkinlikleri cevapladığında tamamlanır.