İçeriğe atla
0 XP 0
12 dkderinleşmeyle 13 dk55 XP6 etkinlik

Aynı köken kuralı ve CORS

Bu derste
  • Aynı köken kuralını, CORS'un kontrollü kapısını, ön kontrol isteğini ve CORS ile CSRF ayrımını tespit ve savunmasıyla öğrenirsin.
  • Tarayıcı açık onlarca sekmeyi birbirinden yalıtır. Bu yalıtımın adı aynı köken kuralıdır.
  • CORS o duvarda kontrollü bir kapı açar. Kapıyı kim açarsa sorumluluk onundur.
  • Aynı köken kuralı CSRF'i durdurmaz. İkisini ayırmayı öğreneceksin.

Puan bütün etkinlikleri cevaplayınca hesaba yazılır. Bu dersi tamamlayınca +55 XP alacaksın. Giriş yapmadan ilerlersen puan yalnız bu tarayıcıda durur.

Aynı anda onlarca sekme açıksın. Birinde bankan, birinde e-postan, birinde tanımadığın bir site. Tanımadığın sitedeki JavaScript, banka sayfasının içeriğini okuyabilseydi bakiyeni ve işlemlerini alırdı.

Tarayıcı buna aynı köken kuralıyla (same-origin policy) engel olur ve sekmeleri birbirinden yalıtır. Bu kural, web güvenliğinin temel taşıdır. Ama modern uygulamalar ön yüzü bir alan adında, API'yi başka bir alan adında tutar ve bu duvarı kontrollü biçimde aşmak ister.

CORS bu ihtiyaç için var. Bu derste önce kökenin tam tanımını, sonra CORS'un nasıl kapı açtığını, en sonda çok karıştırılan CORS ile CSRF ayrımını göreceksin.

Köken nedir

Köken üç parçadan oluşur: şema, alan adı ve port. Standart alan adı yerine ana makine adı (host) der; ikisi burada aynı şeyi anlatır. Üçü birden eşleşirse iki adres aynı köken sayılır. Biri bile farklıysa ayrı kökendir.

Yol, sorgu dizesi ve çapa (fragment) kökene dahil değildir. Örnekle netleşir. Sayfan https://hedef.example adresinde açık olsun.

https://hedef.example/panel aynı kökendir, yalnız yol değişti. Ama http://hedef.example ayrıdır, çünkü şema http oldu. https://api.hedef.example da ayrıdır, çünkü alan adı farklı. https://hedef.example:8443 de ayrıdır, çünkü port değişti.

Portta ince bir nokta var. Adreste port yazmazsan tarayıcı varsayılanı kullanır: http için 80, https için 443. Bu yüzden https://hedef.example ile https://hedef.example:443 aynı kökendir; ikinci adreste port açıkça yazılsa da değeri varsayılanla aynıdır. Ama https://hedef.example:8443 farklı bir kökendir.

Alt alan adı da ayrı kökendir. panel.hedef.example ile hedef.example, aynı şirkete ait olsa bile tarayıcı gözünde iki ayrı güven alanıdır. Bu yalıtım bilinçlidir: bir alt alan adı ele geçirilse bile ötekinin verisine erişemesin diye.

Kural okumayı keser, yazmayı ya da gömmeyi çoğu zaman kesmez. Başka kökene istek gidebilir: form gönderilir, resim ve betik yüklenir, bağlantıya gidilir. Sunucu isteği alır ve işler. Fark, yanıtın ne olduğundadır.

Basit bir isteği tarayıcı gönderir, yanıtı da alır, ama sayfadaki koda vermez ve konsola bir CORS hatası yazar. Bu ayrımı aklında tut: aynı köken kuralı isteğin gitmesini değil, yanıtın okunmasını engeller. CSRF'in yalnız CORS ile kapanmamasının nedeni de budur; bunu birazdan göreceksin.

Etkinlik 1 / 6

Sayfa https://hedef.example adresinde açık. Hangi adres aynı köken sayılır?

Önce bir seçeneğe, sonra aşağıdaki uygun kutuya bas. İstersen seçeneği kutuya sürükleyip bırak. Yerini değiştirmek için aynı işlemi tekrarla.

