← Yazılara dön

Yazı · web-guvenligi / cors / http

CORS hatası neden isteğin gönderilmediği anlamına gelmez?

Preflight reddini, gönderilmiş isteğin yanıtına erişim engelinden ayırmak için pratik bir açıklama.

Tarayıcı konsolunda CORS hatası görünce ilk akla gelen açıklama, isteğin sunucuya hiç ulaşmadığı oluyor. Oysa aynı hata, istek gönderilip sunucu yanıt verdikten sonra da ortaya çıkabilir. Tarayıcının yanıtı sayfadaki JavaScript koduna açmaması ile isteğin gönderilmesini durdurması farklı aşamalardır. Hata ayıklarken önce hangi aşamada kaldığımızı belirlememiz gerekir.

Bu ayrım özellikle veri değiştiren işlemlerde önem kazanır. Ekranda hata görünmesi, işlemin başarısız olduğunu tek başına göstermez. Aynı işlemi yeniden denemeden önce uygulamanın durumuna ve sunucu kayıtlarına bakmak, gereksiz tekrarları önler. Bu yazıdaki alan adları ve senaryolar temsili eğitim örnekleridir; gerçek bir sistemde yapılmış test sonucu anlatılmıyor.

Önce origin ile site kavramlarını ayıralım

HTTP ve HTTPS adreslerinde origin karşılaştırması şema, sunucu adı ve etkin port üzerinden yapılır. Yolun değişmesi origin'i değiştirmez. Örneğin aynı sunucudaki /profil ve /ayarlar aynı origin'dedir. Alt alan adı veya port değiştiğinde ise ilişki cross-origin olur. Bu modelin ayrıntıları WHATWG HTML standardında tanımlanır.

Site karşılaştırması farklıdır: normal alan adı örneklerinde şema ve kaydedilebilir alan adı dikkate alınır. Bu nedenle https://app.example.test ile https://api.example.test, cross-origin oldukları hâlde same-site olabilir. https://external.other.test başka bir site'dır. HTTP'den HTTPS'ye geçiş de schemeful site karşılaştırmasını değiştirir. Cookie davranışını yorumlarken bu ayrımı korumak gerekir. web.dev açıklaması, iki kavramı örneklerle karşılaştırır.

İsteği göndermek ile yanıtı okumak

Same-origin policy, farklı origin'ler arasındaki bütün etkileşimleri aynı biçimde yasaklamaz. Form gönderimi, bazı navigasyonlar ve kaynak gömme işlemleri mümkünken, başka origin'in verisini script ile okumak kısıtlanabilir. MDN'nin same-origin policy rehberi bu ayrımı yazma, gömme ve okuma işlemleri üzerinden açıklar.

CORS, sunucunun bir yanıtın hangi origin'deki script ile paylaşılabileceğini bildirmesini sağlar. Preflight gerektirmeyen uygun bir cross-origin GET isteği düşünelim. İstek gönderilir, sunucu yanıt üretir; fakat yanıtta gerekli CORS izni yoksa çağıran JavaScript bu yanıtı okuyamaz. Konsoldaki hata böyle bir akışın sonunda oluşabilir. MDN CORS rehberi bu tür isteklerin akışını gösterir.

Network panelinde bir HTTP durum kodunun görünmesi ile uygulama kodunun kullanılabilir bir Response alması bu yüzden aynı gözlem değildir. Ayrıca görülen 200, tek başına belirli bir iş kuralının başarıyla tamamlandığını söylemez. İşlemin sonucunu gösteren uygulama kaydı veya kontrollü test verisi ayrıca incelenmelidir.

Preflight reddedildiğinde ne olur?

Bazı cross-origin isteklerde tarayıcı, asıl isteği göndermeden önce izin kontrolü yapar. Buna preflight denir. Örneğin PATCH yöntemi ve belirli başlıklar için şu tür bir OPTIONS isteği oluşabilir:

http
OPTIONS /v1/profile HTTP/1.1
Host: api.example.test
Origin: https://app.example.test
Access-Control-Request-Method: PATCH
Access-Control-Request-Headers: content-type,x-lab-request

Sunucunun yanıtı istenen origin, yöntem ve başlıklar için gerekli izinleri sağlamazsa, tarayıcı bu akıştaki asıl PATCH isteğini göndermez. OPTIONS'ın gönderilmiş olması, PATCH'in de gönderildiğini göstermez. Preflight başarılı olduğunda ise asıl isteğin yanıtı da gerekli CORS denetiminden geçmelidir. Fetch standardı bu aşamaları ayrı tanımlar.

Burada iki farklı hata senaryosu vardır:

  • Preflight başarısızdır; asıl istek gönderilmemiştir.
  • Asıl istek gönderilmiştir; yanıt script ile paylaşılamamıştır.

