---
title: 'Siteler Arası Betik Çalıştırma Önleme'
source: 'https://academia.sh/tr/kurslar/php-web/siteler-arasi-betik-calistirma-onleme'
course: 'Web ile PHP: Oturum, Veri ve Güvenlik'
language: tr
updated: '2026-10-21T05:03:37+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Kullanıcı metnini htmlspecialchars() ile çıktı anında kaçışlar ve ham çıktıyla yapısal farkı ölçer'
    - 'Metin ve öznitelik bağlamında kaçışlamanın aynı fonksiyonla yapıldığını, bağlamı açıkça adlandırır'
    - 'Çift kaçışlamayı ölçer ve kaçışlamanın tek yerde, çıktı anında yapılmasını gerekçelendirir'
---

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

Kullanıcıdan gelen metni sayfaya basarken kaçışlamanın sonucu nasıl daralttığını, bağlama göre kaçışlamayı ve çift kaçışlamayı ölçmek; kaçışlama sahibi değiştirmez.

SQL Enjeksiyonu Önleme dersinde değer sorgu metninden ayrıldı ve sorgunun sahibi sunucu oldu. Değerin gittiği başka bir yer sayfadır: kullanıcı bir ad girer, uygulama bu adı HTML'e basar. **Siteler arası betik çalıştırma (cross-site scripting, XSS)** önleme sorusu şudur: basılan metnin yapısını, yani hangi parçasının işaretleme sayılacağını kim belirliyor?

**GÜ3.** Bu derste kaçışlanan metinler dört zararsız örnektir: `Tom & Jerry`, `<b>kalın</b>`, `"tırnak"` ve `O'Brien`. Amaç, işaretleme karakterlerinin metin olarak çıkıp çıkmadığını ölçmektir; çalışan bir betik üretmek ya da başka bir siteden istek yapmak ölçülmez. Koşan kod savunma tarafındadır.

## Kaçışlama: Metni Metin Olarak Basmak

**Kaçışlama (escaping)**, işaretleme dilinde özel anlam taşıyan karakterleri, aynı karakteri metin olarak gösteren karşılıklarla değiştirir. PHP'de bu işi `htmlspecialchars()` yapar: `&`, `<`, `>`, çift tırnak ve tek tırnak için `&amp;`, `&lt;`, `&gt;`, `&quot;` ve `&#039;` yazar. Aşağıdaki dosya iki fonksiyon tanımlar: ham çıktı üreten ve kaçışlayan. Ham çıktıyı üreten fonksiyon yalnız karşıtlık için vardır.

```php
<?php
// cikti.php — ham ve kaçışlanmış çıktı
declare(strict_types=1);

function kacisla(string $metin): string {
    return htmlspecialchars($metin, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

function selamlamaHam(string $ad): string {
    return '<p>Merhaba ' . $ad . '</p>';
}

function selamlama(string $ad): string {
    return '<p>Merhaba ' . kacisla($ad) . '</p>';
}

function girdiAlani(string $ad): string {
    return '<input value="' . kacisla($ad) . '">';
}
```

Dört örnek metni önce `kacisla()`'dan, sonra iki selamlama fonksiyonundan geçirelim. Ham çıktıda `<b>kalın</b>` sayfanın yapısına karışır; kaçışlanan çıktıda `<` karakteri sayısı sabit kalır.

```php
<?php
// kacisla.php — kaçışlanan karakterler
declare(strict_types=1);
require 'cikti.php';

foreach (['Tom & Jerry', '<b>kalın</b>', '"tırnak"', "O'Brien"] as $metin) {
    echo $metin, ' → ', kacisla($metin), "\n";
}
echo "\n";
foreach (['ayse', '<b>kalın</b>'] as $ad) {
    echo mb_str_pad($ad, 14), ' ham ', substr_count(selamlamaHam($ad), '<'),
        ' · kaçışlı ', substr_count(selamlama($ad), '<'), " etiket başlangıcı\n";
}
```

