Web Teknolojileri Geliştircilerine Güvenlik Konularında Tavsiyeler - Detaylı Same Origin Policy İncelemesi

Biraz web dünyası tarihinden bahsedeceğim sonrasında benim güvenlik kaygılarım ve web dünyası için olmazsa olmaz güvenlik konularından birisi olan same origin policy’e doğru yol alacağım.

Konuyu hikayeleştirmek için bir kaç adım ile web tarihinden bahsetmek istiyorum.

Sene 1950'lerde elektronik dünyasının gelişimi ile bilgisayarların arasındaki iletişim ihtiyacı ortaya çıkıyor. 1960'lı yıllarda Amerikan Savunma Bakanlığı ARPANET adında savunma sistemlerinin iletişimini sağlamak için bir sistem kurmaya çalışıyor. ARPANET’in kurulmasının sebebi sağlam, dayanıklı bir iletişim alt yapısı kurma ihtiyacı. Ekşi deki bir arkadaş ARPANET’in geliştirilme sebebine ilişkin şunu söylüyor.

“ortaya çıkışı daha acıklı… Amerikalılar yaptıkları katliamların (bkz: hiroshima) (bkz: nagasaki) günün birinde kendilerine de yapılabileceğini farkediyorlar… (bkz: paranoya)

Öyle bir durumda da, ülkeden arta kalanda haberleşmeyi sağlayabilmek için, ne şartlarda olursa olsun ayakta kalabilecek, bilgisayar sayı ve konumundan bağımsız, routed bir protocol dizaynına girişiyorlar…

bu olayı da department of defense’in bir kolu olan arpa yapıyor.. (bkz: arpa)..

ip packet’lerinde header’in bir parcası olarak giden dod degeri de bu yüzdendir..”


1970'lere gelindiğinde bugün interneti üzerinde taşıyan TCP/IP protokolü bulunuyor. 1980'lere ulaştığımızda İsviçre’deki meşhur CERN’de bugün bildiğimiz World Wide Web bulunuyor. Aslında internet dediğimiz şey tüm bu gelişim adımlarının birleşiminin bir çıktısı. World Wide Web bu işin son adımı oluyor. Bu adımda herhangi bir noktada bulunan node, başka bir node client-server ilişkisi ile veri aktarımı yapıyor. Yani bildiğimiz internet oluşuyor. WWW bulan abimiz Tim Berners-Lee aynı zamanda client ile server arasında gidecek paketin içeriğini anlatan dili de yayınlıyor. Dilin adı “Tim Berners-Lee’s HTML” :) bildiğimiz ve kullandığımız HTML.

1994'te HTTP dünyasına cookie desteği geliyor. Bu büyük bir adım. Artık serverlar gelen kişinin kim olduğunu hatırlayabiliyor.

1995'e gelindiğinde HTML’in kullanıcılar için yeterli dinamikliğe sahip olmadığı gerekçesi ile dinamik bir dil geliştirme ihtiyacı ortaya çıkıyor. O dönemin tarayıcı pazarı hakimi Netscape client tarafında çalışacak bir script dili geliştirme işine girişiyor. Önce Java’nın sahibi Sun System ile görüşüyor. Sun Sytem, Microsoft’un web teknolojileri ve platformları pazarındaki hakimiyetini kırmak için Java ve teknolojileri kullanımı konusunda Netscape ile anlaşmaya çalışıyor ancak anlaşma sağlanamıyor. Bunun üzerine Nestcape, Brendan Eich’i işe alıyor. Ekibe dahil ediliş sebebi ise Eich’in Scheme programlama dilini Nestcape’in içine gömmesini sağlaması. Ancak Nestcape zamanla Schema dilini kullanmaktan vazgeçiyor ve Sun System’in Java’sı ile rekabet edecek, Java ile benzer tanımlayıcı yapılara sahip bir dil geliştirmeye karar veriyor. Eich, 10 günlük çalışmasının sonucunda JavaScript’in ilk prototipini üretiyor. Adı ilk başta Mocha sonra LiveScript olarak değişiyor ve en sonunuda JavaScript olarak adlandırılıyor. Yeni yazılım dünyasına girmiş arkadaşlar ilk zamanlarda karıştırır. JavaScript’i, Java’nın bir parçası olarak düşünür ancak öyle değildir.

Kısa bir tarih sonrası asıl anlatmak istediğim konuya gelelim. Eğer biraz web teknolojisi ile haşır neşir olduysanız bilirsiniz: Web sitelerinde gezdikçe siteler bizim bilgisayarlarımıza cookie’ler koyarlar. Bu işe de tarayıcı aracılık eder. Konulan cookieler hep aynı yerde saklanır. Saklanan verinin kendisi çok önemlidir çünkü bugün ben sizin facebook cookie’nize sahip olsam sizin facebook hesabınızda direk hiç doğrulama yapmadan gezinebilir, işlem yapabilirim(Burada HTTP header üzerinde kontrol edilebilecek başka yöntemler de var ancak konuyu şimdilik dağıtmayalım). Dolayısı ile çok önemli bir konudur cookie’lerin saklanması. Bu benim için hep bir soru işareti olmuştur. Acaba tarayıcı yanlışlıkla A sitesinin cookie’sini B sitesi gibi farklı bir web sitesine verebilir mi diye ya da kötü niyetli bir site benim diğer cookie’lerime erişebilir mi diye.

Bu işin ilk sorusuydu. Şimdi ikinci sorumuza gelelim. Eğer benim gibi sekmesi olmayan tarayıcılar döneminden geliyorsanız ve sekme desteği olduğu için Opera’ya transfer olduysanız benzer kaygıyı yaşamış olabilirsiniz. Tarayıcı açıp aynı tarayıcı içinde sekmeler açtığınızda; bir sekmede bankanıza giriş yapıp, işlem yaparken diğer sekmede dolaştığınız site banka sekmesindeki herhangi bir işlemi tetikleyebilir ya da okuyabilir mi? Bu da ikinci güvenlik kaygısı ile sorduğum soru olmuştur. Sanırım biraz paranoyaklık var…

Şimdi bu sorulara cevap vermeye çalışalım.

www.bank.com URL’i bankanızın sitesine giriş yapıtınız ve oturum açtınız diyelim. Ben eğer www.bank-sahte.com sitesi adında bir site yapar, içine bir iframe koyar ve url olarak www.bank.com sitesini verirsem. Bu sitenin URL’ni size ulaştırdım ve siz de açtınız diyelim. Ne olur bir düşünelim. Ben aslında sitenin içinde direk www.bank.com sitesinin içeriğini göstermiş olurum değil mi? Ve eğer bu iframe içindeki bileşenlere erişebilirsem(JavaScript kullanarak DOM’lara erişmek) o bileşenler üzerinde işlem yapabilirim değil mi?

frames.bank_frame.document.getElementById("bakiye").value

Örneğin yukardaki kod ile iframe içindeki bakiye bilginize eriştim. Tabi bir hacker olsam bu kadar masum olmazdım. Bakiyeye ulaşabiliyorsam para transferi de rahatlıkla yapabilirim.

<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
</head>
<body>
<iframe name="bank_frame" src="http://www.cbank.com/paragonder.php"><iframe>
<script>
frames.bank_frame.document.getElementById("alici_hesap").value="DOGAN_IN_HESABI";
frames.bank_frame.document.getElementById("tutar").value=1;
frames.bank_frame.document.getElementById("paraGonderButonu").click();
</script>
</body>
</html>

Yukarıdaki sayfa linkini size göndermiş ve siz açmış olsaydınız banka hesabınızdan benim hesabıma 1 TL para transferi yapmış olacaktınız. Aslında çok basit bir yaklaşım değil mi ? Bunu sadece banka için düşünmeyelim bugün her gün açık olan facebook, gmail oturumunuzu düşünün ya da diğer sitelerdeki oturumlarınızı. Nerdeyse hiç oturumumuzu kapanmadan kullanmaya devam ediyoruz. Bu durumda tehlikenin farkında mısınız!

Belki farkındasınız, belki değilsiniz ancak zaten sağolsun JavaScript ve DOM’u dünyamıza getiren Nestcape yazılımcıları, bu konuya da kafa yormuşlar ve bir policy’si(ilke) yayınlamışlar. Bu şekilde aslında bu işler o kadar da kolay olmuyor.

Biraz detaylandıralım… Konuya DOM(Document Object Mode) ile başlayalım. DOM, JavaScript dilinin client üzerinde çalışması ile, ekrandaki bileşenlere erişip, onlar üzerinde işlem yapmasını sağlayan bir API’dır.

Düşündünüz mü bilmiyorum ancak JavaScript client biriminde çalışan bir dil. Yani sizin tarayıcınız üzerinde koşuyor. Tarayıcınızda açtığınız oturumları düşünün, kullandığınız onca sitenin cookie’sini düşünün. JavaScript cookie’ye erişebiliyor. Iframe kullanıp ya da kötü amaçlı yazığım scriptleri düşünün. Tarayıcı makinemiz üzerinde kod koşturuyor. Tehlike! Tarayıcı, bizim adımıza üzerinde koşan JavaScript koduna bazı noktalarda kızmalı, bazı konularda izin vermeli değil mi? Yani makinemiz üzerinde koşan kodun denetimini birisi yapmalı. Burada tarayıcılara güvenmiş oluyoruz. İşte bu noktada tarayıcıların hep beraber uyması gereken bir ilke gerekli. Bu ilkeyi 1995 yılında Netscape yazılımcıları tanımlamışlar ve adına “Same Origin Policy” demişler. Hemen hemen tüm modern tarayıcılar bu ilkeyi takip ediyor ve izinlerini buna göre veriyorlar.

Nedir bu Same Origin Policy derseniz?

Kaynağa ulaşım sağlanırken kullanılan protokol, URL ve erişilen port bilgisi birleşiminden oluşan tanıma origin adı verilir. Eğer kaynak ile istemci aynı origin tanımına sahip ise kaynağın içeriğine erişim sağlanabilir.

Bu üçünün birleşimi origin tanımını üretiyor.

Örnek verelim. Bir sitemiz var diyelim URL şu şekilde: http://www.example.com/dir/page.html

Bu site üzerinde aşağıda tanımlanan URL’lere yapacağımız çağrılara Same Origin Policy’nin izin verip vermeyeceğini kontrol edelim.

Görselde göründüğü gibi protokol, URL ve port bilgilerine göre yapacağımız çağrılarda kaynağa erişim sağlanıp sağlanmayacağımızı görüyoruz. Sadece origin yapısının tam uyuşumu sağlanan tanımlarda erişim sağlayabiliyoruz. Son adımda port değişimi için tarayıcı tipine göre değişiklik durumu söz konusu. IE port değişikliklerinde izin verirken diğer major tarayıcılar port değişikliğine izin vermemektedir. Port değişiminde erişim sağlayabilir olup olmadığımızı alttaki tablodan görebiliriz.


Altta güzel bir tanımlama örneği var. Sizin bir siteniz var diyelim origin’i A olan ve B sitesi üzerinden işlemler yapmak istiyorsunuz. Bu durumda yapabileceğiniz ve yapamayacaklarınız listelenmiş.

Given these rules, we can assume that a site with origin A:

  1. Can load a script from origin B, but it works in A’s context
  2. Cannot reach the raw content and source code of the script
  3. Can load CSS from the origin B
  4. Cannot reach the raw text of the CSS files in origin B
  5. Can load a page from origin B by iframe
  6. Cannot reach the DOM of the iframe loaded from origin B
  7. Can load an image from origin B
  8. Cannot reach the bits of the image loaded from origin B
  9. Can play a video from origin B
  10. Cannot capture the images of the video loaded from origin B

Bu durumda www.bank-sahte.com sitesi üzerinden iframe ile www.bank.com sitesini açıp içindeki elementlere ulaşma hayalimiz suya düştü.

Bırakın farklı bir domain ile çağırmayı, www.example.com domaini altından login.example.com sitesine ya da contact.example.com sitesine bile bu policy altında erişim sağlayamıyorsunuz! Peki bunu esnetme şansımız var mı derseniz?

Evet bir yolu var. document.domain tanımı kullanarak TLD(Top Level Domain) tanımı yapılabiliyor. Bu şekilde site kendisinin origin URL tanımını yapıyor. Bu erişimin tamamlanması için hem www.example.com bu tanımı koduna eklemeli hem de login.example.com sitesi kendi koduna bunu eklemeli. Eğer iki tarafta eklemezse olmuyor. Mantıklı da zaten. Örnek verelim: Kötü niyetli bir arkadaş sunucumuz üzerine bir şekilde erişim sağladı ve hack.example.com diye bir sayfa oluşturdu diyelim. İçine document.domain tanımı ekledi www.example.com diye. Bu durumda direk example.com un verisine erişebilirdi ancak tarayıcı burada akıllı davranıyor ve example.com sitesinin de aynı domaine TLD tanımlayıp tanımlamadığına bakıyor ve buna göre izin veriyor.

Tekrar hatırlayalım: Bu durumda www.bank-sahte.com sitesi üzerinden iframe ile www.bank.com sitesini açıp içindeki elementlere ulaşma hayalimiz suya düştü.

Doğru düştü ancak şimdi başka bir soru aklınıza gelmiş olabilir. Nasıl oluyorda ben kendi URL’im dışında bir başka siteye erişip veri alıp veremiyorum? Bugün kullandığımız web siteleri onlarca siteden veri alıp/verip sayfayı oluşturuyor. Peki bu ilke ile nasıl oluyor?

Web 2.0 ile siteler çok daha interaktif olma ihtiyaçı doğdu ancak Same Origin Policy tüm tarayıcılar tarafında katı şekilde uygulanıyordu. Bu durumda yazılımcılar bu ilkeyi nasıl atlatırız çaresini düşünmeye başladılar. Bir yandan da bu ilkenin genişletilmesi amacıyla adımlar attılar.

Şimdi gelin XmlHTTPRequest, JSONP, XDomainRequest ve CORS inceleyelim.

XmlHTTPRequest

XmlHTTPRequest bir HTTP methodudur. Bu sayede asenkron bir şekilde farklı bir kaynağa çağrı yapılabilir. Bunu örnekleyecek olursak: Facebook ya da Twitter’da scroll down edersek bize sayfayı yüklemeden verilerin getirilmesini sağlar değil mi. İşte burada yapılan JavaScript, AJAX çağrısında X-Requested-With değeri HTTP header’ına eklenir. Ekleme sırasında değer olarak XmlHTTPRequest verisi verilir.

X-Requested-With: XmlHTTPRequest

Yapılan XmlHTTPRequest çağrılarında Same Origin Policy katı bir şekilde uygulanır. Kuralları şu şekildedir:

  • XmlHTTPRequest çağrısı farklı bir origin’e yapılabilir ancak cevabı okunamaz.
  • Eğer aynı origin’e çağrı yapıldı ise cevap okunabilir.
  • Eğer çağrının yapıldığı kaynak aynı origin de ise custom header eklenebilir.

Kendi origin’im altındaki bir origin’e çağrı yapabiliyorum ve veri çekebiliyorum. Örneğin: http://www.example.com/mainpage.php ile http://www.example.com/posts.php gibi.

Ancak burada önemli bir güvenlik açığı mevcut. Görüldüğü gibi farklı bir origin’e çağrı yapabiliyorum ancak cevap alamıyorum. Cevap almıyor olmak sıkıntı değil. Çağrı yapabiliyor olmakta zaten ciddi bir güvenlik açığı. Bu saldırı tipine Cross-Site Request Forgery deniyor.

Bunu küçük bir örnek vererek açıklayayım. Bir sitem var diyelim URL’i www.example.com olan. Hedefimdeki kullanıcıda internet alışverişlerinde www.alisverissitesi.com adresini kullanıyor diyelim. Bu site www.alisverissitesi.com/sepeti_sil.php linki çağırıldığında sepeti boşaltıyor olsun. Ben kendi siteme alttaki gibi bir tag eklersem olacakları tahmin edin?

<img src="www.alisverissitesi.com/sepeti_sil.php" width="0" height="0">

Kullanıcının alisverissitesi.com sitesindeki sepetini boşaltmış olurum değil mi? Kullanıcının bu konudan bir haberide olmaz.

Bu yöntem ile CSRF’e önlem almamış tüm sitelerin kullanıcılarına saldırı yapabilirim. Burada önlemde aslında basit. Göndereceğiniz bir formunuz var diyelim. Göndermeden önce random bir sayı üretip session alanınızda bu sayıyı saklarsanız. Daha sonra form talebi alır ve gelen sayı değeri ile session’da tutmuş olduğunuz sayıyı kıyaslarsanız CSRF’a çözüm bulmuş olursunuz. Çünkü dışardan yapılacak olan çağrıda bu random sayı üretilmemiş olacak ve session içindeki ile uyuşmayacaktır. Dolayısı ile sizde kötü niyetli site üzerinden uyuşmazlıktan dolayı işlemi yapmayacaksınız.

Burada değinmek istediğim bir konuda XSS saldırıları. Cross-site scripting diye geçiyor XSS. Burada konu şu: Geliştirici arkadaş, kullanıcıdan aldığı veriyi denetlemeden kodun içinde kullanıyor ise bu tip durumlarda XSS saldırıları yapılabiliyor. Bu çok daha tehlikeli bir durum. Düşünün ki masum bir şekilde kendi sitenizi yapmış bir geliştiricisiniz. Ancak bilmeden XSS açığı verdiniz ki bir çok insan bunu yapıyor. Kötü niyetli arkadaş geliyor ve XSS saldırısı yapıp kendi kodunu sizin sitenizde çalıştırıyor. Tehlikenin farkında mısınız! Bu kötü niyetli arkadaş sizin kameranıza bile erişip, izleme şansı var. Yani ne ders çıkarmak lazım. Güvenli olduğunu emin olmadığımız siteler, yapımcısı hiç bir art niyete sahibi olmasada kullanıcılar için sadece sayfayı açmış olmak bile bir tehlike!!!

Örnek verelim: Site arama yapmak için bir inputbox içeriyor olsun ancak inputbox içinde girilen veriyi kontrol etmeden site içinde aranan kelime diye bir alana bu yazılıyor olsun.

Bizde kötü niyetliyiz ve alttaki şekilde kodu siteye ekliyoruz.

<script>alert(document.cookie)</script>

Ne oldu bu durumda. Sitenin kodu içinde kötü niyetli kişinin kodu çalışmış oldu. XSS saldırılarının tipleri var. Bu en masum olanı. Eğer kötü niyetli kişinin kodu kayıt altına alınır ve tüm kullanıcıların oturumlarında bu kod çalışırsa düşünün neler olabilir.

Dolayısı ile tekrar altını çiziyorum. Sadece basit bir sayfayı açmak bile risk içeriyor. Burada site sahibide hiç bir art niyete sahip olmayabilir. Ancak siz bu siteye giriş yaparak bilgilerinizi sızdırmış olabilirsiniz.

Konuya geri dönelim. XmlHTTPRequest ile aynı origin’deki kaynağa paket atıp, alabiliyordum. Ancak bu yeterli mi?

JSON Padding (JSONP)

Şimdi soru şu olabilir. Peki kur bilgisi, hava durumu bilgisi ya da başka bir siteden çekeceğim albüm bilgisini nasıl çekeceğim. XmlHTTPRequest yaptığımda sadece kendi origin üzerinden yaptığım çağrıların cevaplarını okuma izni veriyor. Bu durumda başka bir siteden nasıl veri isteyip ekrana gösterebilirim.

2000'lerin başlarında JSON’un keşfi ile veri tipinin kendisi JavaScript kodu haline dönüşmüş oldu. Bu durumda akıllı yazılımcı arkadaşlar şunu düşündü. Same Origin Policy başka bir site üzerinden kaynak kodun erişimine izin veriyor ve erişilen kodu kendi tarayıcımız üzerinde koşturabiliyoruz. Peki bu durumda biz web servisinden JSON verisi alırsak ve bunu kendi kodumuz üzerinde bir fonksiyon olarak çalıştırırsak aslında başka bir origin üzerinden veri çekmiş oluruz!

İşte bunun üzerine JSONP hayatımıza girdi. Burada çağrının yapıldığı origin bilgisine bir de callback fonksiyon bilgisi ekleniyor. Örneğin alttaki çağrıyı farklı bir origin’e çağrı yapıyoruz ve sonuç bize bir JavaScript fonksiyonu olarak dönüyor.

http://www.example.com/getAlbums?callback=foobarbaz

Örnek callback fonksiyonlu cevap:

foobarbaz(
[
{"artist": "Michael Jackson", "album": "Black or White"}
{"artist": "Beatles, The", "album": "Revolution"}
]
);

Burada görüldüğü gibi foobarbaz fonksiyonu kod içinde çağırılıp istenilen yerde kullanılabilir. Çünkü o bir JavaScript kodu, JavaScript fonksiyonu.

JSONP güzel ancak bunun da önemli bir güvenlik açığı var. O da çağrı yaptığınız yerden gelen JavaScript kodunu direk kendi kodunuz içinde çalıştırıyorsunuz! Tehlikenin farkında mıyız? Düşünün projenizde başkasının kodunu kopyalayıp çalıştır diyorsunuz. Müthiş basit bir hackleme yöntemi olur değil mi bu? Bu durumda sizin çağrı yapacağınız origin’in doğru domain olduğu ve giden/gelen verinin bozulmadığına kesin olarak emin olmanız gerekiyor. Mutlaka HTTPS kullanmalısınız. Bu durumda JavaScript’in mixed content kısıtından dolayı tüm site ve içeriğinin HTTPS’e geçmesi gerekmektedir. Çağırılan URL’in güvenli olduğununa emin olsakta dönen kodun içeriğinde çağırılan fonksiyonların filtrelenmesinde fayda var. Yani her fonksiyon çalıştırılamasın, kodumuzda at koşturan kodlar çalışmasın :)

XDomainRequest and CORS

JSONP ile artık farklı bir origin üzerinde çağrı yapıp, okuyabildik. Peki başka yeni bir tanıma neden ihtiyaç duyuyoruz diye sorabilirsiniz. Ancak JSONP’nin problemi sadece read-only olması. Yani biz çağrı yapıyoruz çağrının çıktısında sadece veri okuyoruz. Yazma işlemi yok. POST ile veri göndermek istiyorsunuz ancak yapamıyorsunuz. Web 2.0 nin hayatımıza girme ihtiyaçı ile Same Origin Policy yapımcılarına baskı artırılır ve policy’nin esnetilmesi sağlanır. Esnemeyi sağlayacak bir kaç adım attırılır. Bu adımlardan ilki Microsoft tarafından yapılır. XDomainRequest adlı yapı tanıtılır. Diğer major tarayıcılar ise CORS(Cross Domain Resource Sharing) tanıtırlar. IE 8 ve 9 da Microsoft kendi geliştirdiği yapıyı kullanmayı tercih etsede, CORS’un popülerliği ile IE 10 altında CORS’a destek verir ve yola devam eder. Mantık basittir ancak kısıt Same Origin Policy’den çıkıp web servisi hizmeti verecek olan domain’in insiyatifine bırakılır. Tarayıcı CORS çerçevesinde tanımlı olan bilgiyi kontrol eder. Eğer uygun ise uygular, değilse engeller.

Mantık şöyledir. A origin’i B origini üzerinden bir asenkron çağrı yapacak diyelim. A çağrı yaparken HTTP header’ına ORIGIN adında bir header ekler. B sitesi ise Access-Control-Allow-Origin header’lı bir cevap döner. Bu header içinde izin verilen cross origin’lerin bilgilerini verir.

Alttaki tanım ile sadece www.example.com adresinin yapacağı CORS çağrılarına izin verileceği bildirilmiş.

Access-Control-Allow-Origin: www.example.com

Tüm subdomain’lere izin verilmek istenirse şöyle tanımlanabilir:

Access-Control-Allow-Origin: *.example.com

Tüm domainlere izin verilmek istenirse alttaki şekilde tanımlanabilir:

Access-Control-Allow-Origin: *

Mantıktan anlaşıldığı gibi ben bir origin üzerinden başka bir origin’e çağrı yapacağım ancak yapmadan önce ben yapabilir miyim diye soruyorum. O da kime yaptıracağını dönüyor. Dolayısı ile süreç iki adımdan oluşuyor gibi.

Bu ikili adımı kolaylaştırmak için iki farklı request tipi tanımlanmış.

1.Simple Request

Eğer istek POST, GET ve HEAD ise ve Content-Type header bilgisine application/x-www-form-urlencoded, multipart/form-data, text/plain tiplerinden biri set edildi ise, CORS mekanizması bunu Simple Request kategorisinde değerlendirecek ve sunucudan dönen veri tarayıcı tarafından gösterilecektir.

Örnek bir Simple Request CORS çağrısı:

GET / HTTP/1.1
Host: cors.example.com
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en,en-US;q=0.5
Origin: http://www.acceptmeplease.com
Connection: keep-alive

Sunucunun CORS çağrısına cevabı

HTTP/1.1 200 OK
Date: Sun, 24 Apr 2016 12:43:39 GMT
Server: Apache
Access-Control-Allow-Origin: http://www.acceptmeplease.com
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: application/xml
Content-Length: 423

<?xml version="1.0" encoding="UTF-8"?>
...

Direk mesajı aldı ve işledi. Sonucu tarayıcı ilk mesaj cevabı ile ekrana gösterebilir.

2.Preflighted Request

  • Eğer istek POST, GET ve HEAD dışında farklı bir method ise ve Content-Type header bilgisine application/x-www-form-urlencoded, multipart/form-data, text/plain tiplerinden başka bir tip olarak set edildi ise.
  • Yapılan isteğe custom header eklendi ise.

Bu durumda CORS mekanizması bu çağrıyı preflighted request olarak değerlendirir. Dolayısı ile tarayıcı method çağrısını yapmadan önce HTTP OPTIONS methodu ile sunucuya bir ön istek yapar. Hedef sunucunun bu isteği kabul edip etmeyeceğini sorar. Bu kontrole preflighted request denir.

Örnek bir preflighted request alttaki şekilde öncelikle OPTIONS methodu ile sunucuya soruluyor. Kontrol edilmesi gereken method ve header bilgisini çağrı içinde taşıyor. Alttaki şekilde koyu olarak gösterilenler.

OPTIONS /resources/post-here/ HTTP/1.1
Host: cors.example.com
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Connection: keep-alive
Origin: http://www.acceptmeplase.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-TOKEN-ID

Eğer sunucu kontrolü istenen method ve header’a izin verirse alttaki şekilde 200 dönüyor ve mesajın içinde izin verilen origin, methodlar, header’ların bilgileri bulunuyor.

HTTP/1.1 200 OKDate: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache
Access-Control-Allow-Origin: http://www.acceptmeplease.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-TOKEN-ID
Access-Control-Max-Age: 86400

Vary: Accept-Encoding, Origin
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

Şimdi sorabilirsiniz. Arkadaş POST ve X-TOKEN-ID ile çağrı yapabilir olup olmadığını her seferinde sormamın ne anlamı var. Boşa giden, gelen paket. Bir defa sordum ya zaten?

Bu soruyu, CORS yapımcıları kendilerine sormuşlar ve Access-Control-Max-Age adında bir header değişkeni tanımlamışlar. Bu değişken, bana sorduğun bu kontrol sorusunu şu süre boyunca tekrar sormana gerek yok diyor. Yukardaki örneğe bakarsak 86400 saniye dönmüş sunucu. Yani 24 saat boyunca bana tekrar sormana gerek yok diyor. Sonrasında gelebilirsin.

Bu süreyi veren arkadaşların dikkat etmesi gereken bir husus vardır. O da her tarayıcının kendisine göre bir maksimum değeri vardır. Yani siz tarayıcının kendi maksimum değerinden daha fazlasını buraya yazarsanız, tarayıcının maksimum değeri, sizin değerinizi ezer. Örneğin Chrome tarayıcısının maksimum süresi 10 dakikadır. Bunun sebebi ise bellek zehirlenmelerine mani olmak içindir. Bellek zehirlemesi hacker’ların kullandığı bir yöntemdir. Bu şekilde izin verilen origin’lerin yapısı bozulabilir. Bu tip zehirlenme saldırılarını bertaraf etmek için Chrome süreyi kısa tutmuş.

Burada benim de yaptığım bir güvenlik hatasından bahsetmek isterim. Vakti zamanında aynı domainde olup, farklı bir port üzerinde çalışan siteme erişim sağlamak istediğimde CORS hatası almıştım ve internette arattığımda karşıma gelen ilk cevapta

Access-Control-Allow-Origin: *

kullanmam gerektiğim yazmıştı, ben de kullanmıştım. Sorun çözülmüştü aslında. Peki burada problem nedir derseniz? Baktığınızda internette aratıp, çözüm olarak karşımıza gelen şeylere dikkat etmeden kullandığımızda açıklarımızı bilmeden vermiş oluyoruz. Yukardaki tanımlamayı yaparak ne yapmış oldum. Benim siteme, dışarıdan rahatlıkla erişim sağlanabilecekti, veri alış verişi tüm dünyadan ulaşılıp, tarayıcılar üzerinde gösterilebilecekti. Peki bunu istemiş miydim? Hayır istememiştim. Benim derdimi aslında alttaki tanımlama çözecekti.

Access-Control-Allow-Origin: http://www.example.com:8081

Dolayısı ile internette bulunan ilk çözümü direk alıp uygulamak yerine detayını inceleme yapmak lazım. Aman dikkat!

CRON ile Cookie Kullanımı

Başka bir origin üzerinden başka bir origin’e çağrı yaptık artık ve veride gönderebiliyoruz. Peki verinin dışında o sitenin cookie’sini yada sertifikalarını vs. gönderebiliyor muyuz derseniz? Bu durumda XMLHttpRequest’in withCredentials değeri devreye giriyor. Hem client hem server tarafının credentials değerine TRUE vermesi halinde cookie transferi işlemi yapılabiliyor. Örnek verecek olursak, alttaki JavaScript kodu ile bir XMLHttpRequest çağrısı yapalım ve withCredentials değerini true olarak tanımlayalım.

var invocation = new XMLHttpRequest();
var url = 'http://cors.example.com;

function callOtherDomain(){
if(invocation) {
invocation.open('GET', url, true);
invocation.withCredentials = true;
invocation.onreadystatechange = handler;
invocation.send();
}
}

Örnek Client tarafından yapılan CORS çağrısı. İçinde cookie gönderiliyor.

GET / HTTP/1.1
Host: cors.example.com
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Origin: http://www.acceptmeplease.com
Cookie: pageAccess=2