Her cross-origin istek preflight gerektirmez. Yaygın olarak “simple request” denilen sınıfa girmek de isteğin zararsız veya yetkili olduğunu söylemez; yalnız ilgili tarayıcı koşullarını karşıladığını anlatır. Ayrıca tarayıcı daha önce aldığı preflight iznini geçerli olduğu sürece kullanabilir. Her denemede yeni OPTIONS görmeyi beklemek doğru olmaz. MDN CORS rehberi

Bir fetch çağrısında credentials: "include" kullanılması, uygun kimlik bilgilerinin isteğe eklenmesini talep eder. Bunun sonucunda cookie'nin kesin olarak gönderileceği söylenemez; cookie kapsamı, SameSite kuralları ve tarayıcının ilgili politikaları da uygulanır. Same-site bir alt alan çağrısı ile cross-site çağrıyı aynı varsayımla değerlendirmemek gerekir.

Credential içeren bir CORS yanıtının paylaşılması için Access-Control-Allow-Origin değerinde uygun açık origin ve Access-Control-Allow-Credentials: true gerekir. Bu durumda wildcard origin olan * kullanılamaz. İzin verilen origin değerinin gelişigüzel yansıtılması da güvenli bir izin listesi sayılmaz. Fetch CORS ve credentials bölümü

Bu başlıklar uygulamadaki erişim kontrolünün yerine geçmez. Kullanıcının kimliği, istediği kaynağa erişim yetkisi ve işlemin koşulları sunucuda denetlenmelidir. Aynı şekilde, bir origin değerinin yanıtta görünmesi tek başına hassas verinin okunabildiğini kanıtlamaz; gerçek paylaşım davranışının ve veri bağlamının birlikte değerlendirilmesi gerekir.

CORS hatası CSRF değerlendirmesini bitirmez

CSRF değerlendirmesinde soru, kullanıcının otomatik eklenen kimlik bilgileriyle istenmeyen bir işlemin yaptırılıp yaptırılamadığıdır. İşlemi tetikleyen tarafın yanıtı okuyabilmesi her senaryoda gerekli değildir. Dolayısıyla CORS hatası görmek veya preflight sonucuna bakmak tek başına “CSRF var” ya da “CSRF yok” sonucuna götürmez.

Sunucu tarafındaki CSRF kontrolleri, kabul edilen içerik türleri, origin doğrulaması ve cookie politikası uygulamanın akışına göre birlikte ele alınmalıdır. OWASP CSRF önleme rehberi bu kontrollerin nasıl tamamlayıcı kullanılabileceğini açıklar. Bu yazının kapsamı CORS akışıdır; diğer tarayıcı güvenlik politikaları burada ayrıca incelenmiyor.

Hata ayıklarken hangi kanıta bakmalı?

Kendi uygulamanızda veya izinli bir laboratuvarda incelemeyi küçük, gözlenebilir adımlarla yürütün:

  1. Çağrının başladığı origin'i, hedef URL'yi, yöntemi ve eklenen başlıkları kaydedin.
  2. Network panelinde OPTIONS ile asıl isteği ayrı bulun. Paneldeki filtrelerin ilgili kaydı gizlemediğinden emin olun.
  3. İstek ve yanıt başlıklarını ilgili kayıt üzerinde inceleyin; bir OPTIONS yanıtının başlıklarını asıl yanıtla karıştırmayın.
  4. Konsol hatasını, JavaScript'in sonucu okuyabilmesini ve sunucudaki işlem sonucunu ayrı gözlemler olarak yazın.
  5. Veri değiştiren işlemlerde yeniden denemeden önce sonucu doğrulayın. İnceleme kayıtlarında token ve cookie gibi sırları paylaşmayın.

Network panelinin istek, başlık ve başlatıcı zinciri alanları için Chrome DevTools başvurusu yararlı bir başlangıçtır. Bir proxy'nin Repeater aracı veya tarayıcı dışı HTTP istemcisi yanıtı gösterebilir; bu, tarayıcının aynı yanıtı sayfadaki script'e açacağını kanıtlamaz. Tarayıcı davranışına ilişkin iddiayı tarayıcı bağlamında doğrulamak gerekir.

Son olarak, mode: "no-cors" okunabilir bir API yanıtı elde etmenin çözümü değildir. Bu modda cross-origin yanıt opaque olabilir; script durum koduna, başlıklara ve gövdeye normal biçimde erişemez. MDN Request.mode belgesi bu sınırı açıklar. Sağlıklı teşhis, hatanın adından tahmin yürütmek yerine isteğin hangi aşamaya ulaştığını belirlemekle başlar.

← Yazılara dönmuhammet.saridogan.work@gmail.com