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()vebin2hex()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.