CSRF (Cross-Site Request Forgery, Türkçesiyle siteler arası istek sahteciliği) bir kullanıcının açık olan oturumunu, kendi haberi olmadan kötü amaçlı bir isteğe imza attırmak için kullanan bir saldırı türüdür. Saldırgan sizin tarayıcınızda çalışan bir kodu ele geçirmez; onun yerine tarayıcınızın çerezleri otomatik gönderme davranışını kötüye kullanır. Siz banka panelinize ya da hosting kontrol panelinize giriş yapmışken başka bir sekmede zararlı bir sayfayı açarsanız, o sayfa arka planda sizin oturumunuzla gerçek bir istek tetikleyebilir. Sunucu isteği "giriş yapmış meşru kullanıcıdan geldi" diye kabul eder, çünkü çerez gerçekten sizindir.
Bu yazıda CSRF'in temel mantığını sıradan bir örnekle açıklayacağız, sık karıştırılan XSS ile arasındaki farkı netleştireceğiz ve sahada gerçekten işe yarayan korunma yöntemlerini tek tek ele alacağız: CSRF token, SameSite çerez politikası, çift gönderim (double submit) deseni ve para transferi gibi kritik işlemlerde yeniden doğrulama. Amacım, bir sistem yöneticisinin müşterisine anlatır gibi, kavramı hem yeterince derin hem de uygulanabilir bir şekilde aktarmak. Örnekler kasıtlı olarak basit tutuldu; üretim koduna aynen kopyalamaktan çok mantığı görmeniz için yazıldılar.
CSRF Aslında Nasıl Çalışır?#
CSRF'i anlamanın en kolay yolu, tarayıcının çerezleri nasıl gönderdiğini hatırlamaktır. Bir siteye giriş yaptığınızda sunucu size bir oturum çerezi verir. Bundan sonra o siteye giden her istekte tarayıcı bu çerezi otomatik olarak ekler; isteği kimin, hangi sekmenin, hangi sayfanın tetiklediğine bakmaz. İşte CSRF tam olarak bu "otomatik ekleme" davranışının üstüne kuruludur.
Klasik senaryoyu düşünelim. Diyelim ki bir bankacılık sitesi para transferini şöyle basit bir formla yapıyor olsun:
<form action="https://banka.example/transfer" method="POST">
<input name="alici" value="12345">
<input name="tutar" value="100">
</form>
Saldırgan, tamamen kendi kontrolündeki kotu-site.example adresinde şuna benzer görünmez bir form barındırır ve sayfa açılır açılmaz JavaScript ile otomatik gönderir:
<form id="f" action="https://banka.example/transfer" method="POST">
<input type="hidden" name="alici" value="99999">
<input type="hidden" name="tutar" value="5000">
</form>
<script>document.getElementById('f').submit();</script>
Siz banka sitesine giriş yapmışken bu zararlı sayfayı açarsanız, tarayıcınız banka.example alan adına giden POST isteğine oturum çerezinizi otomatik ekler. Banka sunucusu geçerli bir çerez görür, isteği sizden gelmiş kabul eder ve 99999 numaralı hesaba 5000 lira gönderir. Siz ekranda belki hiçbir şey görmezsiniz. Saldırının en sinsi tarafı budur: bir zafiyet sömürmez, sadece meşru güven mekanizmasını kandırır.
Aynı mantık GET istekleriyle daha da kolay çalışır. Eğer bir işlem sadece bir URL'e gitmekle tetikleniyorsa, saldırganın bir resim etiketi yerleştirmesi bile yeter:
<img src="https://banka.example/transfer?alici=99999&tutar=5000">
Bu yüzden ilk altın kural şudur: durum değiştiren hiçbir işlem GET ile yapılmamalıdır. Para transferi, şifre değiştirme, kayıt silme gibi eylemler yalnızca POST, PUT veya DELETE gibi metotlarla ve korumalı biçimde yapılmalıdır.
CSRF ile XSS Arasındaki Fark#
Bu iki saldırı sürekli birbirine karıştırılır ama tamamen farklı çalışırlar; farkı kavramak, doğru savunmayı seçmek için şarttır.
XSS (Cross-Site Scripting) bir siteye zararlı JavaScript enjekte etmektir. Saldırgan kodu kurbanın tarayıcısında, hedef sitenin kendi bağlamında çalıştırır. Yani saldırgan sizin adınıza sayfayı okuyabilir, çerezleri (httpOnly değilse) çalabilir, ekranı değiştirebilir. XSS güçlüdür çünkü saldırgan kod çalıştırır.
CSRF ise hiçbir kod çalıştırmaz. Saldırgan hedef siteyi okuyamaz, cevabı göremez, çerezinizi ele geçiremez. Yaptığı tek şey tarayıcınıza "kör bir istek" gönderttirmektir. Saldırgan isteği tetikler ama sonucunu asla göremez. Bu yüzden CSRF'e bazen "kör saldırı" da denir.
Aradaki farkı bir tabloyla özetleyelim:
| Özellik | CSRF | XSS |
|---|---|---|
| Saldırganın amacı | Kurbanın oturumuyla sahte istek göndertmek | Kurbanın tarayıcısında kod çalıştırmak |
| Kod çalışır mı? | Hayır, sadece istek tetiklenir | Evet, JavaScript hedef bağlamda çalışır |
| Cevabı görebilir mi? | Hayır (kör) | Evet, sayfa içeriğini okuyabilir |
| Temel savunma | CSRF token, SameSite çerez | Çıktı kaçışlama (escaping), CSP, girdi doğrulama |
| Çerez çalınabilir mi? | Hayır | Evet (httpOnly değilse) |
Kritik bir uyarı: XSS varsa CSRF korumaları çöker. Çünkü saldırgan sitenizde kod çalıştırabiliyorsa, sayfadaki gizli CSRF token'ını da okuyup isteğe ekleyebilir. Bu yüzden CSRF korumasını XSS korumasının yerine değil, onunla birlikte düşünmelisiniz. Uygulama katmanı güvenliğinizi bütünlemek için ağ tarafında bir web uygulama güvenlik duvarı (WAF) da devrede olmalıdır.
Birinci Savunma: CSRF Token#
En yaygın ve en sağlam korumaların başında CSRF token gelir. Mantığı basittir: sunucu, oturuma bağlı, tahmin edilemez, rastgele bir değer üretir ve bunu formun içine gizli bir alan olarak yerleştirir. İstek geldiğinde sunucu, gönderilen token ile oturumda sakladığı token'ı karşılaştırır. Saldırganın sayfası bu değeri bilemez (çünkü hedef siteyi okuyamaz, kör saldırı hatırlayın), dolayısıyla doğru token'ı ekleyemez ve istek reddedilir.
Formda token şöyle görünür:
<form action="/transfer" method="POST">
<input type="hidden" name="csrf_token" value="a7f3c9e1b2d84f60a1c5...">
<input name="alici" value="12345">
<input name="tutar" value="100">
<button type="submit">Gönder</button>
</form>
Sunucu tarafında, örneğin PHP ile, üretim ve doğrulama şuna benzer:
// Token üretme (form gösterilirken)
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
$token = $_SESSION['csrf_token'];
// İstek gelince doğrulama
if (!hash_equals($_SESSION['csrf_token'] ?? '', $_POST['csrf_token'] ?? '')) {
http_response_code(403);
exit('Geçersiz istek: CSRF doğrulaması başarısız.');
}
Burada iki nokta çok önemlidir. Birincisi token kriptografik olarak güçlü bir rastgelelikle üretilmeli; rand() gibi tahmin edilebilir kaynaklar değil, random_bytes gibi güvenli kaynaklar kullanılmalıdır. İkincisi karşılaştırma hash_equals gibi zamanlama saldırısına dayanıklı bir fonksiyonla yapılmalı, doğrudan == ile değil.
Modern SPA (tek sayfa uygulama) mimarilerinde token genellikle bir API isteğinin başlığında taşınır:
curl -X POST https://panel.example/api/transfer \
-H "Authorization: Bearer eyJ..." \
-H "X-CSRF-Token: a7f3c9e1b2d84f60a1c5..." \
-H "Content-Type: application/json" \
-d '{"alici":"12345","tutar":100}'
Not düşelim: eğer kimlik doğrulamanız çerez yerine Authorization: Bearer başlığı ile yapılıyorsa, CSRF riski büyük ölçüde ortadan kalkar. Çünkü tarayıcı bir başlığı diğer sitelere otomatik eklemez; sadece çerezleri otomatik ekler. Modern başsız (headless) mimarilerde bu, tasarımdan gelen doğal bir korumadır.
İkinci Savunma: SameSite Çerez Politikası#
CSRF'in kökeni çerezlerin siteler arası otomatik gönderilmesi olduğuna göre, çözümün bir kısmı da doğrudan çerez seviyesinde uygulanabilir. SameSite özniteliği, bir çerezin başka bir siteden tetiklenen isteklerde gönderilip gönderilmeyeceğini kontrol eder. Üç değeri vardır:
| Değer | Davranış | CSRF'e etkisi |
|---|---|---|
Strict | Çerez yalnızca aynı siteden gelen isteklerde gönderilir | En güçlü koruma, ama harici linkten girişte oturum "kopuk" görünebilir |
Lax | Üst düzey GET gezinmelerinde gönderilir, çapraz POST'ta gönderilmez | İyi denge, modern tarayıcılarda varsayılan |
None | Her durumda gönderilir (mutlaka Secure gerekir) | Koruma yok, sadece gerçekten gerekiyorsa |
Oturum çerezinizi şöyle ayarlamak, çapraz sitelerden gelen POST isteklerinde çerezin gönderilmesini engeller:
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax; Path=/
Nginx tarafında uygulama çereziniz üstünden geçen başlıkları destekleyecek şekilde bir örnek:
# Uygulamanın ürettiği çereze SameSite ve Secure zorlamak için
proxy_cookie_flags ~ secure samesite=lax httponly;
SameSite=Lax bugün çoğu tarayıcıda varsayılan hale geldiği için, hiçbir şey yapmasanız bile bir miktar CSRF koruması kazanırsınız. Ancak buna tek başına güvenmeyin. Eski tarayıcılar bu özniteliği yok sayabilir, bazı senaryolar (örneğin alt alan adları arası akışlar) beklenmedik davranabilir ve SameSite=None gerektiren meşru üçüncü taraf entegrasyonlarınız olabilir. Bu yüzden SameSite'i CSRF token ile birlikte kullanmak en doğru yaklaşımdır; ikisi birbirinin yedeği olur.
Çerezlerinizde bir başka önemli öznitelik HttpOnly'dir. Bu CSRF'i doğrudan engellemez ama olası bir XSS durumunda çerezin JavaScript ile okunmasını engelleyerek zincirleme saldırıları zorlaştırır. Secure ise çerezin yalnızca HTTPS üstünden gitmesini sağlar; sitenizde geçerli bir SSL sertifikası olmadan bu özniteliği anlamlı şekilde kullanamazsınız.
Üçüncü Savunma: Çift Gönderim (Double Submit) Deseni#
Sunucuda oturum başına token saklamak istemediğiniz, tamamen durumsuz (stateless) bir API tasarladığınız durumlar olur. Böyle senaryolar için "double submit cookie" deseni pratik bir alternatiftir. Mantığı şudur: sunucu rastgele bir değeri hem çerez olarak hem de sayfaya gömülü token olarak verir. İstek geldiğinde çerezdeki değer ile başlıktaki/gövdedeki değer karşılaştırılır. Eşitse istek kabul edilir.
Bu neden işe yarar? Çünkü saldırganın sayfası çapraz siteden istek atarken çerezi otomatik gönderebilir ama başlığa yazılacak değeri okuyamaz (çereze JavaScript ile erişmesi aynı-köken politikasıyla engellenir). Dolayısıyla iki değeri eşleştiremez.
# Sunucu yanıtı
Set-Cookie: csrf=Z9x7...; Secure; SameSite=Lax; Path=/
# İstemcinin gönderdiği istek
POST /api/sifre-degistir
Cookie: csrf=Z9x7...
X-CSRF-Token: Z9x7... <- JS bunu çerezden okuyup başlığa koyar
Basit bir doğrulama pseudo-kodu:
def csrf_kontrol(request):
cerez_token = request.cookies.get("csrf")
baslik_token = request.headers.get("X-CSRF-Token")
if not cerez_token or not baslik_token:
return reddet(403)
if not sabit_zamanli_karsilastir(cerez_token, baslik_token):
return reddet(403)
return devam_et()
Double submit deseninin bir zayıf noktası, alt alan adları arasında çerez paylaşımı yapan yapılarda saldırganın bir alt alan adı üstünden çerez enjekte edebilmesidir (cookie tossing). Bu yüzden desenin güçlendirilmiş sürümünde token, sunucu tarafından imzalanır (örneğin HMAC ile). Kısacası tamamen durumsuz kalmak istiyorsanız çift gönderim iyi bir seçenektir, ama imzalı varyantını tercih edin ve mutlaka SameSite ile birleştirin.
Dördüncü Savunma: Kritik İşlemlerde Yeniden Doğrulama#
Bazı işlemler o kadar kritiktir ki, tek bir token'a güvenmek yerine kullanıcıya kimliğini bir kez daha ispatlatmak gerekir. Şifre değiştirme, e-posta adresi değiştirme, para transferi, alan adı transferi, sunucu silme gibi eylemler bu gruba girer. Bu tür işlemlerde en etkili ek katman yeniden doğrulamadır (re-authentication).
Uygulama pratikleri şunlardır. En yaygını, işlemi onaylamadan önce mevcut şifrenin yeniden istenmesidir. CSRF saldırganı kurbanın şifresini bilmediği için, token'ı bir şekilde atlatmış olsa bile bu adımda takılır:
<form action="/hesap/eposta-degistir" method="POST">
<input type="hidden" name="csrf_token" value="...">
<input type="email" name="yeni_eposta">
<input type="password" name="mevcut_sifre" placeholder="Mevcut şifreniz">
<button>Onayla</button>
</form>
Daha güçlü bir katman ise iki faktörlü doğrulama (2FA) kodunun tekrar istenmesidir. Örneğin bir uygulama, "hassas işlem" penceresini 5 dakikayla sınırlayıp, bu süre dolduktan sonra kritik eylemler için TOTP kodunu yeniden ister. Böylece hem CSRF hem de çalınmış oturum senaryolarına karşı ciddi bir engel çıkmış olur.
Bir başka pratik, kritik işlemlerde bant dışı (out-of-band) onaydır: işlemi kaydeder ama e-posta veya SMS ile gönderilen bir bağlantı/kod onaylanana kadar uygulamazsınız. Şifre sıfırlama akışlarında zaten kullandığımız bu desen, para hareketleri için de son derece etkilidir. Yeniden doğrulama kullanıcı deneyimini biraz yavaşlatır, bu yüzden her işleme değil, yalnızca gerçekten geri dönüşü zor eylemlere uygulanmalıdır.
Ek Kontroller ve Pratik Kontrol Listesi#
Yukarıdaki dört ana savunmanın yanında, savunmayı derinleştiren birkaç yardımcı kontrol daha vardır. Bunları tek başına yeterli görmeyin ama katmanlı güvenliğin parçası olarak ekleyin.
Origin ve Referer başlığı kontrolü, gelen isteğin gerçekten sizin sitenizden tetiklenip tetiklenmediğine dair güçlü bir ipucudur. Durum değiştiren isteklerde bu başlıkları beklenen alan adıyla karşılaştırabilirsiniz:
map $http_origin $csrf_ok {
default 0;
"https://panel.example" 1;
"https://clou.tr" 1;
}
server {
location /api/ {
if ($request_method = POST) {
set $blok "$csrf_ok";
}
if ($blok = 0) {
return 403;
}
# ... proxy_pass vb.
}
}
Özel başlık şartı da işe yarar: SPA'nızın her API isteğine X-Requested-With: XMLHttpRequest gibi özel bir başlık eklemesini zorunlu kılabilirsiniz. Basit HTML formlarıyla yapılan çapraz site istekleri bu başlığı ekleyemez, çünkü özel başlık eklemek CORS ön kontrolünü (preflight) tetikler ve tarayıcı bunu izin vermeden göndermez.
Aşağıdaki kontrol listesini uygulamalarınız için bir denetim rehberi olarak kullanabilirsiniz:
| Kontrol | Durum |
|---|---|
| Durum değiştiren tüm işlemler POST/PUT/DELETE mi? | Zorunlu |
Oturum çerezinde SameSite=Lax veya Strict var mı? | Zorunlu |
Çerezlerde HttpOnly ve Secure etkin mi? | Zorunlu |
| Formlarda kriptografik güçte CSRF token var mı? | Zorunlu |
Token karşılaştırması sabit zamanlı mı (hash_equals)? | Zorunlu |
| Kritik işlemlerde yeniden doğrulama var mı? | Önerilir |
Origin/Referer doğrulaması yapılıyor mu? | Önerilir |
| Önünde bir WAF katmanı var mı? | Önerilir |
Bu kontrolleri düzenli olarak gözden geçirmek, bir güvenlik olayından sonra değil öncesinde yapmanız gereken bir alışkanlıktır. WordPress gibi hazır platformlarda çekirdek zaten "nonce" adı verilen bir CSRF token mekanizması kullanır; ama eklentiler bu mekanizmayı doğru uygulamayabilir. Bu konuyu ayrıntılı işlediğimiz WordPress güvenliği rehberimize göz atmanızı öneririm.
Sıkça Sorulan Sorular#
CSRF saldırısı gerçekten hâlâ tehlikeli mi, yoksa eski bir sorun mu?#
Hâlâ gerçek bir tehdittir. Modern tarayıcıların SameSite=Lax çerezleri varsayılan yapması riski önemli ölçüde azalttı, ancak tamamen ortadan kaldırmadı. Eski tarayıcılar, yanlış yapılandırılmış çerezler, SameSite=None gerektiren üçüncü taraf entegrasyonlar ve GET ile durum değiştiren kötü tasarlanmış uç noktalar CSRF'i canlı tutuyor. OWASP güvenlik listelerinde yıllardır yer alması boşuna değil; katmanlı savunmayı ihmal etmemek gerekir.
CSRF token her istekte değişmeli mi, yoksa oturum boyunca sabit mi kalabilir?#
Her ikisi de geçerli tasarımlardır. Oturum başına sabit bir token, uygulanması en kolay ve çoğu senaryoda yeterince güvenli olan yöntemdir. İstek başına yenilenen (per-request) token daha yüksek güvenlik sağlar çünkü çalınmış bir token'ın kullanım penceresi çok kısalır, ancak "geri" düğmesi ve çoklu sekme gibi durumlarda kullanıcı deneyimini zorlaştırabilir. Çoğu uygulama için oturum başına sabit, güçlü rastgelelikle üretilmiş bir token pratik ve güvenli bir dengedir.
SameSite çerez kullanıyorsam CSRF token'a hâlâ gerek var mı?#
Evet, ikisini birlikte kullanmanızı öneririm. SameSite güçlü ama tek başına yeterli değildir; eski tarayıcı desteği, alt alan adı senaryoları ve SameSite=None gerektiren meşru akışlar boşluklar bırakabilir. CSRF token ise tarayıcı davranışından bağımsız, uygulama seviyesinde çalışan bir savunmadır. İki katmanı üst üste koyduğunuzda biri devre dışı kalsa bile diğeri devrede olur; güvenlikte bu "derinlemesine savunma" yaklaşımı esastır.
JWT ile Authorization başlığı kullanıyorsam CSRF'e karşı korunmuş olur muyum?#
Büyük ölçüde evet. CSRF'in temeli tarayıcının çerezleri otomatik göndermesidir; oysa Authorization: Bearer gibi bir başlığı tarayıcı çapraz sitelere otomatik eklemez, onu her seferinde sizin JavaScript kodunuz eklemek zorundadır. Token'ı çerez yerine localStorage veya bellekte tutup başlıkla gönderiyorsanız CSRF riski doğal olarak ortadan kalkar. Ancak bu durumda XSS riskine daha dikkat etmeniz gerekir, çünkü token'ı çalabilecek zafiyet artık script enjeksiyonudur.
CSRF saldırısına uğradığımı nasıl anlarım?#
Genellikle doğrudan anlamak zordur, çünkü saldırı kullanıcının kendi oturumuyla "meşru" görünen istekler üretir. Yine de belirtiler vardır: kullanıcıların yapmadıklarını iddia ettiği hesap değişiklikleri, beklenmedik e-posta/şifre güncellemeleri, sunucu loglarında Referer veya Origin başlığı beklenmedik alan adlarını gösteren durum değiştiren istekler bunlara işaret eder. Bu yüzden 403 ile reddedilen CSRF doğrulamalarını loglamak ve olağan dışı desenleri bir WAF ile izlemek erken uyarı sağlar. Şüpheli bir durum tespit ederseniz oturumları geçersiz kılıp kullanıcıları yeniden giriş yapmaya zorlamak ilk adım olmalıdır.
Kapanış#
CSRF, karmaşık bir zafiyet sömürmeden yalnızca güven ilişkisini kötüye kullandığı için sinsi ama bir o kadar da önlenebilir bir saldırıdır. Doğru metotları kullanmak, oturum çerezine SameSite ve HttpOnly eklemek, kriptografik güçte CSRF token doğrulamak ve kritik işlemlerde yeniden doğrulama istemek; bu dört adım birlikte uygulandığında saldırı yüzeyini son derece daraltır. Unutmayın, güvenlik tek bir sihirli çözüm değil, katman katman kurulan bir bütündür ve CSRF korumasını XSS korumasıyla el ele yürütmek gerekir.
Clou.TR olarak bu katmanları sizin için hazır kurgulamaya çalışıyoruz. SSL sertifikası ile çerezlerinizi Secure işaretiyle güvenle taşıyabilir, uygulamanızın önüne bir web uygulama güvenlik duvarı (WAF) koyarak kötü niyetli istekleri kaynağında filtreleyebilir ve DDoS koruma ile altyapınızı ayakta tutabilirsiniz. Güvenlik odaklı bir altyapı için hosting paketlerimizi ya da tam kontrol isteyen ekipler için sanal ve fiziksel sunucu çözümlerimizi inceleyebilir, yönetim yükünü bize bırakmak isterseniz sunucu yönetimi hizmetimizle projelerinizi uzman ellere teslim edebilirsiniz.