```
Tom & Jerry → Tom &amp; Jerry
<b>kalın</b> → &lt;b&gt;kalın&lt;/b&gt;
"tırnak" → &quot;tırnak&quot;
O'Brien → O&#039;Brien

ayse           ham 2 · kaçışlı 2 etiket başlangıcı
<b>kalın</b>   ham 4 · kaçışlı 2 etiket başlangıcı
```

Dört metin de metin olarak çıktı: `&`, `<`, `>`, çift ve tek tırnak karşılıklarına döndü. Sayım yapıyı gösterir. `ayse` adında ham ve kaçışlı çıktı aynıdır, iki etiket başlangıcı vardır (`<p>` ve `</p>`). `<b>kalın</b>` adında ham çıktı dört etiket başlangıcına çıkar; kaçışlı çıktı ikide kalır. Ham çıktıda istemcinin değeri sayfanın yapısına iki etiket ekledi, kaçışlıda ekleyemedi.

## Bağlama Göre Kaçışlama

Aynı fonksiyon her bağlamda yeterli değildir; yeterli olduğu bağlam, çıktının nereye gittiğiyle belirlenir. Metin düğümünde `<` ve `&` yeterlidir. Çift tırnaklı bir **öznitelik** değerinde tırnağın da kaçışlanması gerekir, çünkü değer tırnakla biter. `ENT_QUOTES` bayrağı ikisini de kapsar. `girdiAlani()` bu bağlamı kullanır.

```php
<?php
// baglam.php — öznitelik içi kaçışlama
declare(strict_types=1);
require 'cikti.php';

echo girdiAlani('"tırnak"'), "\n";
echo girdiAlani("O'Brien"), "\n";
echo selamlama("O'Brien"), "\n";
```

```
<input value="&quot;tırnak&quot;">
<input value="O&#039;Brien">
<p>Merhaba O&#039;Brien</p>
```

Öznitelik bağlamında çift tırnak `&quot;` olarak çıktı, yani değeri erken bitiremez. Tek tırnak her iki bağlamda `&#039;` olur. Sonuç şudur: bağlam adlandırılmadan "kaçışladım" denmez. Metin düğümü için yeterli olan, tırnaksız bir özniteliğe ya da başka bir bağlama (betik içeriği, adres) aynen taşınmaz; o bağlamlar için ayrı kaçışlama gerekir ve bu ders yalnızca metin düğümü ile çift tırnaklı özniteliği kapsar.

## Taraf: Kaçışlama Sahibi Değiştirmez

Aynı sorunun taraf ölçümü, `taraf.php` çekirdeğiyle yapılır. Karar sırası istemci, üçüncü taraf, ortam; hiçbiri değiştiremediyse sunucu.

```php
<?php
// taraf.php — bu sonucu kim değiştirebiliyor?
declare(strict_types=1);

// Kodun dışındaki taraflar, karar sırasıyla: her istek, her dış çağrı, her dağıtım.
const DIS_TARAFLAR = ['istemci', 'üçüncü taraf', 'ortam'];

/** Sonucu bir kez sapmasız, sonra her dış tarafın sapmasıyla ayrı ayrı hesaplar. */
function sina(callable $sonuc, array $sapmalar): array {
    $taban = $sonuc([]);
    $degisti = [];
    foreach (DIS_TARAFLAR as $taraf) {
        if (array_key_exists($taraf, $sapmalar)) {
            $degisti[$taraf] = $sonuc([$taraf => $sapmalar[$taraf]]) !== $taban;
        }
    }
    return $degisti;
}

/** Sonucu değiştirebilen ilk dış taraf; hiçbiri değiştiremiyorsa sunucu. */
function taraf(array $degisti): string {
    foreach (DIS_TARAFLAR as $t) {
        if ($degisti[$t] ?? false) return $t;
    }
    return 'sunucu';
}

function tarafRaporu(string $ad, callable $sonuc, array $sapmalar): string {
    $degisti = sina($sonuc, $sapmalar);
    $satirlar = [];
    foreach ($degisti as $t => $d) $satirlar[] = $t . ($d ? ' değiştirdi' : ' değiştiremedi');
    $sonTaraf = taraf($degisti);
    echo "$ad → $sonTaraf\n    ", implode(' · ', $satirlar), "\n";
    return $sonTaraf;
}
```

