CSRF Nedir? Örnekle ve Korunma
CSRF (Cross-Site Request Forgery, siteler arası istek sahteciliği), saldırganın hazırladığı bir sayfanın, oturumu açık kullanıcının tarayıcısına onun haberi olmadan başka bir siteye işlem isteği gönderttiği web açığıdır. Tarayıcı oturum çerezini isteğe kendisi ekler, sunucu da isteği kullanıcının kendi isteği sanır. Şifre, e-posta ya da adres değişikliği gibi durum değiştiren işlemler hedef olur.
Bu açıkta saldırgan senin parolanı bilmez, oturumunu da çalmaz. Onun yerine tarayıcını kullanır. Sen bir sekmede bankana girmişken başka bir sekmede zararsız görünen bir sayfa açarsın. O sayfa, senin adına bankaya bir istek yollar. Tarayıcı oturum çerezini bu isteğe kendiliğinden ekler. Sunucu farkı göremezse işlem tamamlanır.
Bu yazı CSRF'i kavram, gerçek vaka ve savunma üzerinden anlatıyor. Çalışan saldırı kodu yok. Zafiyetli deseni ve düzeltilmiş kodu göreceksin.
CSRF nedir?
CSRF, sunucunun "bu istek gerçekten kullanıcının kendi isteği mi?" sorusunu sormamasından doğan bir web açığıdır. Türkçede siteler arası istek sahteciliği denir. MITRE'nin zayıflık kataloğunda CWE-352 numarasını taşır.
PortSwigger Web Security Academy, açığın oluşması için üç koşulun aynı anda bulunması gerektiğini söyler:
- Saldırganın tetiklemek isteyeceği bir işlem olmalı. Şifre, e-posta ya da teslimat adresi değiştirmek gibi.
- Oturum yalnız çerezle tanınmalı. Sunucu başka bir kanıt istememeli.
- İstekteki parametrelerin hepsi tahmin edilebilir olmalı. Saldırganın bilmediği gizli bir değer bulunmamalı.
Üçünden biri eksikse CSRF çalışmaz. Savunmanın mantığı da buradan çıkar: ya çerezin dış siteden gitmesini engellersin ya da isteğe saldırganın bilemeyeceği bir değer eklersin.
CSRF saldırısı adım adım nasıl işler?
Kavramı kurgusal bir örnekle düşünelim. Altay Kargo'nun müşteri portalında teslimat adresini değiştiren bir form var. Form yalnız yeni adresi gönderiyor, başka bir doğrulama yok.
- Ayşe portala giriş yapar. Tarayıcı oturum çerezini saklar.
- Ayşe aynı gün bir forumda paylaşılan bağlantıya tıklar.
- Açılan sayfada görünmeyen bir form vardır. Form, portalın adres değiştirme adresini hedefler.
- Sayfa formu kendiliğinden gönderir. Tarayıcı Ayşe'nin portal çerezini isteğe ekler.
- Portal isteği Ayşe'den gelmiş sayar ve adresi değiştirir.
- Bir sonraki kargo saldırganın seçtiği adrese gider.
Ayşe'nin parolası hiç sızmadı. Saldırgan portalın yanıtını da göremedi. CSRF tek yönlüdür: istek gider, sonuç saldırgana dönmez. Bu yüzden hedef, veri okumak değil durum değiştirmektir.
Bir türü daha var: giriş CSRF'i. Saldırgan kurbanı kendi hesabına giriş yaptırır. Kurban farkında olmadan saldırganın hesabına kart ya da arama geçmişi ekler. OWASP, giriş formlarının da korunmasını bu yüzden ister.
Gerçek dünyada hangi CSRF vakaları yaşandı?
En çok anılan vaka 2008'de Princeton Üniversitesi'nden geldi. Bill Zeller ve Edward Felten dört büyük sitede CSRF açığı yayımladı. ING Direct'te açık, kurbanın hesabından para aktarmaya kadar gidiyordu. Araştırmacılar bunu bir finans kurumunda para transferine izin veren ilk CSRF olarak tanımladı.
Aynı çalışmada YouTube'da kurban adına arkadaş ekleme ve mesaj gönderme mümkündü. MetaFilter'da sahte istekle e-posta adresi değiştirilip hesap ele geçirilebiliyordu. New York Times'ta kullanıcıların e-posta adresi açığa çıkıyordu. Dört sitede de açıklar kapatıldı.
Yine 2008'de Symantec, Meksika'da ilk gerçek "drive-by pharming" saldırısını raporladı. Kurbanlara giden e-postadaki bir görsel etiketi, evdeki modemin yönetim paneline istek atıyordu. Varsayılan parolası değişmemiş modemlerin DNS ayarı değişiyor, kullanıcı sahte banka sitesine yönleniyordu. Bu da bir CSRF'ti, hedefi web sitesi değil modemdi.
SameSite çerezi CSRF'i bitirdi mi?
Hayır, SameSite riski büyük ölçüde azalttı ama tek başına yeterli değildir. OWASP bu özniteliği derinlemesine savunma katmanı olarak görür, asıl savunma olarak değil.
SameSite, çerezin siteler arası isteklerde gidip gitmeyeceğini belirler:
| Değer | Siteler arası istekte çerez gider mi? | Not |
|---|---|---|
| Strict | Hiç gitmez. | En sıkı seçenek, dış bağlantıdan gelince oturum görünmez. |
| Lax | Yalnız üst düzey gezinmede ve GET gibi güvenli yöntemde gider. | Arka planda giden POST isteğine eklenmez. |
| None | Her istekte gider. | Secure özniteliği de zorunludur. |
Chrome, SameSite yazılmamış çerezleri Lax saymaya Chrome 80 ile başladı. Chromium'un kayıtlarına göre dağıtım 17 Şubat 2020 haftasında başladı. 3 Nisan 2020'de salgın nedeniyle geri çekildi. 14 Temmuz 2020'de Chrome 84 ile yeniden açıldı ve 11 Ağustos 2020'de tüm kullanıcılara ulaştı. MDN'nin uyumluluk verisine göre Edge 86'dan beri aynı davranışı izler.
Firefox ve Safari bu varsayılanı açmadı. Firefox'ta yalnız bir ayar bayrağıyla açılabiliyor. Yani SameSite yazmazsan kullanıcılarının bir kısmı hiç korunmaz.
Lax'ın başka sınırları da var:
- Lax, güvenli yöntemli üst düzey gezinmede çerezi gönderir. GET ile durum değiştiren uygulama yine açıktır.
- Chrome, varsayılan Lax uygulanan çerezlerde iki dakikalık bir istisna tanır. Yeni oluşan çerez, üst düzey POST isteğinde de gider.
- SameSite, kökene değil kayıtlı alan adına bakar. Aynı ana alan altındaki güvensiz bir alt alan "aynı site" sayılır.
CSRF token nedir, nasıl çalışır?
CSRF token, sunucunun her oturum için ürettiği, tahmin edilemeyen ve isteğe ayrıca eklenmesi gereken gizli bir değerdir. Saldırganın sayfası bu değeri bilemez. Bu yüzden sahte istek doğrulamadan geçemez.
OWASP Cheat Sheet Series iki deseni öne çıkarır. Senkronize token deseninde jeton sunucuda oturumla birlikte saklanır. Form gizli alanda ya da istek bir başlıkta bu jetonu taşır. Sunucu gelen değeri saklananla karşılaştırır. İmzalı çift gönderim deseninde jeton bir çerezde de durur. Ama değer, oturuma bağlı bir gizli anahtarla HMAC imzalanır. İmzasız "saf" çift gönderimi OWASP önermez, çünkü alt alandan çerez yazabilen biri onu atlatabilir.
Önce zafiyetli desen. Sunucu yalnız çereze bakıyor:
# ZAFİYETLİ: oturum çerezi yeterli sayılıyor
@app.route("/hesap/adres", methods=["POST"])
def adres_guncelle():
kullanici = oturumdaki_kullanici() # yalnız çerezden geliyor
kullanici.adres = istek.form["adres"] # dış siteden gelen form da buraya ulaşır
kaydet(kullanici)
return "Adres güncellendi"
Düzeltilmiş hâli jetonu üretir ve her durum değiştiren istekte doğrular:
import hmac, secrets
def jeton_uret(oturum):
if "csrf" not in oturum:
oturum["csrf"] = secrets.token_urlsafe(32) # kriptografik rastgele
return oturum["csrf"] # formda gizli alana ya da X-CSRF-Token başlığına konur
def jeton_dogrula(oturum, gelen):
beklenen = oturum.get("csrf")
return bool(beklenen and gelen and hmac.compare_digest(beklenen, gelen))
@app.route("/hesap/adres", methods=["POST"])
def adres_guncelle():
gelen = istek.form.get("csrf") or istek.headers.get("X-CSRF-Token")
if not jeton_dogrula(oturum, gelen):
return "Geçersiz istek", 403 # jeton yoksa da reddet
...
Üç ayrıntı sık atlanır. Jeton yoksa isteği geçirme, reddet. Jetonu URL'ye ya da GET parametresine koyma, tarayıcı geçmişine ve kayıtlara düşer. Jetonu oturuma bağla, herhangi bir geçerli jetonu kabul etme. Django, Laravel ve ASP.NET Core bu denetimi hazır sunar. Önce çatının kendi korumasını aç.
Origin, Referer ve Fetch Metadata ne işe yarar?
Bu başlıklar, isteğin hangi siteden başladığını tarayıcının kendi ağzından söyler ve sayfa kodu onları değiştiremez. Sunucu, durum değiştiren istekte bu bilgiye bakarak dış siteden geleni reddeder.
Sec-Fetch-Site başlığı dört değer alır: same-origin, same-site, cross-site ve none. MDN'nin verisine göre Chrome 76, Firefox 90 ve Safari 16.4'ten beri gönderilir. OWASP, eski tarayıcılar için Origin başlığına geri düşmeyi zorunlu sayar. Go dili de 1.25 sürümünde bu mantığı http.CrossOriginProtection adıyla standart kütüphaneye ekledi.
GUVENLI = {"GET", "HEAD", "OPTIONS"}
IZINLI_KOKEN = "https://portal.altaykargo.example"
def kaynak_denetimi(istek):
if istek.method in GUVENLI:
return True # güvenli yöntem durum değiştirmemeli
site = istek.headers.get("Sec-Fetch-Site")
if site is not None:
return site in ("same-origin", "none")
return istek.headers.get("Origin") == IZINLI_KOKEN # eski tarayıcı yedeği
Oturum çerezini de sıkı kur. __Host- öneki çerezi tek bir köke bağlar ve alt alandan üzerine yazılmasını zorlaştırır:
Set-Cookie: __Host-oturum=<rastgele>; Path=/; Secure; HttpOnly; SameSite=Lax
CSRF ile XSS ve CORS arasındaki fark ne?
CSRF tarayıcıya senin adına istek attırır, XSS senin sayfanda saldırganın betiğini çalıştırır, CORS ise bir saldırı değil tarayıcının kökenler arası okuma kuralıdır. Üçü sık karıştırılır:
| Konu | CSRF | XSS | CORS |
|---|---|---|---|
| Nedir? | Kullanıcı adına sahte istek. | Sayfaya enjekte edilen betik. | Kökenler arası okuma izni mekanizması. |
| Saldırgan yanıtı görür mü? | Hayır, tek yönlü. | Evet, sayfanın içinden okur. | Bir saldırı değil, sunucu ayarıdır. |
| Kök neden | Sunucu isteğin kaynağını sormaz. | Girdi kodlanmadan sayfaya basılır. | Yanlış ayar, okumayı fazla açar. |
| Ana savunma | Token, Origin ve SameSite. | Çıktı kodlama ve CSP. | Dar köken listesi, kimlik bilgisiyle joker yok. |
İki kritik not var. OWASP açıkça uyarır: XSS bütün CSRF savunmalarını aşabilir. Sayfanda betik çalıştıran biri jetonu da okur. XSS'i ayrıca XSS nedir yazısında ele aldık. İkincisi, CORS bir CSRF savunması değildir. Basit form istekleri ön kontrol (preflight) yapılmadan gider. CORS yalnız yanıtın okunmasını engeller, isteğin sunucuya ulaşmasını değil. Konuyu aynı köken kuralı ve CORS dersinde uygulamalı görürsün.
OWASP Top 10 2025'te CSRF nerede duruyor?
OWASP Top 10:2025 listesinde CSRF ayrı bir madde değildir. A01:2025 Bozuk Erişim Denetimi kategorisinin içinde, öne çıkan zayıflıklar arasında CWE-352 olarak geçer. Bu kategori 2025 verisinde test edilen uygulamaların tamamında bir biçimde görüldü. Aynı kategoriye SSRF de katıldı.
Zayıflık sıralamalarında CSRF hâlâ üst sıralarda. MITRE'nin 15 Aralık 2025'te yayımladığı CWE Top 25 listesinde CWE-352 üçüncü sırada. Önünde yalnız XSS ve SQL enjeksiyonu var. Chrome'un varsayılan Lax davranışı riski azalttı. Ama eski kod, GET ile durum değiştiren uçlar ve gömülü cihaz panelleri açığı yaşatıyor.
Geliştirici olarak hangi kontrolleri yapmalısın?
Kendi uygulamanı gözden geçirirken şu sırayı izle:
- Durum değiştiren her işlemi listele. GET ile durum değiştiren uç kalmasın.
- Çatının yerleşik CSRF korumasını aç. Kapatılmış rotaları tek tek gerekçelendir.
- Jetonu oturuma bağla, yokluğunda isteği reddet.
Sec-Fetch-SiteveOrigindenetimini ikinci katman olarak ekle.- Oturum çerezine
SameSite,SecureveHttpOnlyözniteliklerini açıkça yaz. - Parola, e-posta ve IBAN değişikliğinde eski parolayı ya da ikinci doğrulama adımını yeniden iste.
- XSS açığını kapat, yoksa diğer adımların hepsi boşa gider.
Türkiye açısından bir not ekleyelim. Adres, e-posta ya da IBAN değiştiren bir CSRF, kişisel verinin yetkisiz işlenmesi demektir. Bu durum KVKK kapsamında ihlal bildirimi gündeme getirebilir. Süreci KVKK veri ihlali bildirimi yazısında anlattık. Test tarafında ise kural net: yalnız kendi uygulamanda, lab ortamında ya da bug bounty kapsamında dene. İzinsiz deneme TCK 243-245 kapsamında suçtur.
CSRF'i uygulamalı nasıl öğrenirsin?
En hızlı yol, açığı bir kayıt ve kod üzerinde görmek. Web Uygulama Güvenliği yolunun erişim denetimi modülünde Kendiliğinden Değişen Adres labı seni bekliyor. Bir müşterinin adresi parolası çalınmadan değişmiş. İstek kaydından, çerez ayarından ve rota kodundan nedeni kanıtlarsın.
Kod incelemesi tarafını görmek istersen Tek Tıkla Para İadesi labına geç. Bir PR'daki eksik jeton denetimini satır satır bulursun. Çerez özniteliklerini terminalde denetlemek için Saha bölümündeki "Açık Çerez" görevine bak. Sunucu tarafındaki kardeş açığı merak ediyorsan sıradaki okuma SSRF nedir olsun.
Kaynaklar
Sık sorulan sorular
SameSite=Lax varsa ayrıca CSRF token gerekir mi?
Çoğu durumda evet. Firefox ve Safari, SameSite yazılmamış çerezleri varsayılan olarak Lax saymaz. Lax, üst düzey GET gezinmesinde çerezi yine gönderir ve aynı ana alandaki alt alanları aynı site sayar. OWASP bu yüzden SameSite'ı ikinci katman olarak görür. Asıl savunma oturuma bağlı jeton ve köken denetimidir.
JWT kullanan bir API'de CSRF olur mu?
Jetonun nerede durduğuna bağlıdır. JWT, Authorization başlığında taşınıyorsa tarayıcı onu dış siteden gelen isteğe kendiliğinden eklemez, bu yüzden klasik CSRF oluşmaz. Ama JWT bir çerezde saklanıyorsa durum oturum çereziyle aynıdır. O zaman jeton denetimi, köken kontrolü ve SameSite yine gerekir. Çerezde tutulan her kimlik bilgisi CSRF riski taşır.
GET isteğiyle CSRF yapılabilir mi?
Evet, uygulama GET isteğiyle durum değiştiriyorsa yapılabilir. Bir görsel etiketi ya da bağlantı bile isteği tetikler ve Lax ayarı üst düzey GET gezinmesinde çerezi gönderir. 2008'deki modem saldırısı tam olarak buydu. Çözüm basittir: GET, HEAD ve OPTIONS hiçbir veriyi değiştirmemeli, değişiklik yalnız POST, PUT, PATCH ya da DELETE ile yapılmalı.
CSRF token her istekte değişmeli mi?
Şart değil. OWASP, jetonun oturum başına bir kez ya da her istekte üretilebileceğini söyler. İstek başına jeton biraz daha güçlüdür ama geri tuşu ve birden çok sekmede kullanıcıyı hataya düşürebilir. Çoğu uygulama için oturum başına üretilen, oturuma bağlı ve tahmin edilemez bir jeton yeterlidir. Girişten sonra jetonu yenilemek iyi bir alışkanlıktır.
CSRF açığını yasal olarak nerede test edebilirim?
Yalnız kendi geliştirdiğin uygulamada, izinli lab ortamlarında ya da bug bounty programının yazılı kapsamında test edebilirsin. İzinsiz bir sitede deneme yapmak TCK 243-245 kapsamında suç sayılır. Başlangıç için PortSwigger Web Security Academy'nin ücretsiz CSRF labları ve AltaySec Akademi'deki vaka labları güvenli ve yasal seçeneklerdir.
- csrf nedir
- cross site request forgery
- csrf token
- samesite çerez
- web güvenliği