İçindekiler
CSRF (Cross-Site Request Forgery, siteler arası istek sahteciliği), tarayıcıların çok temel bir davranışından yararlanır: Bir siteye istek gönderildiğinde, o siteye ait çerezler isteği kim başlatmış olursa olsun isteğe eklenir.
Saldırı nasıl çalışır?
Diyelim ki banka.ornek sitesinde para transferi şu formla yapılıyor:
<form action="https://banka.ornek/transfer" method="post">
<input name="alici" value="TR12...">
<input name="tutar" value="1000">
</form>
Kullanıcı bankaya giriş yapmış ve oturum çerezi tarayıcısında duruyor. Ardından başka bir sekmede, saldırganın hazırladığı bir sayfayı açıyor. O sayfada görünmez bir form var:
<form id="f" action="https://banka.ornek/transfer" method="post">
<input type="hidden" name="alici" value="SALDIRGANIN-IBANI">
<input type="hidden" name="tutar" value="5000">
</form>
<script>document.getElementById('f').submit();</script>
Sayfa açıldığı anda form, kullanıcının tarayıcısından bankaya gönderilir. Tarayıcı, bankanın oturum çerezini isteğe ekler. Banka açısından bu, giriş yapmış kullanıcının kendisinden gelen geçerli bir istektir.
Saldırganın sonucu görmesine gerek yoktur. Yalnızca işlemin yapılması yeterlidir.
Hangi işlemler risk altında?
Durum değiştiren her işlem: şifre ya da e-posta değiştirme, para transferi, sipariş verme, kullanıcı silme, yönetici ekleme, ayarları değiştirme. Bir yönetim panelindeki CSRF açığı, saldırganın kendine yönetici hesabı açtırmasına bile yol açabilir.
Çözüm 1: CSRF token
En yaygın ve en güvenilir yöntem, her forma tahmin edilemeyen bir token eklemek ve sunucuda bu token'ı kontrol etmektir.
Token'ı oturumda üretin:
session_start();
if (empty($_SESSION['csrf'])) {
$_SESSION['csrf'] = bin2hex(random_bytes(32));
}
Her forma gizli alan olarak ekleyin:
<input type="hidden" name="csrf" value="<?= htmlspecialchars($_SESSION['csrf']) ?>">
İşlemi yapmadan önce kontrol edin:
if (!hash_equals($_SESSION['csrf'] ?? '', $_POST['csrf'] ?? '')) {
http_response_code(403);
exit('Geçersiz istek');
}
Bu yöntem neden işe yarar? Saldırganın sayfası, kullanıcının tarayıcısında bankanın sayfasını okuyamaz. Tarayıcıların aynı köken politikası (same-origin policy) buna izin vermez. Bu yüzden token'ı bilemez ve sahte formuna ekleyemez.
İki ayrıntı önemli:
==yerinehash_equalskullanın. Bu fonksiyon karşılaştırmayı sabit sürede yapar ve zamanlama saldırılarını önler.- Token'ı adreste (GET parametresi olarak) taşımayın. Adresler loglara, tarayıcı geçmişine ve Referer başlığına sızabilir.
Laravel, Symfony ve WordPress gibi çatılar bu mekanizmayı yerleşik olarak sunar. WordPress'te bu iş nonce ile yapılır: Formlara wp_nonce_field() eklenir, işlem tarafında check_admin_referer() ya da wp_verify_nonce() ile kontrol edilir. Kendi eklentinizi yazıyorsanız bu adımı atlamamak çok önemlidir.
Çözüm 2: SameSite çerezler
Modern tarayıcılar, çerezlere SameSite özniteliği eklenmesine izin verir:
session_set_cookie_params([
'secure' => true,
'httponly' => true,
'samesite' => 'Lax',
]);
- Strict: Çerez yalnızca aynı siteden başlatılan isteklerde gönderilir. Başka bir siteden gelen bir bağlantıya tıkladığınızda bile çerez gönderilmez; kullanıcı giriş yapmamış gibi görünür.
- Lax: Başka sitelerden gelen POST istekleri ve arka planda yapılan istekler çerez taşımaz. Ama bir bağlantıya tıklanarak yapılan normal geçişlerde çerez gönderilir. Kullanılabilirlik ile güvenlik arasında iyi bir dengedir.
- None: Çerez her durumda gönderilir.
Secureile birlikte kullanılmalıdır. Yalnızca gerçekten siteler arası çalışması gereken çerezler için kullanın.
Chrome başta olmak üzere modern tarayıcıların büyük kısmı, SameSite belirtilmemiş çerezleri Lax gibi ele alır. Bu, CSRF saldırılarını önemli ölçüde zorlaştırdı. Ama bu tek başına bir çözüm değildir: Eski tarayıcılar, alt alan adlarından gelen istekler (SameSite "site" kavramını alan adı düzeyinde ele alır) ve GET ile durum değiştiren uç noktalar hâlâ risk taşır.
Çözüm 3: GET ile durum değiştirmeyin
https://ornek.com/hesap/sil?id=42
Böyle bir adres, bir resim etiketinin içine bile konabilir: <img src="https://ornek.com/hesap/sil?id=42">. Sayfa açıldığında tarayıcı bu adrese istek gönderir. SameSite=Lax da GET ile yapılan üst seviye geçişlerde çerezi gönderir. Kural basit: GET istekleri yalnızca veri okur, asla bir şeyi değiştirmez. Silme, güncelleme ve oluşturma işlemleri POST, PUT ya da DELETE ile yapılır ve token ile korunur.
Çözüm 4: Origin başlığını kontrol edin
Tarayıcılar POST isteklerinde isteğin nereden geldiğini gösteren Origin başlığını gönderir. Sunucu tarafında bu başlığın kendi alan adınız olup olmadığını kontrol etmek, ek bir savunma katmanıdır. Ama token'ın yerine geçmez.
API'ler ne olacak?
Kimlik doğrulaması çerezle değil, Authorization başlığında taşınan bir token ile yapılan API'ler CSRF'ye karşı doğal olarak korunur. Çünkü tarayıcı bu başlığı başka bir siteden gelen isteğe kendiliğinden eklemez. Ama API'niz tarayıcıdan çerezle kullanılıyorsa, aynı önlemler orada da gereklidir.
Özet
- Durum değiştiren her form ve istek için bir CSRF token ve sunucu tarafında kontrol.
- Oturum çerezlerinde
SameSite=Lax,SecureveHttpOnly. - GET istekleriyle asla bir şey değiştirmeyin.
- Çatınızın yerleşik korumasını kullanın ve kapatmayın.
Yorumlar
Henüz yorum yok. İlk yorumu siz yazın.