Cookie Security
Cookie Security kısmına geçmeden önce Cookie nedir ve neden ihtiyacımız vardır onu bir irdeleyelim. Bu yazıda, cookie güvenliğinin derinliklerine inecek, temel mekanizmaları anlayacak ve web uygulamalarınızı nasıl daha güvenli hale getirebileceğinizi pratik örneklerle göstereceğiz.
Web’in görünmez taşıyıcıları olan cookie’ler (çerezler), kullanıcı deneyimini kişiselleştirmekten oturumları yönetmeye kadar birçok kritik görevi üstlenen basit metin dosyalarıdır. Neden ihtiyacımız var konusuna gelicek olursak HTTP protokolü stateless bir protokoldür. Ne demek oluyor bu stateless? Çok basit: Web sunucusu, her gelen isteği bir öncekiyle ilişkilendirmez. Yani, bir kullanıcı olarak sen az önce giriş yapmış olsan bile, sunucu seni bir sonraki istekte tanımaz. Her istek yepyeni bir misafir gibi değerlendirilir. İşte burada devreye cookie’ler giriyor. Sunucu tarafından “Set-Cookie” header belirteciyle eklenir ve her sonraki istekle birlikte otomatik olarak geri gönderilirler.
Örnek bir Set-Cookie HTTP yanıtı:
HTTP/1.1 200 OK
Content-Type: application/json
Set-Cookie: session=Y3liZXJ3aXNI; Path=/;
Şimdi Cookie’nin özelliklerine göz atmaya başlayalım. Dikkatinizi çekmem gereken bir husus var: Cookie, gönderildiği tüm özellikleri birbirinden noktalı virgül(;) ve boşluk karakteri( ) ile ayrılmaktadır.
Set-Cookie HTTP başlığında gönderilen ilk değer ve tek zorunlu alan, set edilmek istenen cookie’nin kendisi yani “Name=Value” çiftidir. Bu, tarayıcının saklayacağı asıl bilgidir. Sonrasında gelen ve noktalı virgül(;) ile ayrılan kısımlar ise bu cookie’nin nasıl davranacağını belirleyen niteliklerdir.
Websitesinde kullanılan “Name” değeri saldırgan perspektifinden bakıldığında arkada hangi teknolojiler çalıştığı hakkında bilgi verebilir. Örneğin Django frameworkü için django_sessionid
varsayılan olarak kullanılır. Diğer yaygın web frameworkleri ve varsayılan cookie isimleri:
Cookie İsmi | Framework / Platform |
---|---|
PHPSESSID | PHP |
JSESSIONID | Java EE / Spring / JSP |
ASPSESSIONID | Classic ASP |
ASP.NET_SessionId | ASP.NET |
CFID / CFTOKEN | ColdFusion |
zope3 | Zope |
cakephp | CakePHP |
laravel_session | Laravel |
ci_session | CodeIgniter |
PLAY_SESSION | Play Framework |
connect.sid | Express.js (Node.js) |
symfony | Symfony |
django_sessionid | Django |
session | Flask (varsayılan) |
sid | Socket.IO |
weblogic.session.se | Oracle WebLogic Server |
Kullandığımız browser hangi HTTP isteğine Cookie’yi ekleyip eklemeyeceğini nasıl bilebiliyor?
Bunun cevabı da hem cookie’lerin attribute’larında hem de browserın güvenlik mekanizmasında yatıyor. Attribute’lara detaylıca birazdan değineceğiz ama öncelikle browserın özelliğine bakalım. Bir cookie set edilirken “Domain=
” parametresi hiç kullanılmazsa, tarayıcı o cookie’yi “Host-Only” olarak işaretler. Bu, cookie’nin SADECE ve SADECE onu set eden tam host adı (hostname) için geçerli olduğu anlamına gelir.
Bu küçük veri parçacıklarının aslında yanlış yapılandırıldığında büyük güvenlik riskleri taşır. Yanlış yapılandırılmış bir cookie hassas kullanıcı verilerinin çalınmasına, hesapların ele geçirilmesine (session hijacking), siteler arası betik çalıştırma (XSS) ve siteler arası istek sahteciliği (CSRF) gibi saldırılara kapı aralayabilir. Bu saldırıların önüne geçebilmek adına bazı güvenlik mekanizmaları geliştirilmiştir. Gelin şimdi teker teker inceleyelim.
Secure
Cookie güvenliğinin temel taşlarından biri olan Secure flagi adından da anlaşılacağı üzere, cookie’nin sadece güvenli bağlantılar yani HTTPS üzerinden iletilmesini sağlar. Bir cookie Secure bayrağı ile işaretlendiğinde, tarayıcı bu cookie’yi yalnızca web sunucusuna HTTPS bağlantısı üzerinden yapılan isteklerde gönderir. Eğer kullanıcı siteye HTTP üzerinden erişmeye çalışırsa veya bir şekilde HTTP bağlantısına yönlendirilirse, Secure olarak işaretlenmiş cookie’ler sunucuya gönderilmez.
Bu ne için önemlidir? Tabii ki “Ortadaki Adam” (Man-in-the-Middle - MitM) saldırılarını engellemek için önemlidir. Eğer cookie’ler şifresiz HTTP üzerinden gönderiliyorsa, saldırgan bu cookie’leri (özellikle oturum kimlikleri gibi hassas verileri içerenleri) kolayca ele geçirebilir. Bu durum, oturum hırsızlığına (session hijacking) ve kullanıcı hesaplarının yetkisiz erişimine yol açabilir.
Örnek kullanım şu şekildedir:
Set-Cookie: session=Y3liZXJ3aXNI; Secure; Path=/
Daha anlaşılabilir kılmak için şu şekilde örnekleyelim: OWASP Juice Shop’da bulunan hiçbir cookie’de Secure flagi işaretlenmeden atanmış bulunuyor. Bu web sitesine HTTP ve HTTPS üzerinden istek attığımızda cookieler eklenecektir.
“continueCode” ve “cookieconsent_status” cookielerine, Cookie Editor eklentisi aracılığıyla Secure flagi ekleyip hangi cookie’ler HTTP isteklerine ekleniyor BurpSuite ile araya girip bunu gözlemleyelim:
Sadece “language” ve “welcomebanner_status” cookie’leri HTTP isteğine eklendi.
HttpOnly
HttpOnly, bir cookie set edilirken Set-Cookie HTTP yanıt başlığına eklenebilen ek bir bayraktır. Temel amacı, cookie’nin istemci taraflı betikler (yani tarayıcıda çalışan JavaScript) tarafından erişilmesini engellemektir. Bu özellik, özellikle XSS saldırılarının en yaygın hedeflerinden biri olan oturum cookie’lerinin çalınmasını engellemek için geliştirilmiştir. XSS açığı aracılığıyla kullanıcının tarayıcısında zararlı bir betik çalıştırdığında, bu betik normalde document.cookie
API’sini kullanarak oturum cookie’sine erişebilir ve bu bilgiyi kendi sunucusuna gönderebilir.
Sunucu tarafından tarayıcının belleğinde tutulan bir cookie, httpOnly parametresi içeriyorsa bu cookie yalnızca HTTP isteklerine eklenir ve Javascript tarafından okunamaz, manipüle edilemez.
Set-Cookie: sessionId=Y3liZXJ3aXNI; Path=/; Secure; HttpOnly
Daha anlaşılabilir kılmak için şu şekilde örnekleyelim:
Aşağıdaki görselde göründüğü üzere hiçbir cookie’de HttpOnly flagi bulunmuyor.
HttpOnly flagi içermeyen cookie’yi şu şekilde konsoldan çekmeye çalışalım:
Cookie’ye Cookie Editor eklentisiyle HttpOnly flagi ekleyip tekrardan cookie’yi Javascript aracılığıyla almayı deneyelim
“document.cookie” komutunu çalıştırdığımızda HttpOnly flagine sahip cookie’lerin gelmediğini görüyoruz.
Max-Age ve Expires
Cookie yönetiminin bir diğer kritik boyutu daha var: ömürleri. Tarayıcı bir cookie’yi ne kadar süreyle saklamalı ve sunucuya geri göndermeli? Bu sorunun cevabı, cookie’nin “kalıcı” mı yoksa yalnızca mevcut oturum süresince mi geçerli olacağını belirler. İşte bu noktada, cookie ömrünü kontrol etmek için kullanılan iki temel attribute devreye giriyor: Expires ve Max-Age. Bu iki attribute’un ne işe yaradığını, aralarındaki farkları ve hangisini ne zaman tercih etmeniz gerektiğini ele alacağız. Öncelikle Oturum Cookie’leri ve Kalıcı Cookie’ler arasındaki ayrımı yapalım:
Oturum Cookie’leri (Session Cookies): Expires veya Max-Age attribute’ları belirtilmezse, cookie bir oturum cookie’si olur. Bu tür cookie’ler tarayıcı kapatıldığında (veya tarayıcı oturumu sona erdiğinde) otomatik olarak silinir. Oturum yönetimi için sıklıkla kullanılırlar.
Kalıcı Cookie’ler (Persistent Cookies): Expires veya Max-Age attribute’larından en az biri belirtilirse, cookie kalıcı hale gelir. Tarayıcı, belirtilen süre dolana kadar veya manuel olarak silinene kadar tarayıcı kapatılıp açılsa bile bu cookie’yi saklar ve ilgili siteye yapılan isteklerle gönderir.
Örnek kullanımlar şu şekildedir:
Set-Cookie: user_preference=dark_mode; Expires=Wed, 21 Oct 2026 07:28:00 GMT
Set-Cookie: tracking_consent=accepted; Max-Age=31536000; Path=/
Bu cookie, ayarlandığı andan itibaren 31.536.000 saniye (yaklaşık 1 yıl) boyunca geçerli olacaktır.
Eğer bir Set-Cookie başlığında hem Expires hem de Max-Age belirtilirse, modern tarayıcılar Max-Age değerini dikkate alır ve Expires’i görmezden gelir. Eski tarayıcılar (özellikle IE 6, 7, 8) Max-Age’i desteklemediği için yalnızca Expires’i kullanırdı, bu yüzden bazen her ikisi de uyumluluk için eklenebiliyordu, ancak günümüzde bu genellikle gereksizdir.
Modern web geliştirmede, kalıcı cookie’ler için Max-Age kullanmak genellikle en iyi yoldur.
Domain
Domain, cookie’lerin belli subdomainlerde kullanılmasını sıkılaştırmak amacıyla çok kullanışlı bir özelliktir. Yukarıda bahsettiğimiz üzere browserın belleğindeki cookielerin domain alanlarında bir “tail-matching” denilen bir eşleşme işlemi gerçekleştiriliyor. Domain attribute’unun nasıl işlediğini şu örnekle inceleyelim:
Diyelim ki elimizde bir alışveriş sitesi var.
- Ana site:
example.com
- Ürün alt alanı:
shop.example.com
- Kullanıcı alt alanı:
user.example.com
Kullanıcı example.com
üzerinden giriş yaptıktan sonra, bu oturum bilgisini shop.example.com
ve user.example.com
gibi subdomainlerde de kullanmak istiyorsan, cookieler şu şekilde ayarlanmalı:
Set-Cookie: session=Y3liZXJ3aXNI; Domain=example.com; Path=/
Eğer Domain
attribute’unu hiç belirtmeseydik, çerez yalnızca çerezi ayarlayan alan adıyla sınırlı olurdu. Örneğin login.example.com
üzerinden kullanıcı giriş yaptı ve şu şekilde bir cookie atandı:
Set-Cookie: session=Y3liZXJ3aXNI; Path=/
Burada Domain attribute’u belirtilmediği için kullanıcımız shop.example.com
‘a gittiğinde, tarayıcı bu cookie’yi shop.example.com
sunucusuna göndermez, çünkü cookie’nin domain kapsamı (login.example.com
) isteğin yapıldığı domain (shop.example.com
) ile eşleşmez.
Güvenlik açısından dikkat etilmesi gereken hususlar:
- En Az Ayrıcalık Prensibi (Principle of Least Privilege): Eğer bir cookie yalnızca
api.example.com
tarafından kullanılacaksa, onuapi.example.com
‘dan Domain attribute’u belirtmeden ayarlayın. Böylece diğer subdomain’lere cookie gönderilmez. - Subdomain Güvenliği: Güvenilmeyen alt alanlar varsa,
Domain
özniteliğini ayarlamamak daha güvenlidir. Özellikle saldırgan bir alt alan kontrolündeyse,Domain=example.com
ile atanan cookie değeri ile diğer subdomainlere yönelik saldırılar düzenlenebilir. - Diğer Attribute’larla Birlikte Kullanım: Domain attribute’u tek başına yeterli değildir. Güvenliği artırmak için mutlaka “Secure”, “HttpOnly” ve “SameSite” attribute’ları ile birlikte kullanılmalıdır.
Path
Path attribute, çerezin hangi alt dizinlerde (yani URI path’lerinde) geçerli olduğunu belirler. Browserlar, Domain parametresinden sonra Path parametresini kontrol ederler ve eğer uyumluluk sağlanırsa Cookie isteklere eklenir. Detaylarını şu şekilde inceleyelim:
Eğer Path attribute’u açıkça belirtilirse (örneğin, Path=/admin
), cookie yalnızca belirtilen yol ve onun alt yollarına yapılan isteklere eklenir:
Set-Cookie: session=Y3liZXJ3aXNI; Domain=example.com; Path=/admin
Path | Eklenir mi? |
---|---|
/admin/users | ✅ Evet |
/admin/settings/security | ✅ Evet |
/ (Ana dizin) | ❌ Hayır |
/shop | ❌ Hayır |
Eğer Set-Cookie başlığında Path attribute’u belirtilmezse, tarayıcı varsayılan olarak cookie’yi ayarlayan kaynağın (URL’nin) dizinini (directory) Path olarak kabul eder. Örnek HTTP request response akışı:
POST /auth/process-login HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Cookie: some_other_non_session_cookie=value
username=testuser&password=secret123
HTTP/1.1 302 Found
Location: /dashboard <-- Başarılı login sonrası yönlendirme adresi
Set-Cookie: session_id=Y3liZXJ3aXNI
Bu yanıtı alan tarayıcı session_id
cookie’sini saklarken, Path attribute’u belirtilmediği için, cookie’yi ayarlayan URL’nin (/auth/process-login
) dizin yolunu (/auth/
) varsayılan Path olarak kabul eder. Sonuç olarak, https://example.com/auth/user-settings
gibi /auth/
altında bir yola istek yaptığında session_id
cookie’sini gönderecektir. Ancak, sunucunun yönlendirdiği https://example.com/dashboard
adresine veya https://example.com/
ana sayfasına ya da https://example.com/shop
gibi farklı bir dizine istek yaptığında, bu session_id
cookie’sini göndermeyecektir, çünkü bu yollar cookie’nin varsayılan olarak atanan Path=/auth/
kapsamına girmez. Şu tablo ile görselleştirelim:
URI | Eklenir mi? |
---|---|
https://example.com/auth/user-settings | ✅ Evet |
https://example.com/dashboard | ❌ Hayır |
https://example.com/shop | ❌ Hayır |
SameSite
“SameSite” attribute’u Chrome 51 ile eklenen CSRF (Siteler arası istek sahteciliği) saldırılarını engellemeye yönelik geliştirilmiştir. Bu attribute tarayıcıya bir cookie’nin yalnızca cookie’nin ait olduğu site içinden yapılan isteklerle mi, yoksa farklı bir siteden (cross-site) başlatılan isteklerle de mi gönderileceğini söyler. Temel amacı, kullanıcının haberi olmadan, kötü niyetli bir site tarafından kullanıcının cookie’lerini kullanarak başka bir sitede işlem yapılmasını engellemektir. Bu kurallar üçüncü kaynaktan yüklenen CSS ve Javascript içeriklerine de aynen uygulanır.
SameSite attribute’unun çalışma mantığı şu üç temel değere bağlıdır ve bu değerler atanabilir:
- Strict: a. İsminden de kendini ele verdiği üzere en sıkı atanabilecek değer budur ve cookie yalnızca isteğin kaynağı cookie’nin ait olduğu site ile tamamen aynı olduğunda gönderilir, üçüncül hiçbir websitesine cookie eklemez.
- Lax:
a. Cookie, aynı siteden yapılan tüm isteklerle gönderilir. Farklı bir siteden yapılan isteklerde ise yalnızca top-level navigasyona (yani browser adres çubuğunda bir değişiklik oluyorsa) sebep olacak bir GET isteğinde eklenir. Daha detaylı açıklamak gerekirse kullanıcı “another-site.com”daki bir linke tıklayarak “my-site.com”a geldiğinde (bu bir GET isteğidir), Lax cookie’si gönderilir ve kullanıcı oturumu açık kalır. Ancak “another-site.com”un arka planda “my-site.com”a POST isteği yapması veya
<img>
,<iframe>
gibi alt kaynak istekleri ile cookie’yi göndermeye çalışması durumunda cookie gönderilmez. - None:
a. None ise, tüm üçüncül isteklere Cookie eklenecektir. None kullanmak için mutlaka birlikte “Secure” attribute eklenmek zorundadır. Tarayıcılar, Secure olmadan
SameSite=None
olarak işaretlenmiş cookie’leri reddeder. Bu, cookie’nin yalnızca şifreli (HTTPS) bağlantılar üzerinden gönderilmesini garanti altına alarak ortadaki adam saldırı riskini azaltır.
Özetle şu şekilde bir tablo ortaya çıkıyor:
İstek Tipi | Örnek Kod | Cookie’nin Gönderileceği Değer |
---|---|---|
Link | <a href="..."> |
Normal, Lax |
Prerender | <link rel="prerender" href="..."/> |
Normal, Lax |
Form GET | <form method="GET" action="..."> |
Normal, Lax |
Form POST | <form method="POST" action="..."> |
Normal |
Iframe | <iframe src="..."></iframe> |
Normal |
Ajax | $.get("...") |
Normal |
Image | <img src="..."> |
Normal |
__Secure- ve __Host-
Şimdiye kadar Domain, Path, Secure, HttpOnly, SameSite, Max-Age gibi temel attribute’ların önemini ve nasıl çalıştıklarını detaylıca inceledik. Bu attribute’lar, cookie’lerimizi korumak için temel yapı taşlarını oluşturur. Fakat web’in karmaşık doğası ve gelişen tehditler, bazen ek savunma hatlarına ihtiyaç duyar. İşte bu noktada isimleriyle bile dosta güven düşmana korku veren Cookie Prefix’leri devreye giriyor: __Secure-
ve __Host-
Standart attribute’lar harika olsa da, bazı senaryolarda zafiyetler ortaya çıkabilir. Eğer bir uygulamanın daha az güvenli bir subdomain’i (örn. insecure-sub.example.com
) varsa, bu subdomain ana domain veya diğer güvenli subdomain’ler için cookie ayarlayabilir (Domain=example.com
kullanarak). Bu, ana uygulamadaki oturum cookie’lerinin üzerine yazılmasına veya manipüle edilmesine (Session Fixation gibi) yol açabilir.
Tarayıcı geliştiricileri, bu tür sorunlara karşı ek bir güvence sağlamak için cookie isimlerine özel anlamlar yükleyen iki prefix tanımladılar. Tarayıcılar bu prefix’lerle başlayan isimlere sahip cookie’leri yalnızca belirli koşullar sağlandığında kabul eder. Eğer koşullar sağlanmazsa, tarayıcı Set-Cookie başlığını sessizce reddeder ve cookie’yi saklamaz.
“__Secure-” için Kural: “__Secure-” prefix’i ile başlayan bir cookie (örneğin __Secure-SessionID
), Set-Cookie başlığında mutlaka Secure attribute’u ile birlikte gönderilmelidir.
// DOĞRU KULLANIM: Tarayıcı kabul eder.
Set-Cookie: __Secure-SID=12345; Secure; Path=/; HttpOnly; SameSite=Lax
// YANLIŞ KULLANIM: Tarayıcı reddeder (Secure attribute'u eksik).
Set-Cookie: __Secure-SID=67890; Path=/; HttpOnly; SameSite=Lax
“__Host-” için şu kurallar yerine getirilmeli:
- Secure attribute’u bulunmalıdır.
- Path attribute’u “/” olarak ayarlanmalıdır.
- Domain attribute’u belirtilmemelidir.
// DOĞRU KULLANIM:
Set-Cookie: __Host-SID=abcdef; Secure; Path=/; HttpOnly; SameSite=Lax
// YANLIŞ KULLANIM: Tarayıcı reddeder (Domain attribute'u var).
Set-Cookie: __Host-SID=ghijkl; Secure; Path=/; Domain=example.com; HttpOnly; SameSite=Lax
// YANLIŞ KULLANIM: Tarayıcı reddeder (Path=/ değil).
Set-Cookie: __Host-SID=mnopqr; Secure; Path=/users; HttpOnly; SameSite=Lax
// YANLIŞ KULLANIM: Tarayıcı reddeder (Secure attribute'u eksik).
Set-Cookie: __Host-SID=stuvwx; Path=/; HttpOnly; SameSite=Lax
Belki bütün bunlar ilk bakışta çok fazla detay gibi görünebilir, ancak unutmayın ki bu ayarları doğru yapılandırmak, XSS, CSRF ve oturum kaçırma (session hijacking) gibi yaygın saldırılara karşı en önemli savunma hatlarımızdan biri. Bu attribute’ları, web uygulamalarınız için birer güvenlik kalkanı gibi düşünebilirsiniz.
Umarım bu yazı, cookie güvenliği konusundaki farkındalığınızı artırmış ve uygulamalarınızı daha güvenli hale getirme yolculuğunuzda size rehberlik etmiştir.
Güvenli kodlamalar!