İçeriğe geç
academia.sh

Ders 13 / 15

İstek Sahteciliği Koruması

Bir isteği kullanıcının kendi uygulamasının mı yoksa başka bir sitenin mi yaptığını sahtecilik belirteciyle ayırmak, belirtecin ölçülen etkisi ve SameSite çerez niteliğinin sınırı.

Yayın Güncelleme

Bu derste

  • Belirteçsiz ve belirteçli bir e-posta güncellemesinde sonucu kimin değiştirdiğini ölçer
  • random_bytes(), bin2hex() ve hash_equals() ile sahtecilik belirteci üretir ve karşılaştırır
  • SameSite çerez niteliğinin tarayıcının davranışı olduğunu ve belirtecin yerine değil yanına konduğunu açıklar
İçindekiler

Kaçışlama sayfaya basılan metni sınırladı. İstek ise başka yönden gelir: kullanıcı uygulamada oturum açıkken başka bir sitedeki bir sayfa, kullanıcının tarayıcısından uygulamaya bir istek gönderebilir. Siteler arası istek sahteciliği (cross-site request forgery, CSRF) önleme sorusu şudur: bu isteğin kullanıcının kendi ekranından mı yoksa başka bir yerden mi geldiğini kim belirliyor? Bu ders isteğin kabul edilip edilmediğini ölçer; reddedilen isteğin kodu koştu mu diye sormaz.

GÜ4. Bu derste “başka site” yalnız bir dizi kaydıdır: 'koken' => 'https://baska.test'. Gerçek bir ikinci site, çerez ya da tarayıcı kurulmaz. İstekler dizidir; ölçülen şey, e-posta güncellemesinin kabul edilip edilmediğidir. Belirteç hiçbir yerde basılmaz, yalnız uzunluğu basılır.

Belirteçsiz ve Belirteçli Güncelleme

Sahtecilik belirteci (CSRF token), sunucunun kullanıcıya gösterdiği forma koyduğu ve istekle birlikte geri beklediği rastgele bir değerdir. Kullanıcının kendi ekranından gelen istek bu değeri taşır; başka bir sitedeki sayfa değeri bilmediği için taşıyamaz. Belirteç random_bytes() ile üretilir, bin2hex() ile okunabilir hâle getirilir ve karşılaştırma hash_equals() ile yapılır: bu fonksiyon iki dizgeyi, farkın ilk bulunduğu konumdan bağımsız bir sürede karşılaştırır.

<?php
// istek.php — korumasız ve belirteçli e-posta güncellemesi
declare(strict_types=1);

function belirtecUret(): string {
    return bin2hex(random_bytes(16));
}

function guncelle(string $mevcut, array $istek, ?string $beklenen): string {
    if ($beklenen !== null && !hash_equals($beklenen, (string) ($istek['belirtec'] ?? ''))) {
        return $mevcut;
    }
    return $istek['eposta'];
}

guncelle() yeni e-postayı döndürür; istek reddedilirse mevcut değeri olduğu gibi geri verir. $beklenen boşsa koruma yoktur. Üç istek iki yöntemle denenir: kullanıcının kendi ekranından, belirteçsiz başka siteden, yanlış belirteçli başka siteden.

<?php
// guncelle.php — üç istek, iki yöntem
declare(strict_types=1);
require 'istek.php';

$mevcut = '[email protected]';
$belirtec = belirtecUret();
$istekler = [
    'uygulamadan    ' => ['koken' => 'https://uygulama.test', 'eposta' => '[email protected]', 'belirtec' => $belirtec],
    'belirteçsiz    ' => ['koken' => 'https://baska.test', 'eposta' => '[email protected]'],
    'yanlış belirteç' => ['koken' => 'https://baska.test', 'eposta' => '[email protected]', 'belirtec' => str_repeat('0', 32)],
];
foreach ($istekler as $ad => $istek) {
    $korumasiz = guncelle($mevcut, $istek, null) !== $mevcut;
    $korumali = guncelle($mevcut, $istek, $belirtec) !== $mevcut;
    echo $ad, ' · korumasız ', $korumasiz ? 'kabul' : 'ret', ' · belirteçli ', $korumali ? 'kabul' : 'ret', "\n";
}
echo 'belirteç uzunluğu ', strlen($belirtec), "\n";
echo 'iki belirteç ', belirtecUret() === belirtecUret() ? 'aynı' : 'farklı', "\n";
uygulamadan     · korumasız kabul · belirteçli kabul
belirteçsiz     · korumasız kabul · belirteçli ret
yanlış belirteç · korumasız kabul · belirteçli ret
belirteç uzunluğu 32
iki belirteç farklı

Korumasız yöntem üç isteği de kabul etti; başka sitenin isteği ile kullanıcının kendi isteği arasında fark yoktu. Belirteçli yöntem yalnız belirteci taşıyan isteği kabul etti; belirteçsiz ve yanlış belirteçli istek reddedildi. Belirteç 32 karakterdir (16 bayt, onaltılık yazımla) ve iki üretim farklı değer verdi. Ölçülen şey kabul ya da retten ibarettir; belirtecin kendisi basılmaz, çünkü basılan bir belirteç bir sonraki çalıştırmada başka bir değer olurdu ve gizli bir değerin çıktıda yeri yoktur.

