---
title: 'Siteler Arası Betik Çalıştırma'
source: 'https://academia.sh/tr/kurslar/frontend-kalitesi/siteler-arasi-betik-calistirma'
course: 'Frontend Kalitesi'
language: tr
updated: '2026-08-17T18:09:22+00:00'
license: 'CC BY-SA 4.0'
---

# Siteler Arası Betik Çalıştırma

Sayfaya giren metnin kod olarak çalışması sorunu; yansıyan, depolanmış ve DOM tabanlı türlerin oluşum yolu, bağlama duyarlı çıktı kaçırma ve bağ hedeflerinde şema izin listesi.

Bu kursun ilk iki konusu kaliteyi ölçülebilir iki eksende ele aldı. Başarım konusu
sayfanın ne kadar hızlı kullanılabilir olduğunu bütçeyle bağladı; erişilebilirlik konusu,
Erişilebilirlik Testi dersinde otomatik denetimin nereye kadar gidebildiğini ve elle
doğrulamanın nerede zorunlu olduğunu gösterdi. İki eksen de kullanıcının arayüzle
kurduğu ilişkiyi iyileştirmeyi hedefliyordu.

Üçüncü eksen ters yönde durur: kullanıcıyı arayüzün kendisinden korumak. Kuzey Yamaç
Ölçüm İstasyonu uygulamasında her ölçüm kaydına serbest metin bir **ölçüm notu** girilir.
Bu not veritabanına yazılır, başka kullanıcıların ekranında görünür, dışa aktarılan
raporda yer alır. Notu yazan kişi kötü niyetli olmasa bile, yazdığı metnin sayfaya nasıl
girdiği bir güvenlik kararıdır. Bu ders, o metnin veri olarak kalmasını sağlayan katmanı
kurar.

## Metnin Koda Dönüştüğü An

Tarayıcı bir belgeyi ayrıştırırken metni bağlamına göre yorumlar. Web'in Temelleri ve HTML
kursunda kurulan ayrım burada belirleyici olur: `<` karakteri ayrıştırıcı için bir etiketin
başlangıcıdır, sıradan bir harf değildir. Uygulama, kullanıcıdan gelen bir dizgiyi belgeye
olduğu gibi yerleştirdiğinde ayrıştırıcıya "burada veri var" demez; ayrıştırıcı gördüğü
karakterleri kendi kurallarına göre okur ve dizginin içindeki işaretlemeyi yapı olarak
kabul eder.

**Siteler arası betik çalıştırma (cross-site scripting)**, bu sınır kaymasının adıdır: bir
kaynağın (origin) belgesi içinde, o kaynağın yetkileriyle, uygulamanın yazmadığı kod
çalışır. Yetki sözcüğü kritiktir. Çalışan kod, belgeyle aynı kaynakta olduğu için o
kaynağın yerel depolamasını okuyabilir, `HttpOnly` işaretli olmayan çerezlerine erişebilir,
kullanıcının oturumuyla istek gönderebilir. Aynı kaynak politikası (same-origin policy) bu
kodu yabancı saymaz, çünkü kod belgenin içindedir.

Sorunun kökeni bir ayrıştırıcı hatası değildir. Kökeni, uygulamanın veriyi kod olarak
yorumlanabilecek bir yere, yorumlanmasını engelleyen bir dönüşüm yapmadan koymasıdır.

## Üç Oluşum Yolu

Aynı sonuca üç farklı yoldan varılır ve savunmanın nereye konacağı bu yola bağlıdır.

**Yansıyan (reflected)** biçimde, girdi isteğin kendisinden gelir ve aynı yanıtta geri
yazılır. Ölçüm listesindeki arama alanı sorgu dizgisine (query string) yazılır, sunucu
"aradığınız terim: …" başlığını üretirken bu değeri sayfaya koyar. Zarar tek bir yanıtta
oluşur ve kurbanın hazırlanmış bir adresi açması gerekir.