0/5 seçenek yerleşti.

CORS: duvarda kontrollü kapı

Ön yüz ile API ayrı kökenlerde durunca sorun çıkar. panel.hedef.example adresindeki JavaScript, api.hedef.example adresine istek atar ve yanıtı okumak ister. Aynı köken kuralı bunu engeller. CORS (Cross-Origin Resource Sharing, çapraz köken kaynak paylaşımı) tam burada devreye girer ve sunucuya kontrollü bir kapı açma yetkisi verir.

Anahtar şudur: kararı tarayıcı uygular, ama izni sunucu verir. Sunucu yanıtına hangi kökene güvendiğini bir başlıkla yazar; bu başlık Access-Control-Allow-Origin'dir. Tarayıcı bu değeri isteğin kökeniyle karşılaştırır. Uyarsa yanıtı sayfadaki koda teslim eder, uymazsa saklar.

İstekler ikiye ayrılır. Basit istek doğrudan gider. Bir isteğin basit sayılması için üç koşulun birden sağlanması gerekir.

Metot GET, HEAD ya da POST olmalıdır. Yalnız güvenli listedeki başlıkları taşımalıdır. POST ise gövde türü şu üçünden biri olmalıdır: application/x-www-form-urlencoded, multipart/form-data ya da text/plain.

Bir HTML formunun gönderdiği istek çoğu zaman bu tanıma girer. Bu koşullardan biri bozulunca istek artık basit değildir. Örneğin Authorization başlığı ya da application/json gövde taşıyorsa, tarayıcı asıl isteği yollamadan önce izin sorar. Buna ön kontrol (preflight) denir.

Ön kontrol, OPTIONS metoduyla yollanan ayrı bir istektir. Tarayıcı bu isteğe Access-Control-Request-Method başlığında asıl metodu, Access-Control-Request-Headers başlığında da asıl başlıkları yazar. Sunucu, izin verdiği metotları ve başlıkları yanıtında bildirir. Access-Control-Max-Age başlığı da bu iznin ne kadar önbelleğe alınacağını söyler; tarayıcının kendi üst sınırı vardır ve büyük değerleri bu sınıra indirir.

İzin uyarsa tarayıcı asıl isteği yollar. Tarayıcının yolladığı ön kontrolü curl ile elle de yollayabilirsin. Aşağıda böyle bir isteği ve sunucunun yanıtını görüyorsun.

$ curl -i -X OPTIONS https://api.hedef.example/kaynak \
    -H "Origin: https://panel.hedef.example" \
    -H "Access-Control-Request-Method: DELETE" \
    -H "Access-Control-Request-Headers: Authorization"
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://panel.hedef.example
Access-Control-Allow-Methods: GET, POST, DELETE
Access-Control-Allow-Headers: Authorization
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 600
Vary: Origin
curl ile yollanan OPTIONS ön kontrolü ve sunucunun izin yanıtı.

Yanıtı satır satır oku. Sunucu yalnız panel.hedef.example kökenine güvendiğini yazdı. Kabul ettiği metotlar arasında DELETE de var, yani asıl istek gidebilir. Authorization başlığını da onayladı.

Access-Control-Allow-Credentials: true, çerez ya da kimlik başlığı taşıyan isteklere yanıtın okunabileceğini söyler. Son satırdaki Vary: Origin önemlidir: yanıt gelen kökene göre değiştiği için, bir ara önbellek yanlış kökene ait yanıtı başka bir kökene sunmasın diye konur. 600 saniyelik Max-Age ise tarayıcının bu onayı on dakika saklamasını söyler, böylece her istekte OPTIONS yinelenmez.

Etkinlik 2 / 6

Tarayıcı hangi durumda OPTIONS ön kontrol isteği yollar?

CORS'u yanlış kurmak

CORS'un tehlikesi, kapıyı fazla açmakta yatar. En kaba hata Access-Control-Allow-Origin başlığına yıldız yazmaktır; bu, yanıtı her kökene okutur. Herkese açık ve gizli veri taşımayan bir API için kabul edilebilir, ama kullanıcı verisi dönen bir uçta ağır bir sızıntıdır.