Sunucunun cookie’li credentials’a verdiği yanıt:

Date: Mon, 01 Dec 2008 01:34:52 GMT
Server: Apache/2.0.61 (Unix) PHP/4.4.7 mod_ssl/2.0.61 OpenSSL/0.9.7e mod_fastcgi/2.4.2 DAV/2 SVN/1.4.2
X-Powered-By: PHP/5.2.6
Access-Control-Allow-Origin: http://www.acceptmeplease.com
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 106
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

Son olarak iki tane HTTP header parametresinden bahsedeceğim.

httpOnly flag(bayrağı) ile set edilen cookie verisinin sadece HTTP methodlarında kullanımına imkan verilmektedir. Bu şekilde JavaScript üzerinden sitenin cookie’si alınıp, kullanılamaz. Örneğin XSS tipi saldırılar ile kişinin cookie bilgisi alınıp, ekrana basılamaz. Burada sorumluluk tarayıcıya bırakılıyor. Tarayıcı sadece HTTP çağrılarında sitenin çağrısına kendi cookie bilgisini ekler ve sunucuya iletir. Güvenlik için httpOnly true olması önemlidir.

httpOnly kuralını by pass edip, cookie verisine erişime için kullanılan bir yöntem ise HTTP Trace methodu ile sunucuya çağrı yapmaktır. HTTP Trace methodu debugging için kullanılır. Bir nevi echo methodudur. Gönderdiğiniz HTTP request’ini response olarak döner. Burada kilit konu gönderdiğiniz HTTP paketinin dedik. Yani httpOnly kullandınız ve JavaScript’in cookie erişmesini engellediniz. Ancak HTTP paketlerinde cookie gidiyor değil mi? Bu durumda siz eğer sitede XSS açığı tespit eder ve bir şekilde kod injection yaparsanız, HTTP Trace ile yapacağınız çağrının çıktısını alır ve parse edip içinden cookie ayıklayabilirsiniz.

Artık modern tarayıcılar HTTP Trace kullanımına izin vermiyor. Ancak burada hacker arkadaş sadece tarayıcı üzerinden bu çağrıyı yapmak zorunda değil, farklı bir yöntem ile bu çağrıyı başlatacak bir yöntem bulursa cookie’ye erişim sağlamış olurlar.

Dolayısı ile sunucu tarafında çalışan arkadaşların HTTP Trace methodunu disable etmeleri güvenlik açısından önemli olacaktır.

secure flag(bayrağı) ile cookie verisinin sadece güvenli bağlantıda sunucuya iletilmesi sağlanmaktadır. Yani HTTPS üzerinden yapılan çağrılarda sadece cookie HTTP header’a eklenecektir. Bu şekilde dışardan çalınması engellenmiş olur. Örneğin gazete.com diye bir sitemiz var. Hem siteye login olabiliyoruz hem de anasayfa üzerinden haberlere erişebiliyoruz. Güvenli olsun diye login sayfasını HTTPS üzerinden verirken, anasayfayı hızlı açılması için HTTP üzerinden servis etmeyi tercih etmişiz. Bu durumda iki protokolde mevcut. Ben eğer secure true çekmez isem bu durumda HTTP çağrılarımda da cookie siteye gitmiş olur ve hattı dinleyenler bu pakete erişebilirler.


Burada ifade ettiklerim benim de zaman içinde yaptığım hataları içeriyor. Sanmayın herkes yazılım dünyasına başladığında bunları bilerek yola koyuluyor. Zamanla detaylara iniliyor ve bu tür konulara kafa yorulmaya başlanıyor. İşin büyülü yanlarından birisi de güvenlik. Güvenlikteki temel cümlede şudur: Hiç bir sistem tam olarak güvenli değildir, biz yazılımcılar sadece hackerların işlerini zorlaştırırız. Umarım yazım faydalı olmuş ve sizlere bir şeyler katmıştır.

Benim için önemli bir söz “bir kişi anlatır iki kişi öğrenir”. Öğrenme konusu benim en büyük motivasyonum olsa da, takip eder, beğenir ya da yorum yaparsanız bu tür paylaşımları yapmak için motive edici olacaktır.

Saygılarımla,

Doğan Aydın

Github Sayfam: https://github.com/trda

Linkedin Sayfam: https://www.linkedin.com/in/doganaydin/


Yazının içinde https://www.netsparker.com’un Same Origin Policy tanımından bir çok örnek aldım. Bunu da özellikle bildirmek isterim.

REFERANSLAR