**Depolanmış (stored)** biçimde, girdi bir kez kaydedilir ve sonraki her okumada yeniden
sunulur. Ölçüm notu tam olarak bu sınıftadır: notu kaydeden kullanıcı ile etkilenen
kullanıcı farklı kişilerdir ve etkilenen kişi sıradan bir gezinti yapar. Kalıcılığı
nedeniyle etkisi en geniş türdür.

**DOM tabanlı** biçimde, veri sunucuya hiç uğramaz. İstemci kodu, adresin parçasını
(fragment), yerel depolamadaki bir kaydı veya bir ileti olayının içeriğini okur ve belgeye
yazar. Sunucu günlüklerinde iz kalmaz; savunma yalnızca istemci kodunda kurulabilir.
Tarayıcı ve Web Platformu kursunda tanıtılan depolama API'leri bu yüzden güvenilir kaynak
sayılmaz: yerel depolamaya yazan da aynı sayfada çalışan koddur.

## Savunmanın Yeri: Girişte Değil Çıkışta

Sezgisel çözüm girdiyi süzmektir: kayıt sırasında tehlikeli görünen karakterleri atmak.
Bu yaklaşım iki nedenle yetersizdir. Birincisi, hangi karakterin tehlikeli olduğu girdinin
nereye yazılacağına bağlıdır ve kayıt anında bu bilinmez; aynı not hem HTML gövdesinde hem
bir öznitelikte hem de dışa aktarılan bir adreste görünebilir. İkincisi, süzme veriyi bozar:
"12'lik anemometre &lt; 3 m/sn okudu" cümlesi geçerli bir ölçüm notudur ve olduğu gibi
saklanmalıdır.

Doğru yer çıkıştır. Veri deposunda ham metin durur; belgeye yazılacağı anda, yazılacağı
bağlamın kurallarına göre dönüştürülür. Bu dönüşüme **kaçırma (escaping)** denir: anlamlı
karakterleri, o bağlamda anlamını yitiren ama görüntüsü korunan gösterimlerle değiştirmek.

Kaçırma bağlamdan bağımsız değildir. Dört ayrı bağlamın dört ayrı kuralı vardır ve birinin
kuralını başka bir bağlamda uygulamak koruma sağlamaz.

## Dört Bağlam, Dört Kural

Aşağıdaki dosya aynı ölçüm notunu dört bağlam için ayrı ayrı kaçırır. Not, dört bağlamın
da anlamlı karakterlerini taşıyan zararsız bir işaret metnidir.

```js
// kacirma.mjs — ayni olcum notunu dort baglamda kaciran islevler
const GOVDE = { '&': '&amp;', '<': '&lt;', '>': '&gt;' };
const OZNITELIK = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' };

function govdeKacir(metin) {
  return String(metin).replace(/[&<>]/g, (c) => GOVDE[c]);
}

function oznitelikKacir(metin) {
  return String(metin).replace(/[&<>"']/g, (c) => OZNITELIK[c]);
}

function adresKacir(metin) {
  return encodeURIComponent(String(metin));
}

function betikKacir(metin) {
  return JSON.stringify(String(metin))
    .replace(/</g, '\\u003c')
    .replace(/>/g, '\\u003e')
    .replace(/&/g, '\\u0026');
}

const not = `12'lik <isaret> & "kuzey" yamaci`;

const baglamlar = [
  ['HTML govdesi', govdeKacir(not)],
  ['Oznitelik degeri', oznitelikKacir(not)],
  ['Adres bileseni', adresKacir(not)],
  ['Betik dizgisi', betikKacir(not)],
];