Sonuç, kaçışlanmış ve kaçışlanmamış selamlama HTML'idir. İstemcinin sapması farklı bir addır: `<b>kalın</b>`.

```php
<?php
// xss.php — basılan HTML'in tarafı
declare(strict_types=1);
require 'taraf.php';
require 'cikti.php';

tarafRaporu('ham çıktı', fn(array $s) => selamlamaHam($s['istemci'] ?? 'ayse'), ['istemci' => '<b>kalın</b>']);
tarafRaporu('kaçışlı çıktı', fn(array $s) => selamlama($s['istemci'] ?? 'ayse'), ['istemci' => '<b>kalın</b>']);
```

```
ham çıktı → istemci
    istemci değiştirdi
kaçışlı çıktı → istemci
    istemci değiştirdi
```

İki çıktının sahibi de **istemcidir**. Kaçışlama bu sahibi değiştirmedi, çünkü basılan metin hâlâ istemcinin gönderdiği ada bağlıdır: ad değişince HTML değişir. Değişen, bu değişimin türüdür. Ham çıktıda istemci sayfanın yapısını da değiştirebiliyordu, kaçışlıda yalnız bir metin düğümünün içeriğini değiştirebilir. Kaçışlama sonucu sunucuya taşımaz, istemcinin elindeki alanı daraltır; sahip aynı kalır.

## Sınırlayıcı Ölçüm

Kaçışlama tek yerde yapılmalıdır. Bir değer depolanırken kaçışlanır, basılırken yine kaçışlanırsa karakterler iki kez dönüştürülür: `&` önce `&amp;` olur, sonra o `&` yeniden `&amp;` olur. Ölçüm, aynı metni bir kez ve iki kez kaçışlar.

```php
<?php
// cift.php — çift kaçışlama
declare(strict_types=1);
require 'cikti.php';

$metin = 'Tom & Jerry';
$saklanan = $metin;
echo 'bir kez   ', kacisla($saklanan), "\n";
echo 'iki kez   ', kacisla(kacisla($saklanan)), "\n";
```

```
bir kez   Tom &amp; Jerry
iki kez   Tom &amp;amp; Jerry
```

İkinci kaçışlama, ilkinin yazdığı `&amp;` içindeki `&` karakterini yeniden kaçışladı ve tarayıcıda `Tom &amp; Jerry` metni görünür oldu. Çift kaçışlama güvenlik açığı değildir, görünür bir hatadır; ama onu "düzeltmek" için kaçışlamayı kapatmak ya da bir yere atlamak ham çıktıyı geri getirir. Doğru düzeltme kaçışlamayı tek yere koymaktır: değer depoda ham durur ve basıldığı anda, basıldığı bağlama göre bir kez kaçışlanır.

## Özet

- Bu dersin ölçü sınıfı **istemci**: kaçışlanmış selamlamada da basılan HTML'i, istemcinin gönderdiği ad değiştirdi.
- Kaçışlama sahibi değiştirmez, daraltır: `<b>kalın</b>` ham çıktıda etiket sayısını artırdı, kaçışlı çıktıda artırmadı; sayfanın yapısı istemcinin değeriyle genişlemedi.
- Aynı fonksiyon metin düğümünde ve çift tırnaklı özniteliğte yeterlidir, `ENT_QUOTES` ile tek ve çift tırnak da kaçışlanır; bağlam açıkça seçilir.
- Çift kaçışlama metni bozar: `Tom &amp;amp; Jerry`. Kaçışlama yalnız çıktı anında, tek yerde yapılır; depoda ham değer durur.

## Sonraki Adım

Kaçışlama sayfaya basılan metni sınırladı. Sayfadan gelen bir istek ise başka bir siteden de gelebilir: isteği gerçekten kullanıcının mı yaptığını kim belirliyor? İstek Sahteciliği Koruması dersi bunu bir belirteçle ölçer.