Taraf: Güncellemeyi Kim Yapıyor

Aynı ölçümü taraf.php çekirdeğine bağlayalım. Karar sırası istemci, üçüncü taraf, ortam; hiçbiri değiştiremediyse sunucu.

<?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ç, güncellemeden sonraki e-postadır. Sapma, üçüncü tarafın belirteçsiz gönderdiği istektir: başka sitenin kaydı ve kendi e-postası. Sapmasız istek, kullanıcının belirteçli kendi isteğidir.

<?php
// sahtecilik.php — e-postayı kim değiştirdi
declare(strict_types=1);
require 'taraf.php';
require 'istek.php';

$belirtec = belirtecUret();
$kendi = ['koken' => 'https://uygulama.test', 'eposta' => '[email protected]', 'belirtec' => $belirtec];
$baska = ['koken' => 'https://baska.test', 'eposta' => '[email protected]'];

tarafRaporu('belirteçsiz', fn(array $s) => guncelle('[email protected]', $s['üçüncü taraf'] ?? $kendi, null), ['üçüncü taraf' => $baska]);
tarafRaporu('belirteçli', fn(array $s) => guncelle('[email protected]', $s['üçüncü taraf'] ?? $kendi, $belirtec), ['üçüncü taraf' => $baska]);
belirteçsiz → üçüncü taraf
    üçüncü taraf değiştirdi
belirteçli → sunucu
    üçüncü taraf değiştiremedi

Belirteçsiz güncellemede e-postayı üçüncü taraf değiştirdi: başka sitenin isteği kullanıcının e-postasını kendi değeriyle değiştirdi. Belirteçli güncellemede üçüncü taraf değiştiremedi; kalan sahip sunucudur, çünkü belirteci üreten ve karşılaştıran kod orada. Belirteç değeri sunucunun elinde kalır ve forma yalnız kopyası konur; isteği gönderen taraf kopyayı görmediği sürece karşılığını üretemez.

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

Tarayıcıların çerezi başka bir sitenin isteğiyle göndermesini sınırlayan bir çerez niteliği (cookie attribute) vardır: SameSite. Sunucu bu niteliği çerez başlığına yazarak ister; uygulamak ise tarayıcıya aittir. Sunucu tarafında ölçülebilen tek şey, başlık dizgesinin içinde nitelik olup olmadığıdır.

<?php
// samesite.php — SameSite sunucuda yalnız bir başlık dizgesidir
declare(strict_types=1);
require 'taraf.php';
require 'istek.php';

function cerezBasligi(string $ad, string $deger, string $samesite): string {
    return 'Set-Cookie: ' . $ad . '=' . $deger . '; SameSite=' . $samesite . '; HttpOnly';
}

echo cerezBasligi('oturum', 'deger', 'Lax'), "\n";

$kendi = ['koken' => 'https://uygulama.test', 'eposta' => '[email protected]'];
$baska = ['koken' => 'https://baska.test', 'eposta' => '[email protected]'];
tarafRaporu('SameSite yalnız', fn(array $s) => guncelle('[email protected]', $s['üçüncü taraf'] ?? $kendi, null), ['üçüncü taraf' => $baska]);
Set-Cookie: oturum=deger; SameSite=Lax; HttpOnly
SameSite yalnız → üçüncü taraf
    üçüncü taraf değiştirdi

Başlık dizgesinde SameSite=Lax var, ama sunucu kodundaki sonuç değişmedi: e-postayı yine üçüncü taraf değiştirdi. Bu beklenen sonuçtur. Niteliğin etkisi sunucuda koşan hiçbir satırda ölçülemez, çünkü çerezi isteğe eklemeyi ya da eklememeyi tarayıcı seçer; sunucu yalnız ister. Dolayısıyla SameSite belirtecin yerine konmaz, yanına konur: nitelik tarayıcı tarafında bir katman ekler, belirteç sunucu tarafında sahibi değiştirir. İkincisi olmadan sunucunun sonucu üçüncü tarafın elinde kalır.

Özet

  • Bu dersin ölçü sınıfı belirteçsiz güncellemede üçüncü taraf, belirteçli güncellemede sunucu: e-postayı belirteç olmadan başka sitenin isteği değiştirdi; belirteçle değiştiremedi.
  • Sahtecilik belirteci random_bytes() ve bin2hex() ile üretilir (32 karakter), hash_equals() ile karşılaştırılır; belirtecin kendisi basılmaz, uzunluğu basılır.
  • Savunma sonucu sahibinden alır: yanlış ve eksik belirteç aynı biçimde reddedildi, e-posta mevcut değerinde kaldı.
  • SameSite çerez niteliği tarayıcının davranışıdır; sunucu yalnız ister ve başlık dizgesinde görünür. Sunucudaki sonucu değiştirmedi, belirtecin yerine değil yanına konur.

Sonraki Adım

İstek artık kullanıcının ekranından gelmişse, istekle gelen kimlik bilgisi saklanırken de aynı soru sorulur: parola nasıl saklanıyor ve saklanan karmayı kim değiştirebiliyor? Parola Saklama dersi bunu ölçer.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat