HTTP İşleyişi ve Güvenliği Açısından Cookie ve Session Yönetimi

Ziyahan Albeniz - 20 Temmuz 2016 -

İlk başlarda sadece statik sayfaları sunmak için kullanılan HTTP protokolü (yapısının basitliği buradan gelmekte.), çok geçmeden üzerindeki durağanlığı attı ve ziyaretçilerle etkileşime geçecek yollar tasarlandı. İnteraktif bir iletişim için en önemli gereksinim olan, bir ziyaretçi isteğini, diğer tüm ziyaretçi isteklerinden ayrı kılacak tekil bir değer idi. İşte web'in ilk günlerinde, 1994, bir eticaret uygulaması tasarlarken bu ihtiyacı hisseden Lou Montulli adındaki Netscape çalışanı, web'in ilk alışveriş sepeti uygulaması için, Unix sistemlerde "magic cookie" olarak kullanılan bu konsepti, webe uyarlamaya karar verdi. Netscape Navigator tarafından ilk sürümünden itibaren desteklenen Cookie'ler, zamanla tüm browserlar tarafından desteklenmeye başlandı.

HTTP İşleyişi ve Güvenliği Açısından Cookie ve Session Yönetimi

Spiderman'i süper bir kahraman haline getirenin nasıl küçük bir örümcek ısırığı olduğunu hatırlıyorsunuz değil mi?

Hayat, akışını mükemmel kılan bu tür küçük ayrıntılarla dolu. Gündelik yaşamın hayhuyunda gözümüzden kaçsa da, bu minik ayrıntılar, pek çok sistemin işleyişini mükemmelleştiren, alçakgönüllü hizmetkarlar gibidir. Bu küçük ayrıntılar olmadan, pek çok büyük sistemin nasıl bir anda anlamsızlaşacağını ya da büyük sistemlerin gerçek güçlerini nasıl da bu küçük ayrıntılardan aldığını gelin birlikte görelim.

Yolu kuru temizleme, vestiyer, emanet ofisleri, kayıp eşya bürolarına düşenler hatırlayacaktır. Veznede sizi karşılayan güleryüzlü (umarım öyledir!) görevli kusursuzca isteğinizi yerine getirir; depolarında size ait olan eşyayı size bitamam ve hatasız teslim ederler.

İşin şaşırtıcı yanı, gün içerisinde bu noktaları ziyaret eden binlerce kişi gibi, sizin hakkınızda da en ufak bir bilgileri yoktur. Kim olduğunuzu bilmeden, hatta çoğu zaman rutin bir selamlamadan sonra, isteğinizi kusursuzca yerine getirip, memnuniyetinizi kazanmalarındaki püf nokta, bir müşteri olarak sizi tanıtan, aldığınız hizmeti belgeleyen küçük fişler ve bu fişlerin üzerinde, sizi tanımlayan benzersiz değerlerdir.

İşte bu değerler sayesinde, depodan, rafdan size ait olan ilgili eşya, ürün getirilip size kusursuzca teslim edilir.

Internet'in en görkemli protokolü, interneti bu kadar büyülü hale getiren HTTP için de durum bundan farklı değil. Gün içerisinde binlerce müşteriyle, ziyaretçiyle muhatap olan HTTP sunucularının sizi tanımalarına imkan yoktur. Alınganlık etmeyin ama siz de HTTP sunucuları için binlerce, milyonlarca müşteriden, ziyaretçiden birisiniz. Dükkana giren müşteri birbirinden ayırmak için müşterinin kendilerine sunacağı hizmet fişlerinden başka bir seçenekleri yoktur.  Onlar da tıpkı yukarıdaki örnekte olduğu gibi, kendilerine teslim ettiğiniz bir hizmet fişi sayesinde sizi tanırlar ve raflarında sizin fişinizdeki numarayla sakladıkları bilgiyle işlemlerine devam ederler. HTTP'nin bu özelliği, yani gelen istekleri kendiliğinden ayıramaması Stateless özelliği olarak bilinmektedir.

HTTP için bu bilgi, hizmet fişleri Cookie olarak bilinmektedir. İsmine dair Hansel ile Gratel masalından, Çin'in şans kurabiyelerine kadar çeşitli şehir efsaneleri bulunsa da, ismin kaynağına dair en güçlü rivayet Unix sistemlerinde X Window'a login olmak için kullanılan Magic Cookie'lerden türediği yönündeki rivayettir.[1] 

İlk başlarda sadece statik sayfaları sunmak için kullanılan HTTP protokolü (yapısının basitliği buradan gelmekte.), çok geçmeden üzerindeki durağanlığı attı ve ziyaretçilerle etkileşime geçecek yollar tasarlandı. İnteraktif bir iletişim için en önemli gereksinim olan, bir ziyaretçi isteğini, diğer tüm ziyaretçi isteklerinden ayrı kılacak tekil bir değer idi. İşte web'in ilk günlerinde, 1994, bir eticaret uygulaması tasarlarken bu ihtiyacı hisseden Lou Montulli adındaki Netscape çalışanı, web'in ilk alışveriş sepeti uygulaması için, Unix sistemlerde "magic cookie" olarak kullanılan bu konsepti, webe uyarlamaya karar verdi. [2] Netscape Navigator tarafından ilk sürümünden itibaren desteklenen Cookie'ler, zamanla tüm browserlar tarafından desteklenmeye başlandı.

Zamanla Cookie'leri daha güvenli kılmak için tasarlanan ve opsiyonel olan birkaç özellik dışında Cookie'ler ilk günkü yapısını bozulmadan korumaktadır.

Bugün giderek azalsa da, web'in ilk günlerinde hakkında yürütülen kara propagandaya rağmen Cookie'ler  hiçbir zararlı kod içermeyen basit metin dosyalarıdır. Browserınızda maksimum 4KB'lık bir yer tutarlar.

Browserınızda bir Cookie'nin yaşamı, talep ettiği web isteğine cevaben dönen yanıttaki  şu header belirteci ile başlar:

Set-Cookie: value[; expires=tarih][; domain=alan adı][; path=path][; secure]

Cookie belirteci içeren örnek bir HTTP yanıtı:

HTTP/1.1 200 OK
Content-Type: text/html
Set-Cookie: CookieName=CookieValue; path=/; 

Cookie spesifikasyonu ile HTTP'den dönen yanıttaki Cookie belirtecinin birbirinin aynı olmadığı dikkatinizi çekmiş olmalı. Bunun tek bir nedeni var, Cookie value değeri dışındaki tüm değerler, opsiyonel, yani isteğe bağlı değerlerdir. Bu değerlerin HTTP sunucusu tarafından kullanılıp kullanılmayacağı, HTTP sunucusunun Cookie'yi kullanış amacına bağlıdır.

Şimdi yavaş yavaş Cookie özelliklerini inceleyelim. Öncelikle önemli bir ayrıntıyı hatırlatmakta fayda var. Cookie ile birlikte gönderilen tüm özellikler birbirinden ";" noktalı virgül ve space ile ayrılmalıdır.

name:  Cookie belirtecinin bu ilk kısmı name=value formatında belirtilmesine rağmen, Browser'lar bu konuda ısrarcı değillerdir. Bu yüzden sadece isim olarak set edilen Cookie'ler görmek sizi şaşırtmamalı. Bazı durumlarda web uygulamaları, pozisyon bayrakları olarak name=value biçiminde set edilmeyen, sadece isim değeri olan Cookie'ler set edebilirler.

domain: Cookie'nin browserda set edildikten sonra, hangi domain'e yapılan isteklerle birlikte gönderileceğini belirten seçenektir. Fişlerde bulunan kurum adı gibidir. X firmasına ait bir teslim fişini, Y firmasında kullanamazsınız. Bu da opsiyonel bir değerdir. Belirtilmediği takdirde browser, Cookie'yi set eden domain adını kullanacaktır.

X sitesi, Y sitesine ait bir Cookie browserda set edemez. Güvenlik nedeniyle bu tür girişimler sunucu tarafında da, istemci tarafında da engellenmiştir. Fakat, Bir Cookie aynı domain'e ait birden fazla alt domain için kullanılabilir. Örneğin example.com için set edilmiş bir Cookie, mail.example.com, calendar.example.com, crm.example.com sitelerine yapılan isteklerle beraber gönderilebilir. Burada browserda kayıtlı Cookie'lerin domain değeri ile, istek yapılan URL'in hostname'i Tail Comparison olarak adlandırılan, sondan başa (sağdan sola) doğru yapılan bir karşılaştırma işlemi ile kontrol edilmekte, ve eşleşen Cookie'ler istekle beraber gönderilmektedir.

Set-Cookie: Scanner=Netsparker; domain=example.com

Cookie Set Eden Domain

Cookie'nin gönderileceği domainler

Cookie'nin gönderilmeyeceği domainler

www.example.com

www.example.com

other.www.example.com

example.com

art.example.com

other.example.com

art.example.com

art.example.com

other.art.example.com

example.com

www.example.com

other.example.com

.example.com

example.com

art.example.com

www.example.com

any.other.example.com

  • Tablo ile ilgili istisnai durumlar, güvenlik kısmında ayrıca tartışılacaktır.

path : Path değeri, Cookie'ler için opsiyonel olan bir diğer alandır. Cookie'nin hangi pathlere yapılan isteklerle beraber gönderileceğini belirtir. Browser, kullanıcıdan bir URL için istek geldiğinde, sakladığı Cookie listesinden hangi Cookie'lerin göndereceğine karar verirken, domain'den hemen sonra bu özelliğe bakar. Buradaki işlem domain eşleşmesinde yapılan Tail Comparing yani sağdan sola, sondan başa kıyaslamasının tam tersidir. Dizin kapsamı/kavramı ile doğru orantılı bir mukayese yapılır. Buna göre eğer bir Cookie'nin path değeri;

 "/"  ise, example.com 'a yapılan tüm çağrılara bu Cookie eklenir.

"/foo" ise, example.com/foo ve example.com/foo/baz , example.com/foo/baz/… çağrılarına Cookie eklenir.

Eğer Cookie ile birlikte bir path değeri set edilmedi ise, varsayılan path değeri olarak Cookie'yi set eden sayfanın path değeri kabul edilir.

Set-Cookie: Scanner=Netsparker; path=/foo

secure:  Cookie'yi secure olarak bildirdiğimizde, yukarıdaki şartlara ilaveten (domain ve path eşleşmesi), eğer bağlantı tipi HTTPS yani güvenli bir bağlantı ise gönderilebileceğini belirtiyoruz. Bu opsiyonel değer set edilmediğinde güvenli ya da değil domain ve path şartına uyan tüm isteklerle birlikte gönderilir. Unutulmaması gereken bir diğer önemli nokta da secure tanımlı bir Cookie'yi ancak HTTPS olarak yapılmış bir isteğin yanıtında set edebiliriz.

expires: Cookie'nin browserda tutulacağı süreyi belirler. Opsiyonel bir değer olduğu için, belirtilmediği takdirde, oturum süresince browser belleğinde tutulur. Browserın kapanması ile birlikte de silinir. "expires" değeri için beklenen format: Wdy, DD-Mon-YYYY HH:MM:SS GMT

Set-Cookie: Scanner=Netsparker; domain=example.com; path=/; expires=Sun, 21-02-2016 08:25:01 GMT

"expires" seçeneği için bir değer belirtmediğiniz takdirde, Cookie "Session" olarak işaretlenecektir. Yani Cookie'nin ömrü browser açık kaldığı müddetçe devam edecek; browserın kapanması ile birlikte Cookie de browser belleğinden silinecektir. Eğer bir Cookie "expires" değeri olarak ileri bir tarihle birlikte set edildiyse bu tip Cookie'ler de Persistent Cookie olarak tanımlanmaktadır.

Bir Cookie'yi istemci browserından silmek istediğimizde yine "expires" değerine müracaat ediyoruz. "expires" değeri olarak geçmiş bir tarih atandığında, Cookie, browser belleğinden silinecektir.

Set-Cookie: Scanner=Netsparker; domain=example.com; path=/; expires=Sun, 21-02-1977 08:25:01 GMT

max-age: Expires attribute'ü ile aynı işlevi görmektedir. Cookie'nin browserımızda saklanacağı süreyi belirtir, fakat bir farkla. Expires attribute'ünde bu değeri  Wdy, DD-Mon-YYYY HH:MM:SS GMT formatında belirtmek gerekirken, max-age değeri ile Cookie'nin saklanacağı süreyi saniye cinsinden belirtebiliriz.

Set-Cookie: Scanner=Netsparker; domain=example.com; path=/; max-age=86400

IE6, IE7 ve IE8 browserında max-age attribute'ü desteklenmemektedir. max-age attribute'ünü destekleyen browserlarda, cookie talimatı hem max-age hem de expires attribute'ünü içeriyorsa max-age attribute'ü dikkate alınır.

Negatif bir değer yahut 0 (sıfır) olarak belirtildiği takdirde cookie browser belleğinden silinir.

Set-Cookie: Scanner=Netsparker; domain=example.com; path=/; max-age=0

ya da

Set-Cookie: Scanner=Netsparker; domain=example.com; path=/; max-age=-1

Bir Cookie'yi browser belleğinde benzersiz kılan dört özellik vardır. Bu değerler name, domain, path ve secure değerleridir

İsim dışındaki, yani zorunlu parametreler dışındaki tüm opsiyonel parametreleri arzu ettiğiniz sırayla belirtebilirsiniz:

Set-Cookie: Scanner=Netsparker; domain=example.com; path=/; secure
Set-Cookie: Scanner=Netsparker; secure; domain=example.com; path=/
Set-Cookie: Scanner=Netsparker; path=/; domain=example.com; secure

Eğer bir Cookie'nin değerini değiştirmek istiyorsanız, yukarıda saydığımız bir Cookie'nin tekilliğini oluşturan değerler ile birlikte göndermelisiniz.

Örneğin, Netsparker değeri ile set ettiğimiz Scanner Cookie'sini değiştirmek için:

Set-Cookie: Scanner=NetsparkerCloud; path=/; domain=example.com; secure

Fakat Cookie'nin ayırıcı niteliklerinden biri bile değiştiğinde, örneğin path değerini "/foo" olarak değiştirip Cookie'yi gönderdiğimizde, istemci eski Cookie'nin değerinin üzerine yazmak yerine, yeni bir Cookie oluşturulacaktır:

Set-Cookie: Scanner=NetsparkerCloud; path=/foo; domain=example.com; secure

Cookie'lerin browser belleğinden otomatik olarak silinmesinin bazı şartları vardır. Bu şartlar:

  • Session tipindeki Cookie'ler browser kapandığında otomatik browser belleğinden silinmektedir.
  • Persistent Cookie'ler, expires ya da max-age'de belirtilen saniye cinsindeki değerleri aşıldıktan sonra otomatik olarak silinmektedir.
  • Browser Cookie limitleri aşıldığında, yeni Cookie'lere yer açmak için Cookie'ler silinebilmektedir. Browser Cookie limitleri ile ilgili olarak http://browsercookielimits.squawky.net/ adresinde bulunan tabloya göz atabilirsiniz.

Cookie limitleri için özetle şunları söylemek mümkün, hem browserların hem de web trafiğini korumak için Cookie sayılarında ve içerdiği verilerin boyutlarına limit konulmuştur. Browserlar için bu limitler anlamlı gelebilir ama sunucu trafiği için ne anlama geldiğini merak ediyor olmalısınız. Yazının devamında göreceğimiz üzere, browserda saklanan Cookie'ler, ilgili web sitelerine istek yapılmak istendiğinde, bu istekle beraber sunucuya gönderildikleri için, sunucu trafiğini de doğrudan etkileyeceklerdir.

Cookie mekanizmasını açıklayan orjinal metinde domain başına Cookie limiti 20 olarak belirtilse de, Microsoft IE ile başlayarak bu limitler yavaş yavaş artmış, Safari ve Chrome browserlarında ise tamamen adet limiti kaldırılmıştır.

Cookie'lerin içereceği data miktarı ise, spesifikasyonun ilk günlerinden bu yana 4KB olarak sabittir.

Javascript ve Cookie'ler

"document.cookie" fonksiyonunu kullanarak Javascript ile Cookie oluşturulabilir ve Cookie'lerin içeriğini değiştirebilirsiniz.

Browserda saklanan ilgili siteye ait Cookieler'i görmek için browserın konsol ekranından "document.cookie"  attribute'ünü parametre aktarmadan çağırmanız yeterli. "document.cookie" çağırımına yanıt olarak Cookie'leri aşağıdaki formatta görebileceksiniz.

Cookie1=Cookie1Val; Cookie2=Cookie2Val;

Javascript üzerinden Cookie set etmek istediğinizde ise aşağıdaki kodu kullanabilirsiniz;

document.cookie="name=Netsparker; domain=example.com; path=/";

"document.cookie" fonksiyonu, HTTP isteğinde siteye ait Cookie'lerin hangi biçimde gönderileceği konusunda ipucu verecektir. "document.cookie" fonksiyonunun ürettiği çıktı, aşağıdaki HTTP isteğinden de görüleceği üzere, sadece Cookie isim ve değerini içermektedir.

GET http://example.com HTTP/1.1
Host: example.com
Cookie: name=Netsparker; name2=NetsparkerCloud

Cookie'ye ait domain, path, secure ve expires değerlerini document.cookie çağırımının çıktısı olarak göremezsiniz. Cookie'ye ait bu değerleri incelemek için tüm modern browserlarda bulunan Developer Tools ya da debug fonksiyonlarını ya da EditThisCookie ismindeki browser pluginini kullanabilirsiniz.

httpOnly

Cookie'lerin Javascript tarafından DOM nesnesinin niteliklerine ulaşarak okunabildiğini, değiştirilebildiğini ve silinebildiğini söyledik. Bugünün web dünyasında, kullanıcıya ait pek çok değerin, daha zengin bir etkileşim için saklanıp, Javascript kütüphaneleri tarafından okunabilmesinin sayısız faydaları olsa da, bazı Cookie'lerin, örneğin Javascript'i hiç ilgilendirmeyen, sadece kullanıcının yetkili bir kişi olduğunu server'a bildirmek için set edilmiş Session Cookie'lerini okuyabilmesinin gereği yok. Gerek olmadığı gibi böyle bir durum, XSS zafiyeti gibi diğer parametrelerle bir arada düşünüldüğünde oldukça tehlikeli bir hâl alabilir.

Bu sebepten ötürü 2002 yılında Microsoft tarafından geliştirilen[3] ve sonrasında Cookie spesifikasyonuna da eklenip, bugün tüm modern browserlar tarafından desteklenen httpOnly flag'ı kullanıma sunulmuştur.

Sunucu tarafından browser belleğine kaydedilen Cookie eğer bir httpOnly belirteci içeriyor ise, bu Cookie yalnızca HTTP isteklerine eklenir ve Javascript tarafından okunup, manipüle edilemez.

httpOnly belirteci ile bir Cookie tanımlamak için, Cookie tanımına httpOnly ifadesini eklemek yeterlidir:

Set-Cookie: PHPSESSID=tgce245t7alseaugc36nvbu681; domain=lab.local; path=/; httpOnly


Yukarıda ana hatları ile Cookie'leri inceledik. Şimdi 1994 yılından günümüze, derece derece Cookie ve Session yönetiminin nasıl geliştiğini örnek bir vaka ile inceleyelim.

Bir web sitesi sahibi olarak bütün ziyaretçiler başımızın tacıdır. Ama mutlaka bazı ziyaretçilerin diğerlerinden ayrıcalıklı olduğu alanlar var. Hiç değilse sayfamızın editörleri, bizden hizmet satın alan kullanıcılar bazı içerikleri görmek için daha yetkili.

Sayfamıza bir login formu koyarak ziyaretçilerin kendilerini tanıtmalarını ve hakettikleri özel muameleyle ağırlanmalarını arzu ediyoruz. HTTP'nin unutkan (stateless) bir protokol olduğundan söz etmiş idik. Kullanıcının her gezdiği sayfada yetkili olup olmadığını anlamak için tekrar tekrar kullanıcı adı ve parolalarını sormak, iyi niyetle yaptığımız işi, elimize yüzümüze bulaştırmamıza sebep olabilir. İşte tam da bu noktada, kullanıcımız bir kere geçerli bir kullanıcı adı ve parola ile sisteme girdiğinde, artık gezinimi esnasında onu rahatsız etmemize gerek kalmayacak. Bunun için başarılı login ile birlikte bu kullanıcının login olan bir kullanıcı olduğunun her yeni istekte otomatik olarak bize bildirilmesini sağlayacak bir Cookie set ediyoruz:

Set-Cookie: isLogged=Yes;

Bu Cookie ile birlikte, kullanıcımızın adını da bir yerlere not edip, ismi ile hitap etmemiz yerinde olacaktır. Bir HTTP cevabı içerisinde Set-Cookie headerı kullanarak, tarayıcılarımızın limitleri dahilinde birden fazla Cookie tanımlaması yapabiliriz. Başarılı girişten sonra cevabımız şöyle olacak:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Set-Cookie: isLogged=Yes;
Set-Cookie: username=Customer;

Sunucu tarafındaki kontrol ise:

<?php
if($_COOKIE["isLogged"]=="Yes") {
        echo "Welcome ".$_COOKIE["name"];
} else {
        echo "Please log in!";
}
?>

Düz metin dosyaları olarak bilgisayarımızda saklanan Cookie'ler maalesef her zaman browserda durduğu gibi durmuyor. "document.cookie" örneğinde gördüğümüz gibi, kötü niyetli bir kullanıcı da istediği vakit bizim sitemizle ilgili bir Cookie tanımlaması yapabilir:

Cookie manipüle edildikten sonra:

Session'lar

Web uygulamalarımızın iş akışlarını, karar mekanizmalarını ve yetki süreçlerini etkileyecek değişken ve değerleri istemci tarafında saldırıya maruz kalacak bir şekilde bırakmak yerine, uygulamamız için daha güvenli olan sunucu tarafında tutmanın daha uygun olacağı düşünülmüş olacak ki, sunucu tarafında da Cookie'lere çok benzeyen bir mekanizma olan Session mekanizması geliştirildi.

Session teknolojisi ile beraber, istemci tarafında kritik datalar bırakmak yerine, sunucu tarafında tuttuğumuz kritik datalara referans olacak bir anahtar değeri istemci belleğinde tutmak tercih edilmeye başlandı.

Kullanıcı sunucuya yaptığı her yeni istek ile birlikte, "Benim bilgilerimi tuttuğunuz dosyanın referans numarası şu", diyerek, sunucu tarafında ziyaretçi için saklanan veri koleksiyonuna ulaşılması mümkün kılındı.

Şimdi Session'lara biraz daha yakından bakalım.

session_start()

session_start() fonksiyonu çağrıldığında, PHP'nin ilk yapacağı iş yapılandırma dosyasında belirtilen isimde (default olarak PHPSESSID) bir Cookie gönderilip gönderilmediğini kontrol etmek olacak:

PHP'nin oturum dosyalarını hangi dizinde sakladığını yine phpinfo() çıktısından rahatlıkla öğrenebiliriz:

Eğer PHPSESSID Cookie'si istek ile birlikte gönderilmedi ise, PHP isteğe cevaben, browser'a PHPSESSID isminde bir Cookie set etmesini ve bundan sonra yaptığı isteklere bu değeri de eklemesini isteyecek:

İstemci ve sunucu iletişiminde, birbirlerinin talimatlarına riayet ettikleri takdirde gayet iyi iletişim kuracağını bilen istemci de bir sonraki istekte yine kendisini tanıtmak için bu Cookie'yi istekle birlikte gönderir:

"session_start" fonksiyonunun iki işlem yaptığından söz etmiştik. Öncelikle kendisi gelen istekte PHPSESSID adında bir Cookie olup olmadığını kontrol edecektir. Bu değer PHPSESSID değeri,  istekte kendisine ulaştırıldığı için, şimdi ikinci bir iş yapacak, oturum dosyalarını tuttuğu klasörde PHPSESSID Cookie ile iletilen referans değerinde bir dosya olup olmadığını kontrol edecek:

Eğer dosya yoksa yine bu dizinde "sess_sp39odgr48v3e5d3dsl6dvcn46" isimli dosyayı oluşturacak; eğer dosya var ise dosyayı açacak ve dosya içerisindeki değeri $_SESSION  isminde bir süper global değişkene aktaracak.

Bu Session dosyalarında verilerin nasıl saklandığını merak ediyor musunuz? Aşağıdaki örnekle üzerinden hep birlikte inceleyelim:

<?php
        session_start();
        if(authenticate($user, $paswd)) {
                $_SESSION["loggedIn"] = "yes";
                $_SESSION["last_login"] = date("Y-m-d H:i:s");
} else {
    echo "Try again!";
}
?>

Daha okunaklı bir oturum çıktısını PHP'den de alabiliriz:

<?php
session_start();
var_dump($_SESSION);
        
?>


array(2) { ["loggedIn"]=> string(3) "yes" ["last_login"]=> string(19) "2016-02-19 13:48:20" }

Oturumları Saklamak İçin Daha Fazla Seçenek

PHP varsayılan olarak oturum verilerini text dosyalarında tutmaktadır. Oturum dosyalarına çok hızlı erişip, okumasının sağladığı avantaj bir yana bazı güvenlik tedbirleri ve yapısal nedenlerden ötürü oturumları başka ortamlarda saklamak isteyebilirsiniz.

Oturumların saklandığı dizinde, www-data, yani web sunucusu tam yetkili olduğundan dosyaları dikkatle korumalı ve diğer tüm kullanıcıları tehdit eden Cookie bilgilerinin çalınma riskine karşı tedbirler almalısınız.

Diğer yandan web uygulamalarında dağıtık sistemleri tercih edenler için text dosyalarında oturum bilgilerinin tutulması mümkün olmayacaktır. Birden çok makine bir dosya sistemindeki dosyalara erişebilse bile, sıralı erişim zorunluluğu ve bu erişim esnasında dosyaların kilitlenmesinden ötürü sisteminizi felce uğratabilir.

Neyse ki PHP'nin session handler fonksiyonları sayesinde bir Cookie'nin oluşturulmasından, dilediğimiz ortamda saklanmasına ve silinmesine kadar tüm olayları yönetmek ve customize edebilmek mümkün. Bunun için PHP'nin session_set_save_handler() fonksiyonunu kullanarak, oturum başlatılmasından, oturum sonlandırılmasına kadar ki tüm olaylarda tetiklenecek fonksiyonlar bildirmeliyiz:

bool session_set_save_handler ( callable $open , callable $close , callable $read , callable $write , callable $destroy , callable $gc [, callable $create_sid ] )

Bu fonksiyona parametre olarak atanacak 6 olay şu şekildedir:

  • callable $open(string $savePath, string $sessionName) - session_start fonksiyonu ile oturumun başlatılması ile beraber çağırılacak fonksiyon. TRUE ya da FALSE değer dönmelidir.
  • callable $close - Bir nevi class'ların destructor metodu gibi çalışır. TRUE ya da FALSE değer dönmelidir.
  • callable $read(string $sessionId) - Serialize edilmiş bir oturum verisi dönmelidir.  Buradan dönen değer unserialize edilerek $_SESSION super globaline aktarılır.
  • callable $write(string $sessionId, string $data) - Oturum kapatılacağı ya da oturum değerleri kaydedileceği zaman çağırılır.
  • callable destroy($sessionId) - Oturum sonlandırıldığında, session_destroy fonksiyonunuyla çağırılır. TRUE ya da FALSE dönmelidir.
  • callable gc($lifetime) - PHP tarafından random bir şekilde çağırılarak, eski oturum datalarının temizlenmesini sağlar.
  • create_sid()  - Opsiyonel, yeni bir Session anahtarına ihtiyaç duyulduğunda çağırılır. String tipinde bir veri dönmelidir.

Güvenlik Açısından Session ve Cookie'ler

Bu kısımda bir attack surface'i olarak Cookie'nin tüm bileşenlerini ele alıp, olası saldırı ve etkilerini, korunma yöntemlerini ele alacağız.

Yukarıda belirttiğimiz gibi bir Cookie, name-value çifti, expires, path, domain değerleri ve httpOnly, secure belirteçlerinden meydana gelmektedir.

name[=value]

Bir Cookie tanımındaki tek zorunlu alan olan name alanı, alabileceği name ve value değerleri için, ayrı ayrı saldırgan perspektifinden değerlendirilebilir.

name, tek başına bir saldırı vektörü olmasa da uygulama hakkında fikir vererek saldırganın hedefi tanımasına ve atak stratejisini özelleştirmesine yol açabilir.

Örneğin PHP uygulamaları varsayılan olarak PHPSESSID cookiesini kullanmaktadır. Aşağıdaki tabloda yaygın platformları/ frameworkleri ve kullandıkları Cookie isimlerini bulabilirsiniz.

Cookie Name

Framework / Platform

JSESSIONID

Java Platform

ASPSESSIONID

IIS WEB Server

ASP.NET_sessionid

Microsoft ASP.NET

CFID/CFTOKEN

Cold Fussion

zope3

Zope

cakephp

CakePHP

kohanasession

Kohana

laravel_session

Laravel

ci_session

Codeigniter

value: Sizi sunucu tarafında benzersiz kılan; sunucu tarafındaki verilerinize referans teşkil eden en önemli noktadır. Ele geçirilmesi durumunda sunucu tarafındaki tüm oturumunuz ele geçirileceğinden, buraya atanan değerin benzersiz olması, tahminin güç ve şifrelenmiş olması önem arz etmektedir.

Aşağıdaki gibi bir Cookie değeri set edildiğini düşünelim

Set-Cookie: SESID=user:attacker

Görüleceği üzere Cookie üzerindeki bu okunaklı değerler, kolaylıkla değiştirilip, sunucudaki başka bir kullanıcın oturumu ele geçirilebilir (Session Enumaration & Session Hijacking).

Set-Cookie: SESID=user:victim

Değer şifrelendiği takdirde bile, kırılması güç bir algoritma seçilmelidir.

Örneğin:

Set-Cookie: SESID=dXNlcjp2aWN0aW0=

Her ne kadar tehlikelerden azade gibi görünse de, Cookie değerinin sonundaki "=" işareti base64 encoding kullanıldığı konusunda güçlü bir sanı uyandıracaktır. Ve  bu değer decode edildiğinde karşımıza çıkacak değer:

dXNlcjp2aWN0aW0=        user:victim

Cookie değerinin rassalığı burada büyük bir önem arz etmektedir. Olabildiğince kullanıcıya ait datalar oturum cookielerinin value'lerinde kullanılmamalıdır. Zor bir işlem olsa da, yeterli sayıda Cookie örneği toplanıp analiz edildiğinde, güçlü bir algoritma ile şifrelense dahi oturum ele geçirme saldırısı başarı ile sonuçlanabilir.

session_start fonksiyonu çağırıldığında, HTTP request'inde Cookie headerı ile gönderilen bir değer olup olmadığı kontrol ediliyor.

Gönderilen bir değer olup olmadığı durumlarına göre - varsayılan olarak - sunucunun[4] birkaç davranışı mevcut:

  1. Eğer HTTP isteğinde bir Cookie gönderilmiş ise;

Bu Cookie değerinde belirtilen değerde bir dosya, /var/lib/php5 dizininde var mı? Eğer var ise, dosya içeriğini $_SESSION dizisine at. Eğer yok ise, gönderilen Cookie değerindeki isim ile /var/lib/php5 dizininde bir dosya oluştur ve oturuma ait bilgileri burada saklamaya devam et.

      2. Eğer HTTP isteğinde bir Cookie headerı yok ise:

Sunucudaki Cookie dizininde (/var/lib/php5) oturum bilgilerini saklamak için bir dosya oluşturup, bu dosyanın adının bir referans olarak dönen yanıtta bir Cookie değeri olarak set et.

Yukarıdaki bilgilerden hareketle, eğer Cookie değerinde rassalığı yüksek, benzersiz bir değer kullanılmaz ve Cookie'nin bu beklenen karakteristiği her istekte kontrol edilmezse web uygulamamız savunmasız kalabilir. Bunun nasıl istismar edilebileceğini hep birlikte görelim.

Oturum sabitlemesi (Session Fixation[5]) olarak bilinen atak ile, web gezintimize saldırganın bize dayattığı cookie kimliğe ile devam etmemiz sağlanabilir. Özellikle de Cookie değeri URL üzerinden alınıyorsa, bu saldırgan için biçilmiş kaftan olacaktır.

http://www.example.com/index.php?PHPSESSID=Attacker

Böyle bir linke tıklanmamız zorlandığında, saldırganın bildiği bir cookie ile oturuma devam edeceğimizden, saldırgan kolaylıkla oturumumuzu ele geçirebilecektir:

Resim: https://www.owasp.org/index.php/Session_fixation

Çözüm

Başarılı bir yetkilendirme işleminden sonra oturum anahtarlarını yeniden oluşturmalı ve bu şekilde kullanıcıya göndermeliyiz:


<?php

$_SESSION['logged_in'] = FALSE;

if (check_login())
{
	session_regenerate_id();
	$_SESSION['logged_in'] = TRUE;
}

domain

Cookie'nin güvenliği açısından çok önemli bir özelliktir. Özellikle de subdomain desteği olan sitelerde büyük önem arz etmektedir.

Cookie istekle birlikte gönderilmeden önce, istek yapılan URL ile, browserın belleğindeki cookielerin domain alanlarında bir eşleştirme işlemi yapıldığından söz etmiş idik. Ancak Tail Comparison olarak bilinen bu eşleştirmede sonuç olumlu ise, diğer kriterlerin kontrolünün yapılacağını söylemiştik.

Tarayıcıların bu konudaki farklı davranışları, Cookie domain özelliğinin iyi anlaşılamamış olması ve giderek "www" kullanımının terkedilmesinin bir moda halini almasının yarattığı bazı güvenlik zafiyetlerini bir örnekle inceleyelim.

Ücretsiz web hizmeti veren badsites.local isimli sitede iki ayrı hesap açıldığını düşünelim:

victim.badsites.local
attacker.badsites.local

Bu adreslere girildiğinde kullanıcılara ait olan web siteleri görüntülenecek. Kullanıcı web içeriğinde bir değişiklik yapmak istediği takdirde badsites.local üzerinden login olup, kontrol panel üzerinden gerekli değişiklikleri yapabiliyor.

Victim kullanıcısı sisteme login oluyor. Login için HTTP isteği:

POST http://badsites.local/control.php HTTP/1.1
Host: badsites.local
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 35

username=victim&password=victim

Sunucudan dönen cevaptaki Cookie'nin domain değerine göre üç farklı durum mevcuttur. Bu üç farklı duruma tarayıcıların verdiği farklı tepkileri hep birlikte görelim:

Durum 1 : Cookie'nin domain değeri set edilmemiştir:

HTTP/1.1 200 OK
Set-Cookie: PHPSESSID=ock3keuidn0t24vrf4hkvtopm0; path=/;
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache

Görüldüğü gibi Cookie set edilirken domain bilgisi belirtilmemiştir. Bu durumda test edilen majör tarayıcılarda[6] davranış cookie'nin badsites.local subdomainine yapılan isteklerle birlikte gönderilmemesi yönünde olacaktır. Fakat IE 11 son güvenlik güncellemelerinde güncellenmedi ise (örneğin 11.0.10240.17443) domain bilgisi set edilmediğinde Cookie'yi badsites.local'in tüm subdomainlerine gönderecektir.

Durum 2: Cookie domain bilgisi, badsites.local olarak edilmiştir:

HTTP/1.1 200 OK
Server: Apache/2.4.7 (Ubuntu)
X-Powered-By: PHP/5.5.9-1ubuntu4.14
Set-Cookie: PHPSESSID=1fr54qg3j9rf77toohcpcsk8h0; path=/; domain=badsites.local
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 66
Content-Type: text/html

IE, Edge, Chrome ve Firefox,  attacker.badsites.local 'e yapılan isteklere, badsites.local tarafından oluşturulan Cookie'yi ekleyecektir:

GET / HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Accept-Language: tr,en-US;q=0.7,en;q=0.3
Host: attacker.badsites.local
Pragma: no-cache
Cookie: PHPSESSID=1fr54qg3j9rf77toohcpcsk8h0

Durum 3: Cookie domain bilgisi, .badsites.local olarak edilmiştir:

HTTP/1.1 200 OK
Set-Cookie: PHPSESSID=q3a20kfes2u6fgvgsrspv0rpf0; path=/; domain=.badsites.local;
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache

IE, Chrome ve Firefox,  attacker.badsites.local 'e yapılan isteklere Cookie'yi ekleyecektir:

GET / HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Accept-Language: tr,en-US;q=0.7,en;q=0.3
Proxy-Connection: Keep-Alive
Host: attacker.badsites.local
Cookie: PHPSESSID=q3a20kfes2u6fgvgsrspv0rpf0

Çözüm

Görüldüğü üzere Cookie domain değeri dikkatle set edilmesi gerekli bir alan. Doğru değerlerin set edilmemesi uygulamamız açısından büyük güvenlik risklerine neden olabilir.

Çözüm olarak www alt etki alanını domainlerinizde kullanmayı zorlayabilirsiniz. Bu durumda domain alanınızı set edin ya da etmeyin, yalnızca www.badsites.local adreslerine yapılan isteklere Cookie'ler eklenecektir. [7]

İkinci bir çözüm olarak da, güvenlik açısından risk doğurabilecek siteleri, ana domaininiz altında host etmeyin. Bu yüzden Github gibi pek çok farklı kod host eden siteler bu işlemi github.com domaini altında değil de örneğin github.io gibi farklı bir domain altında host etmektedir.

path

Cookie güvenliği açısından bir başka kritik özellik. Domain filtresinden hemen sonra browserlar path değerini kontrol etmektedir. Burada da tıpkı domain özelliğinde olduğu gibi, kritik güvenlik zafiyetlerine yol açabilecek önemli ayrıntılar bulunmakta.

Path seçeneği de opsiyonel bir seçenek olduğu için değer belirtmek zorunlu değildir. Default değeri "/" 'dir. Bu durumda domain ve diğer özellikler (expires, secure)  eşleştiği takdirde Cookie istekle birlikte gönderilecektir.

Yukarıda path değerini açıklarken bir ikazda bulunmuş idik: Domain kontrolünün tersine path değeri soldan sağa doğru tek tek ve path değerinin sonundaki "/" işarete kadar eşleşme kontrolü yapılmakta idi.

Yine tarayıcıların burada farklı davranışlarını görmekteyiz. Bu davranışladaki farklılık, uygulamamız için  güvenlik zafiyetlerine sebep olabilir.

Path değerinin sonunda "/" işareti içerdiği ve içermediği iki farklı durumu, Firefox, Edge, Chrome ve IE tarayıcılarında  http://badsites.local/victim, http://badsites.local/victim/sub ve http://badsites.local/victim-fake adreslerine ayrı ayrı istekler yaparak test edeceğiz. Cookie'yi set eden sayfamız http://badsites.local/victim sayfasıdır.

Durum 1 :  path değeri sonunda "/" içermiyor. (Örn: /victim)

HTTP/1.1 200 OK
Set-Cookie: PHPSESSID=r9evv4bft7luq4h7c4l5q8b1o4; path=/victim;
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache

IE ve Edge her üç istekte de Cookie gönderiliyor:

GET /victim/ HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Accept-Language: tr,en-US;q=0.7,en;q=0.3
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko
Proxy-Connection: Keep-Alive
Host: badsites.local
Cookie: PHPSESSID=r9evv4bft7luq4h7c4l5q8b1o4
GET /victim/sub/ HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Accept-Language: tr,en-US;q=0.7,en;q=0.3
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko
Proxy-Connection: Keep-Alive
Host: badsites.local
Cookie: PHPSESSID=r9evv4bft7luq4h7c4l5q8b1o4
GET /victim-fake/ HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Accept-Language: tr,en-US;q=0.7,en;q=0.3
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko
Proxy-Connection: Keep-Alive
Host: badsites.local
Cookie: PHPSESSID=r9evv4bft7luq4h7c4l5q8b1o4

Chrome ve Firefox'da ise sadece http://badsites.local/victim ve http://badsites.local/victim/sub istekleri ile birlikte Cookie gönderiliyor.

Durum 2 :  path değeri sonunda "/" içeriyor. (Örn: /victim/)

HTTP/1.1 200 OK
Set-Cookie: PHPSESSID=j0sbcvo5h8q8a1g6n7l4kmaqo5; path=/victim/;
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache

IE, Edge, Chrome ve Firefox tarayıcılar cookie'yi yalnızca http://badsites.local/victim ve http://badsites.local/victim/sub adreslerine yapılan isteklerde gönderiyorlar.

Tarayıcılardaki bu farklılık nedeniyle, alt dizinlerde host edilen siteler, farklı dizinler için set edilmiş oturum değişkenlerine erişebilir. Özellikle de Apache mod_userdir gibi bir modülü kullanıyorsanız, Cookie'ler ile birlikte set edilen değeri gözden geçirmelisiniz.

Çözüm

Cookie ile birlikte path değeri set edilecekse, path değerinin sonuna "/" işareti koymalısınız.

expires ve max-age

Cookie'lerin browserdaki ömrünü belirleyen bu ayarlar, opsiyoneldir. Değer belirtilmediği takdirde Cookie, Session tipinde kabul edilecek ve tarayıcı açık kaldığı müddetçe geçerli olacaktır. Tarayıcı kapatıldığı anda bu cookie'ler tarayıcı belleğinden silinecektir.

Cookie'nin amacına en uygun zaman aşımı değeri set edilmelidir.  Uzun süreli expire, max-age değerleri, ortak kullanılan bilgisayarlarda güvenlik risklerine yol açabilir.  

Yukarıda da söz ettiğimiz üzere expire ve max-age değeri birbirlerinin yerine kullanılabilir. Aynı anda kullanıldıkları takdirde tarayıcılar max-age değerini dikkate alacaklardır.

httpOnly

Cookie'ler kullanıcının oturum yönetiminde kullanıldığı gibi, uygulamaların kullanıcı tercihlerini özelleştirmek istediklerinde de kullanabilecekleri bir alandır. Pek çok script, özelleştirme ile ilgili ayarlarını Cookie'ler olarak saklamakta ve sık sık Javascript ile bu ayarlara erişip, modifiye etmektedir.

Örneğin kullanıcının dil tercihleri için:

Set-Cookie: prefLanguage=tr;

Ya da videoları tam ekran izlemek isteyen bir kullanıcı için, bu isteğin hatırlanması maksadıyla aşağıdaki gibi bir Cookie set edilebilir:

Set-Cookie: fullScreen=yes;

Fakat oturum bilgilerinin saklandığı Cookie'ler yalnızca sunucuyu ilgilendirmekte, sunucuda oturuma ait bilgiler için bir referans tutmaktadır. Bu anlamda Javascript tarafından okunması gerekli değildir. Gerekli olmadığı gibi, bazı durumlarda, örneğin uygulamada XSS zafiyeti mevcut ise oturum bilgilerinin çalınmasına dahi yol açabilir. Oturum bilgilerini tutan Cookie'ler için httpOnly değeri set edilmelidir.

secure

Bilindiği gibi HTTP istekleri istemci ve sunucu arasında plain text, yani düz metin olarak transfer edilmektedir. MiTM (Man in the Middle) saldırısı ile ağı dinleyen biri, veri iletişimi esnasında verileri ve web gezintimiz için en önemli datalardan biri olan oturum bilgilerimizi görebilir, ele geçirebilir.

Bu tehlikeyi bertaraf etmek için cookie'lerin yalnızca güvenli (HTTPS) bağlantılar ile birlikte gönderilmesini Cookie tanımlama işleminde secure belirtecini kullanarak sağlayabiliriz.

Fakat oturum cookieleri dışındaki cookielerin güvensiz bağlantı üzerinden gönderilmeye devam etmesi gizliliğinize dair çeşitli sorunlara yol açabilir. Konunun ayrıntılarını Cookie Hijacking'in Güvenlik ve Gizliliğe Etkileri başlığı ile Netsparker Türkiye Blogu'nda yayınlanan Blackhat 2016 USA sunum notlarında bulabilirsiniz.

SameSite

Tarayıcılar kendisinden X bir kaynağa istek yapıldığında Cookie listesinde, bu kaynakla eşleşen bir Cookie olup olmadığına bakarak, eşleşen bir Cookie olması durumunda (domain - path şayet set edildiyse secure ve protokol eşleştirmeleri) bu istekle birlikte ilgili Cookie'leri gönderir.

Bu işlem, üçüncü bir kaynaktan yüklediğiniz Javascript, CSS, embed edilmiş diğer içeriklerde de aynen tekrarlanır.

Burada güvenlik açısından kritik nokta şudur. Siz bir A sitesine girdiğinizde, A sitesi üzerinden B sitesine yapılan herhangi bir istekte de B sitesine ait Cookie isteğe eklenecektir. Dolayısı ile B sitesi üzerinde devam eden bir oturum, bu yolla kullanılabilecek, daha kötüsü istismar edilebilecektir.

Güvenlik terminolojisinde CSRF - Sea Surf - Cross Site Scripting (CSRF- Sea Surf), olarak bilinen zafiyet -Türkçe karşılığı ile Siteler Arası İstek Sahteciliği- hali hazırda yetkili olan bir kullanıcı oturumunun üçüncü taraflar tarafından istismar edilmesi işlemidir.

Yine bu üçüncü taraf erişimler, reklam ve takip amaçlı da kullanılabilirler. Bunun altında yatan mekanizması ise kısaca şöyle. Siz bir siteye girdiğinizde (example.com), tarayıcınız bu siteye yerleştirilmiş  bir Facebook Like butonu, bir Google Analytics kodu sebebiyle bu kaynaklara istek yapar ve bu isteklerle birlikte browserda bu kaynaklar tarafından set edilmiş Cookie'ler  gönderilir. İstekle birlikte bu isteğin kaynaklandığı sitenin adı (example.com), Referer bilgisi olarak istekte yerini alır. Böylece bu üçüncü taraflar, Cookie ve Referer bilgisini birleştirerek, hangi kullanıcının hangi siteye eriştiğinin kayıtlarını tutabilirler.

Normalde bu tarz izlemelerinin önünü alabilmek için Firefox ve Chrome'da üçüncü taraflardan kaynaklanan isteklere Cookie eklenmesi özelliğini kapatabilmek mümkün, fakat bu, tüm siteleri ve tüm Cookie'leri engelleyeceği için HTTP navigasyonunu olumsuz yönde etkileyecektir. Böyle bir hamle izlenmeyi ve CSRF'i engelleyecek ama attığımız taş ürküttüğümüz kuşa değecek mi?

Chrome 51 ve Opera 39 browserlarına eklenen SameSite Cookie özelliği ile, site sahipleri, istemciye yolladıkları Cookie'leri SameSite parametresi ile kontrol edip, üçüncü taraflardan kaynaklanan isteklere eklenip eklenmeyeceği konusunda tarayıcı davranışını kontrol edebiliyorlar.

Oldukça kullanışlı olan bu özellik sayesinde, tüm Cookie'lerin gönderimini iptal etmek yerine, arzu ettiğiniz Cookie için SameSite özelliğini set edebilirsiniz.

SameSite Cookie set edilmesi gayet basit. HTTP spesifikasyonunda yer alan Cookie talimatına ek olarak SameSite=Lax veya SameSite=Strict parametrelerini eklemeniz yeterli.

Set-Cookie: CookieName=CookieValue; SameSite=Lax;
Set-Cookie: CookieName=CookieValue; SameSite=Strict;

Strict ve Lax

Strict: Adından da anlaşılacağı üzere, SameSite kuralının en katı biçimde uygulandığı opsiyondur. SameSite parametresi Strict olarak set edildiğinde üçüncü taraflardan kaynaklanan hiçbir isteğe SameSite olarak set edilen Cookie gönderilmeyecektir.

Lax: Strict olarak set ettiğimiz bir Cookie, HTTP navigasyonumuzu olumsuz yönde etkileyebilecektir. Örneğin bir sayfadan, bir Facebook profil sayfasına link verildiğinde bu linki tıklayarak navigasyona devam ettiğimizde, eğer Facebook.com oturum Cookie'lerini SameSite=Strict olarak set ettiyse, Facebook.com sayfası tekrar oturum açmamızı isteyecektir.

Çünkü üçüncü taraf bir siteden Facebook.com 'a yönelen bu isteğe Strict olarak işaretlenen Cookie eklenmeyecektir.

İşte navigasyonumuzu etkileyen bu gibi engellerin önünü alabilmek için Lax parametresi kullanılabilir.  SameSite=Lax parametresi ile üçüncü taraf bir siteden kaynaklanan GET isteklerine Lax olarak set edilmiş Cookie eklenecektir.

Buradaki kıstas şudur, GET navigasyonu ile yani HTTP protokolündeki manası ile bir kaynak erişimi için kullanılan GET isteğinin TOP LEVEL bir navigasyona neden olması gereklidir, Yani yapılan istek adres çubuğunda bir değişikliğe sebep olacak, main window'da ilgili site görüntülenecektir. Ancak bu koşulda Lax olarak işaretlenmiş bir Cookie, üçüncü taraf bir siteden kaynaklanan isteğe eklenecektir.

Biraz daha açalım:

Bildiğiniz üzere bir kaynağı iframe, img, script tagları ile de yükleyebilirsiniz. Bu istekler browser tarafından GET fiili ile işlenecektir ama TOP LEVEL bir navigasyona, yani basit anlamıyla browser adres çubuğundaki adres alanında bir değişikliğe neden olmadıkları için bu isteklerle beraber Lax olarak set edilmiş Cookie'ler gönderilmeyecektir.

Özetleyecek olursak:

Cookie Prefixes

Cookie ve Session işleyişindeki en hassas nokta yazının başında da belirttiğimiz üzere sunucunun cookie value alanında bulunan değer ile eşleşen dosyayı diskinden bularak içerisindeki datayı bir oturum değişkenine aktarması; şayet böyle bir dosya yoksa da bu isimle bir dosya oluşturup kullanıcı oturumuna bu cookie ile devam edebilmesi.

Elbette geliştiriciler tarafından ekstra tedbirler alınmadığında geçerli olacak bir saldırı türünden bahsediyoruz. Örneğin her oturum açıldığında cookie değeri yenileniyorsa sözünü ettiğimiz Session Fixation (oturum sabitleme) saldırısı mümkün olmayacaktır. Bu, Cookie Prefixes'ın varlık sebeplerinden ilki.

Diğeri ise yukarıdaki senaryonun bir cüzü niteliğinde. Bir Cookie'yi tekilleştiren öğelerin domain, path ve name olduğunu yukarıda alıntılamıştık. Browser açısından Cookie'nin diğer flagleri yani secure, httpOnly ve SameSite cookienin cookie koleksiyonunda (Cookie Jar) tekil yapan unsurlar arasında sayılmıyor.

Örneğin aşağıdaki gibi bir Cookie set ettiniz

Set-Cookie: MyCookie=value; path=/;

Bu Cookie aşağıdaki şekillerde override edilebilir:

Set-Cookie: MyCookie=newvalue; path=/; secure;
Set-Cookie: MyCookie=newvalue; path=/; httpOnly;

Şöyle bir senaryo da mümkün olabilir. Sitenizde bulunan XSS zafiyeti nedeniyle httpOnly flagı set edilmiş Cookie'nize erişemeyen saldırgan aynı oturum Cookie'sini override ederek oturum sabitleyebilir. Tabii oturum sabitleme noktasında da sitenizde gerekli tedbirler alınmamışsa.

Örnek senaryo:

Set-Cookie: MyCookie=value; path=/; httpOnly;

Saldırgan aşağıdaki kod ile Cookie'yi override edebilecektir:

document.cookie="MyCookie=AttackerValue; path=/;"

Aynı şekilde secure olarak set edilmiş bir Cookie'ye ağı dinleyerek erişemeyen bir saldırgan, kullanıcı tarayıcısını ilgili siteye HTTP protokolü üzerinden güvensiz bağlantı yapmaya zorlayarak, secure olan bu Cookie'yi override edebilir.

Ya da IE ve Edge browserlarda mümkün olan şöyle tehlikeli bir senaryo:

example.com'un set ettiği Cookie:

Set-Cookie: MyCookie=SetByExample.com; path=/;

hacker.example.com aşağıdaki gibi bir yanıt döndürüyor:

Set-Cookie: MyCookie=hacked; path=/;

Example.com'un MyCookie isimli cookie'nin de değeri "hacked" olarak override edilecektir.

Peki bunun bir çözümü yok mu? Var! Cookie Prefixes tam olarak güvensiz bağlantılardan, subdomainlerden ve Javascript yolu ile Cookie'nin override edilmesini engelliyor.

__Secure-

__Secure- öneki bir Cookie isminin başına eklendiğinde bu Cookie'nin yalnız güvenli bir bağlantıya dönen yanıt ile override edilmesine izin veriyor.

Örnek bir kullanım:

Set-Cookie: __Secure-MyCookie=value; path=/; secure;

__Host-

__Host- öneki bir Cookie isminin başına eklendiğinde __Secure- ile aynı görevi görüyor. Buna ilaveten Cookie'nin yalnız yine bu Cookie'yi set eden domain üzerinden override edilebilmesini, Cookie'ye ait herhangi bir subdomain üzerinden değiştirilememesini temin ediyor. Bu prefixin kullanılması için Cookie'de domain attribute'ü set edilmemeli ve path "/" olarak set edilmelidir.

Set-Cookie: __Host-MyCookie=value; path=/; secure;

Önemli not, yukarıdaki saldırının sadece IE ve Edge browserlarda mümkün olduğunu söylemiştik. 2010 başlarında Eric Lawrence[8] tarafından önerilen, 2016'da Mike West[9] tarafından gözden geçirilen bu özellik maalesef IE ve Edge browserlar tarafından desteklenmemektedir.

Ek Tedbirler

  • Her bir Cookie'yi tek bir amaç için kullanın. Oturum için kullandığınız Cookie'leri, sitenizdeki başka işlemler için örneğin Password Resetting'de kullanmayın. Cookie'lerin birden fazla amaç için kullanılması, uygulama akışı içerisinde karışıklıklara daha da kötüsü zafiyetlere yol açabilir. Session Puzzling bir Cookie'nin birden fazla operasyon için kullanıldığı durumlarda nasıl zafiyetlere yol açabileceğine dair güzel bir örnek sunmaktadır.[10] 
  • Oturum değişkenlerinin zaman aşımları için, uygulamanın amacına uygun en optimum değerleri kullanın. Örneğin bir forum ya da wiki sitesi için 1 saatlik bir zaman aşımı ideal iken, bir bankacılık uygulamasında 5 dakikalık bir zaman aşımı yeterli olacaktır. Zaman aşımı, oturum değişkenlerinin edilgen kalabileceği maksimum süreyi belirler. Bu süre içerisinde işlem yapılmayan oturum değişkenleri sunucudan silinecektir.
  • Oturum sonlandırmada, değişkenlerin hem sunucu hem de istemci tarafında geçersiz kılındığından emin olmalısınız. Sunucu tarafında oturum  sonlandırılmaz ve sadece istemci tarafında Cookie sonlandırma işlemi ile yetinilirse, Cookie Reply olarak bilinen ataklar söz konusu olabilir.[11] 

Sonuç

Yazı boyunca ziyaretçimizin web uygulamamızla kurduğu ilişkide önemli bir rol oynayan Cookie'lere ve sunucudaki karşılıkları olan oturum nesnelerine değindik.

Fakat maalesef ilk spesifikasyonundan bu yana işlevselliği değişmeden kalan Cookie'lerin özellikleri hala doğru bir biçimde anlaşılabilmiş değil.

Pek çok web uygulaması bu değerlerin doğru ayarlanmamış olmasından ötürü ataklara karşı savunmasız durumda. Otomatize araçların yalnızca belirli bir kısmını test edebildiği bu özelliklerin kontrolleri mutlaka geliştirici ve güvenlik uzmanları tarafından dikkatle yapılmalı; iyileştirmeler mutlaka farklı tarayıcılarda teyit edilmelidir.


[1] http://cookiecontroller.com/internet-cookies/browser-cookies/

[2] https://curl.haxx.se/rfc/cookie_spec.html

[3] https://msdn.microsoft.com/en-us/library/ms533046.aspx

[4] Apache 2.4.7 / PHP 5.5.9 / Ubuntu Linux

[5] https://www.owasp.org/index.php/Session_fixation

[6] Test edilen tarayıcılar: Chrome, 69.0.3497.100, Internet Explorer 11, Mozilla Firefox 44.0.2, Edge 42.17134.1.0

[7] https://jacob.hoffman-andrews.com/README/why-you-need-a-www/

     http://erik.io/blog/2014/03/04/definitive-guide-to-cookie-domains/

[8] https://textslashplain.com/2015/10/09/duct-tape-and-baling-wirecookie-prefixes/

[9] https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis

[10] https://www.owasp.org/index.php/Testing_for_Session_puzzling_(OTG-SESS-008)

[11] https://www.vanstechelman.eu/content/cookie-replay-attacks-in-aspnet-when-using-forms-authentication