console.log('girdi:', not);
for (const [ad, sonuc] of baglamlar) {
  console.log(ad.padEnd(18), sonuc);
}
```

```
$ node kacirma.mjs
girdi: 12'lik <isaret> & "kuzey" yamaci
HTML govdesi       12'lik &lt;isaret&gt; &amp; "kuzey" yamaci
Oznitelik degeri   12&#39;lik &lt;isaret&gt; &amp; &quot;kuzey&quot; yamaci
Adres bileseni     12'lik%20%3Cisaret%3E%20%26%20%22kuzey%22%20yamaci
Betik dizgisi      "12'lik \u003cisaret\u003e \u0026 \"kuzey\" yamaci"
```

Dört satır dört farklı çıktı verir ve farkların her biri bir kurala karşılık gelir.

Gövde bağlamında yalnız üç karakterin dönüştürülmesi yeter; tırnak işaretleri metin
düğümü içinde anlam taşımaz. Öznitelik bağlamında tırnaklar da dönüştürülür, çünkü tırnak
öznitelik değerinin sınırını belirler. Bu iki kuralın karışması sık görülen bir kusurdur:
gövde kuralıyla kaçırılmış bir değeri tırnaklı bir özniteliğe yazmak, değerin özniteliği
erken kapatmasına izin verir.

Adres bağlamında kural tümüyle başkadır. `&` karakteri HTML varlığına değil, yüzde
kodlamasına çevrilir; çünkü buradaki ayrıştırıcı HTML ayrıştırıcısı değil adres
çözümleyicisidir. Betik bağlamında ise dizgi sınırlayıcısı ve ters bölü karakterleri
dönüştürülür; `<` karakterinin `\u003c` biçimine çevrilmesi, dizginin içindeki metnin
kuşatan betik ögesini erken sonlandırmasını engeller.

Bu iç içelik bir kuralı doğurur: bir değer birden çok bağlamdan geçiyorsa kaçırma da iç
içe uygulanır. Betik içinde yazılan bir adres için önce adres kodlaması, sonra betik
kaçırması gerekir; sıra ters çevrilirse ikinci dönüşüm birincinin ürettiği karakterleri
bozar.

## Kaçırmanın Yetmediği Yer: Şema Denetimi

Bazı öznitelikler değeri metin olarak değil, adres olarak yorumlar. Bir bağın hedefi
kaçırılmış olsa bile bir çalıştırılabilir şemaya işaret ediyorsa, kaçırma hiçbir şey
değiştirmez: karakterlerin hiçbiri HTML açısından anlamlı değildir. Ölçüm notundaki
kullanıcı bağlarını sayfaya koyan kod, bu yüzden şemayı ayrıca denetler.

```js
// sema.mjs — bag hedefinin semasini izin listesine gore dogrular
const IZINLI_SEMA = new Set(['http:', 'https:', 'mailto:']);

function bagHedefi(ham, taban) {
  let adres;
  try {
    adres = new URL(ham, taban);
  } catch {
    return { izinli: false, neden: 'cozumlenemedi' };
  }
  if (!IZINLI_SEMA.has(adres.protocol)) {
    return { izinli: false, neden: `sema listede yok: ${adres.protocol}` };
  }
  return { izinli: true, hedef: adres.href };
}

const taban = 'https://olcum.ornek.test/notlar/';
const adaylar = [
  '/istasyon/kuzey-yamac',
  'https://veri.ornek.test/rapor.csv',
  'mailto:kayit@ornek.test',
  'veri-semasi:text/html;base64,SSBhbSB0ZXh0',
  'olcum://kuzey',
  '::bozuk::',
];

