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

Kategori: Web Güvenliği - 20 Temmuz 2016 - Ziyahan ALBENİZ
Secure Cookie

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 kazanmalarndaki 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üşterileri 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 yalnızca 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 önemi 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ğllerdir. 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. (Örneğin Set-Cookie: fullScreen; path=/;)

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: 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. Burada eşleşmenin doğrultusu domain'de 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, eğer bağlantı tipi HTTPS yani güvenli bir bağlantı ise gönderilebileceğini belirtiyoruz. Bu opsiyonel değer set edilmediğinde Cookie safe olarak algılanır; 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

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

İ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'sinin 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
  • Session tipindeki Cookie'ler browser kapandığında otomatik browser belleğinden silinmektedir
  • Persistent Cookie'ler, expires 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 Cookiler'i görmek için browserın konsol ekranından "document.cookie" fonksiyonunu parametre aktarmadan çağırmanız yeterli. "document.cookie" fonksiyon çağrımına yanıt olarak Cookie'leri aşağıdaki formatta görebileceksiniz.

Cookie1=Cookie1Val; Cookie2=Cooki2Val;

Javascript üzerinden Cookie set etmek istediğinizde ise;

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 fonksiyonunun çıktısı olarak göremezsiniz. Cookie'ye ait bu değerleri incelemek için tüm majör browserlarda bulunan Developer Tools ya da debug fonksiyonlarını ya da EditThisCookie ismindeki browser pluginini kullanabilirsiniz.

httpOnly

Cookie'lerin Javascript tarafından 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 zafiyteti gibi diğer parametrelerle bir arada düşünüldüğünde oldukça tehlikeli bir hal alabilir.

Bu sebepten ötürü 2002 yılında Microsoft tarafından geliştirilen[3] ve sonrasında Cookie spesifikasyonuna da eklenip, bügün tüm modern browserlar tarafından desteklenen httpOnly flag'ı kullanıcılara 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

secure httpOnly-1 secure httpOnly-2

Yukarıda anahatları 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 biraz 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 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 şifrelerini 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 şifre ile sisteme girdiğinde, artık gezinimi esnasında O'nu 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;
<?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" komutu ö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:

secure httpOnly-3

Cookie manipüle edildikten sonra:

secure httpOnly-4

Session

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 böylesine 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, Session mekanizması geliştirildi.

Session teknolojisi ile beraber, istemci tarafında kritik datalar bırakmak yerine, sunucu tarafında tuttuğumuz kritik datalara referrans 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 bilgi 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:

secure httpOnly-5

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

secure httpOnly-6

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:

secure httpOnly-7

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

secure httpOnly-8

"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:

secure httpOnly-9

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!";
}

?>

secure httpOnly-10

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.

secure httpOnly-11

Oturumların saklandığı dizinde www-data yani web sunucusu tam yetkili olduğundan dosyaları dikkatle korumalı ve diğer tüm kulanı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şsebilse 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 kişiselleştirmek 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 superglobaline 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 perpektifinden 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ştirilp, 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 edilğ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 (Apache 2.4.7 / PHP 5.5.9 / Ubuntu Linux) birkaç davranışı mevcut:

  • a)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ğeri ile /var/lib/php5 dizininde bir dosya oluştur ve oturuma ait bilgileri burada saklamaya devam et.

  • b)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ı 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) olarak bilinen bu 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:

secure httpOnly-12

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 cookilerin 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 'da 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şikleri 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
Date: Sat, 27 Feb 2016 20:59:42 GMT
Server: Apache/2.4.7 (Ubuntu)
X-Powered-By: PHP/5.5.9-1ubuntu4.14
Set-Cookie: PHPSESSID=ock3keuidn0t24vrf4hkvtopm0; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
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ş. Bu durumda üç majör tarayıcı da, farklı davranışlar sergilenmekte ve ciddi bir güvenlik zafiyeti meydana gelmektedir.[4]

Eğer domain değeri belirtmemişse, IE 11'de badsites.local etki alanının tüm subdomainlerine yapılan isteklere Cookie eklenecek:

GET / 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: attacker.badsites.local
Pragma: no-cache
Cookie: PHPSESSID=ock3keuidn0t24vrf4hkvtopm0

Victim kullanıcısı ya da badsites.local'e login olmuş biri attacker.badsites.local 'e girdiğinde, attacker.badsites.local sitesi tarafından badsites.local'e ait oturumu ele geçirilecektir:

<?php
  $query = "insert into cookies (cookie) values ('".$_COOKIE["PHPSESID"]."');";
  mysql_query($query);
?>

Chrome ve Firefox tarayıcıları ise Cookie'yi, subdomainlere yapılan istekle birlikte göndermeyecektir.

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

HTTP/1.1 200 OK
Date: Sat, 27 Feb 2016 21:28:13 GMT
Server: Apache/2.4.7 (Ubuntu)
X-Powered-By: PHP/5.5.9-1ubuntu4.14
Set-Cookie: PHPSESSID=1fr54qg3j9rf77toohcpcsk8h0; path=/; domain=badsites.local
Expires: Thu, 19 Nov 1981 08:52:00 GMT
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, 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
Date: Sat, 27 Feb 2016 21:38:02 GMT
Server: Apache/2.4.7 (Ubuntu)
X-Powered-By: PHP/5.5.9-1ubuntu4.14
Set-Cookie: PHPSESSID=q3a20kfes2u6fgvgsrspv0rpf0; path=/; domain=.badsites.local
Expires: Thu, 19 Nov 1981 08:52:00 GMT
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.[5]

İ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 github.io 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, 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ıca 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
Date: Sat, 27 Feb 2016 22:17:59 GMT
Server: Apache/2.4.7 (Ubuntu)
X-Powered-By: PHP/5.5.9-1ubuntu4.14
Set-Cookie: PHPSESSID=r9evv4bft7luq4h7c4l5q8b1o4; path=/victim
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache

IE ile 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
Date: Sat, 27 Feb 2016 22:33:51 GMT
Server: Apache/2.4.7 (Ubuntu)
X-Powered-By: PHP/5.5.9-1ubuntu4.14
Set-Cookie: PHPSESSID=j0sbcvo5h8q8a1g6n7l4kmaqo5; path=/victim/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache

IE, 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 Apace 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

Cookie'lerin browserdaki ömrünü belirleyen bu ayar, opsiyoneldir. Değer belirtilmediği takdirde Cookie, Session tipinde kabul edilecek ve tarayıcı açık kaldığı müddetçe geçerli olacaktır.

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

httpOnly

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

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 zafiyetleri mevcutsa oturum bilgilerinin çalınmasına dahi yol açablir. 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 dosyaları olarak transer edilmektedir. MITM (Man in the Middle) yöntemleriyle 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.

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.[6]
  • 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 sayfası 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 tarfaında geçersiz kılındığından emin olmalısınız. Sunucu tarafında oturum değişkenleri sonlandırılmaz ve sadece istemci tarafında Cookie sonlandırma işlemi ile yetinilirse, Cookie Reply olarak bilinen ataklar söz konusu olabilir.[7]

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 iyi 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] Test edilen tarayıcılar: Chrome, 48.0.2564.116 m, Internet Explorer 11, Mozilla Firefox 44.0.2

[5] https://jacob.hoffman-andrews.com/README/why-you-need-a-www/
http://erik.io/blog/2014/03/04/definitive-guide-to-cookie-domains/

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

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


Web Güvenliği Tarayıcısı

Tam isabet, hızlı ve kolay kullanım

DENEME SÜRÜMÜNÜ İNDİR