Daha ince hata, gelen Origin başlığını körü körüne yanıta yankılamaktır. Uygulama her isteğin kökenini alıp aynısını Access-Control-Allow-Origin olarak geri yazarsa, her siteye güvenmiş olur. Üstelik kimlik bilgisiyle birlikte çalışan bir yankı, herhangi bir sitenin kullanıcının oturumuyla API yanıtlarını okumasına yol açar.

Kimlik bilgisi işin içine girince kurallar sertleşir. Çerez ya da Authorization başlığı taşıyan bir istekte Access-Control-Allow-Credentials değeri true ise, köken alanına yıldız yazmak geçersizdir; tarayıcı yanıtı düşürür. Aynı kural Allow-Methods ve Allow-Headers için de geçerlidir: kimlikli isteklerde yıldız yerine değerleri tek tek yazarsın. Doğru yaklaşım kökeni bir izin listesinden doğrulamaktır.

Gelen Origin değerini listede ararsın; varsa aynısını yanıta yazar ve Vary: Origin eklersin, yoksa CORS başlığı hiç koymazsın. Karşılaştırmayı parça arama ile yapma. hedef.example dizisini içeren bir kontrol, hedef.example.baska.example gibi bir kökeni de kabul eder.

Etkinlik 3 / 6

Bu işlev gelen kökene izin veriyor. Hangi satır, listede olmayan bir kökenin de kabul edilmesine yol açar?

Zafiyetli satıra tıkla.

Güvenli bir politikayı küçük parçalardan kur. Kökenleri tam adlarıyla bir kümede tut ve gelen değeri yalnız birebir eşleşirse yanıta yaz. Her CORS yanıtına Vary: Origin ekle. Ön kontrolde yalnız gerçekten kullandığın metotları ve başlıkları listele.

Kimlik bilgisi gerekmeyen uçta Access-Control-Allow-Credentials hiç yazma. Okuma kapısını daraltmanın yazma tarafını korumadığını da unutma: oturum çerezinin SameSite değeri ayrı bir karardır. Şimdi bu parçaları kendi cümlelerinle bir politikaya dönüştür.

Etkinlik 4 / 6

Bir API ucu için güvenli CORS politikasını üç cümleyle yaz. Hangi başlıkları hangi değerlerle vereceğini belirt.

CORS ile CSRF'i karıştırma

Şimdi en çok karışan ayrımı netleştir. CORS okumayla ilgilidir: başka kökenin yanıtını okumaya çalışırsın. CSRF (Cross-Site Request Forgery, siteler arası istek sahteciliği) ise yazmayla ilgilidir: saldırgan, kurbanın oturumuyla bir işlemi tetikler ama yanıtı okumaya hiç ihtiyaç duymaz.

MITRE'nin 2025 listesinde CSRF (CWE-352) üçüncü sırada durur. Örnekle gör. Kurban bankaya girmiş, sekmesi açık.

Saldırganın sayfasında gizli bir form durur ve sayfa açılır açılmaz kendiliğinden gönderilir. Tarayıcı bankanın çerezini bu isteğe ekler, sunucu isteği yetkili sayar ve para transferi gerçekleşir. Saldırgan yanıtı hiç görmez; işlemin olması ona yeter.

Bu yüzden CORS ayarını sıkılaştırmak CSRF'i kapatmaz. Aynı köken kuralı yanıtı gizler, ama isteğin gitmesini ve işlenmesini engellemez. Savunma iki ayak üstünde durur. Birinci ayak SameSite çerezidir: çereze SameSite=Lax ya da Strict yazarsın, böylece çerez yabancı siteden tetiklenen isteklere eklenmez ve saldırganın formu kimliksiz kalır.

İkinci ayak istek jetonudur: durum değiştiren her forma sunucunun ürettiği, tahmin edilemez bir jeton koyarsın (senkronizasyon jetonu). Saldırgan bu jetonu bilemez, çünkü yanıtı okuyamaz. OWASP SameSite'ı tek başına yeterli saymaz, derinlik savunması olarak jetonla birlikte önerir.