for (const aday of adaylar) {
  const sonuc = bagHedefi(aday, taban);
  console.log(String(sonuc.izinli).padEnd(6), aday.padEnd(44), sonuc.hedef ?? sonuc.neden);
}
```

```
$ node sema.mjs
true   /istasyon/kuzey-yamac                        https://olcum.ornek.test/istasyon/kuzey-yamac
true   https://veri.ornek.test/rapor.csv            https://veri.ornek.test/rapor.csv
true   mailto:kayit@ornek.test                      mailto:kayit@ornek.test
false  veri-semasi:text/html;base64,SSBhbSB0ZXh0    sema listede yok: veri-semasi:
false  olcum://kuzey                                sema listede yok: olcum:
true   ::bozuk::                                    https://olcum.ornek.test/notlar/::bozuk::
```

Son satır kuralın nasıl yazılması gerektiğini gösterir. `::bozuk::` geçerli bir mutlak
adres değildir, ama bir taban adresle çözümlendiğinde göreli yol sayılır ve sayfanın kendi
şemasını devralır. Denetim ham dizgi üzerinde değil, **çözümlenmiş adres** üzerinde
yapılır; izin listesi de reddedilecekleri saymak yerine kabul edilecekleri sayar. Reddedilenleri
saymak, listeye girmemiş her şemayı sessizce geçirir.

## Kaçırmayı Elle Yapmamak

Yukarıdaki işlevler kuralı göstermek için yazıldı; üretim kodunda her yazma noktasında
elle çağrılmaları beklenmez. Bileşen Tabanlı Arayüz Geliştirme kursunda tanıtılan
bildirimsel oluşturma modelinin güvenlik açısından en önemli özelliği budur: ifade yerine
konan değerler öntanımlı olarak metin düğümü sayılır ve bağlamına uygun biçimde kaçırılır.
Kaçırma öntanımlı, ham işaretleme ekleme ise açıkça istenen bir kaçış deliğidir.

Bu kapıların hepsinin ortak yanı, adlarının veya belgelerinin tehlikeyi bildirmesidir: ham
HTML atayan özellik, işaretleme ayrıştırıp ekleyen yöntem, dizgiden bileşen üreten
yardımcılar. Denetim kuralı sadedir: bu kapıların geçtiği yerler sayılabilir olmalı ve her
biri için "buraya gelen veri nereden geliyor" sorusunun yazılı bir yanıtı bulunmalıdır.
Metin yazmak yeterliyse `textContent` kullanılır; `innerHTML` bir ihtiyaç değil, bir
karardır.

Kullanıcının biçimlendirilmiş metin girmesi gerçekten gerekiyorsa sorun kaçırma sorunu
olmaktan çıkar ve **arındırma (sanitization)** sorununa dönüşür: işaretlemeyi ayrıştırıp
yalnız izin listesindeki ögeleri, öznitelikleri ve şemaları bırakan bir katman gerekir.
Arındırma kaçırmadan belirgin biçimde zordur, çünkü ayrıştırma davranışlarındaki
farklılıklara duyarlıdır. Kural olarak arındırıcı elle yazılmaz; bakımı sürdürülen bir
uygulaması kullanılır ve çıktısı, bu dersin sonraki katmanı olan politika denetimiyle
birlikte savunulur.

## Özet

- Siteler arası betik çalıştırma, verinin kod olarak yorumlanabildiği bir yere dönüşümsüz
  yerleştirilmesinden doğar; çalışan kod belgenin kaynağının yetkilerini devralır.
- Üç oluşum yolu vardır: yansıyan, depolanmış ve DOM tabanlı. Sonuncusunda veri sunucuya
  hiç uğramadığı için savunma yalnız istemci kodunda kurulabilir.
- Savunma girişte süzmek değil, çıkışta bağlama uygun kaçırmaktır; HTML gövdesi, öznitelik,
  adres ve betik bağlamlarının kuralları birbirinin yerine geçmez.
- Adres yorumlayan öznitelikler için kaçırma yetmez; çözümlenmiş adresin şeması bir izin
  listesine karşı denetlenir.
- Bildirimsel oluşturma kaçırmayı öntanımlı yapar; ham işaretleme ekleyen kaçış kapıları
  sayılabilir ve gerekçelendirilebilir olmalıdır.

## Sonraki Adım

Kaçırma, uygulamanın kendi yazdığı her yazma noktasında doğru davranmasına bağlıdır. Tek
bir noktanın atlanması savunmanın tamamını geçersiz kılar ve büyük bir arayüzde bunun
gözden kaçmayacağını varsaymak gerçekçi değildir. Sonraki ders, kaçırmanın altına ikinci
bir katman koyar: tarayıcıya hangi kaynaktan gelen betiğin çalıştırılabileceğini bildiren
ve bu kararı belgenin kendisinden bağımsız hale getiren içerik güvenlik politikası.