Yardımcı katmanlar da var. Modern tarayıcılar isteğe Sec-Fetch-Site başlığını ekler; bu başlık isteğin aynı kökenden, aynı siteden ya da çapraz siteden geldiğini söyler. Sunucu, çapraz siteden gelen ve durum değiştiren istekleri bununla reddedebilir. Durum değiştiren işlemi de asla GET ile yapma, çünkü GET bir resim etiketiyle kolayca tetiklenir.

Bir incelik daha var. SameSite'taki site kavramı kökenden geniştir: panel.hedef.example ile api.hedef.example iki ayrı kökendir, ama aynı siteye aittir. Bu yüzden kardeş alt alan adlarından gelen istekler SameSite engeline takılmaz. Tam denetleyemediğin bir alt alan adın varsa, istek jetonu orada da korur.

Derinleş:SameSite varsayılanı nasıl değişti

SameSite bayrağı 2016'da tarayıcılara gelmeye başladı, ama uzun süre isteğe bağlı kaldı. Bir çerezde bu bayrak yoksa, çerez her çapraz istekte gönderiliyordu; yani varsayılan davranış CSRF'e açıktı. 2020'de Chrome bunu tersine çevirdi ve Chromium tabanlı tarayıcılar onu izledi: bayrağı yazılmamış çerez artık Lax kabul ediliyor. Her tarayıcı bu varsayılanı benimsemedi.

Değişikliğin bir inceliği de var. Varsayılan olarak uygulanan Lax, bayrağı yazılmamış bir çerezi, yazıldıktan sonraki ilk iki dakika içinde gelen üst düzey POST isteklerine yine de ekler. Bu istisna, giriş akışlarını bozmamak için kondu. Bu yüzden varsayılana güvenme.

SameSite değerini her çerezde açıkça yaz ve kritik işlemleri ayrıca istek jetonuyla koru. Güvenliğini tarayıcının o anki varsayılanına değil, kendi açık ayarına dayandır.

Etkinlik 5 / 6

Doğru mu, yanlış mı?

CSRF saldırısının başarılı olması için tarayıcının sunucudan dönen yanıtı okuması gerekmez.

Kendi API'ni denetlemek

Bir API'nin CORS ayarını denetlemek için karmaşık bir araca gerek yok. Kendi API'nde ya da yazılı iznin olan bir hedefte, isteğe elle bir Origin başlığı ekler ve yanıtı okursun. Önce izin listesindeki bir kökenle dene ve beklenen başlıkların geldiğini gör. Sonra listede olmayan bir kökenle dene.

Güvenli bir sunucu bu ikinci isteğe Access-Control-Allow-Origin başlığı hiç koymaz. Başlık geliyorsa ve değeri senin yazdığın kökenin aynısıysa, sunucu kökeni yankılıyor demektir. Aşağıda bir e-ticaret ekibinin kendi API'sinde yaptığı denetimin çıktısı var.

$ curl -i -H "Origin: https://deneme.example" https://api.hedef.example/profil
HTTP/1.1 200 OK
Content-Type: application/json
Access-Control-Allow-Origin: https://deneme.example
Access-Control-Allow-Credentials: true

{"ad":"Ege","eposta":"[email protected]"}
Listede olmayan bir kökenle yapılan denetim. Sunucu kökeni yankılıyor.

Çıktıyı satır satır oku. Sunucu, hiç tanımadığı deneme.example kökenini aynen geri yazdı. Yanına kimlik bilgisine izin veren satırı ekledi ve gövdede kullanıcı verisi döndürdü.

Vary: Origin başlığı da yok. Bu tablo, raporda yüksek önemde bir bulgudur.

Düzeltme kodda yapılır: köken tam eşleşmeli izin listesinden doğrulanır, listede yoksa CORS başlığı hiç yazılmaz. İzleme tarafında ise API loglarına Origin değerini de yaz. Beklenmeyen kökenlerden gelen ve kimlik bilgisi taşıyan isteklerin artışı, birinin bu kapıyı yokladığını gösterir.

Etkinlik 6 / 6

Köken üçlüsü şema, port ve ____ alanından oluşur.

Bu derste bir hata mı gördün? Bildir