Selam TurkTeam, BlueTornado yazıyor.
HTTP güvenlik başlıkları, sunucunun yanıtla birlikte tarayıcıya gönderdiği "bu sayfayı nasıl korumalısın" talimatlarıdır. Başlığı koymak işin yarısıdır; asıl mesele tarayıcının o başlığı nasıl yorumladığını bilmek ve politikayı uygulamanızı bozmadan, uygulama katmanında (kod, framework, middleware) doğru kurmaktır. Bu rehberde ağırlık Content Security Policy (CSP) üzerinde: direktifler, nonce/hash, report-only modu ve ihlal raporlama. Web sunucusu tarafındaki TLS ve rate limit ayarları için forumdaki Nginx sıkılaştırma konusuna bakabilirsiniz; burada o kısmı tekrar etmiyoruz.
Konuda Anlatılacaklar
- Güvenlik başlıkları nedir, tarayıcı bunları nasıl uygular?
- Lab: kendi uygulamanızın başlıklarını okumak
- CSP derinlemesine: direktifler ve kaynak ifadeleri
- Nonce ve hash ile inline script'e güvenli izin
- Report-Only modu ve ihlal raporlarının toplanması
- HSTS, X-Frame-Options, nosniff, Referrer-Policy, Permissions-Policy
- Uygulama katmanında kurulum: Express (helmet), Django, PHP
- Sık yapılan hatalar, sonuç, kaynakça ve yasal uyarı
1) Güvenlik Başlıkları Nedir?
Tarayıcı, bir yanıtı işlerken başlıkları okur ve kendi davranışını kısıtlar. Örneğin CSP, "bu sayfada yalnızca kendi alan adımdan script çalıştır" der; XSS açığı olsa bile enjekte edilen script'in çalışması engellenebilir. Başlıklar açığın kendisini kapatmaz, sömürünün etkisini azaltan ikinci savunma hattıdır (defense in depth). Girdi doğrulama ve çıktı kodlama (output encoding) yine zorunludur.
Alıntı:Kural: Başlıklar tarayıcıda uygulanır. curl veya bir bot bu kurallara uymaz; yani CSP sunucunuzu değil, kullanıcınızın tarayıcısını korur.
2) Lab: Başlıkları Okumak
Önce mevcut durumu görelim. Kendi geliştirme ortamınızda (ör. localhost:3000) çalışan uygulamanız üzerinde:
# Yalnızca yanıt başlıklarını göster
curl -sI http://localhost:3000/
# Belirli başlıkları süz
curl -sI http://localhost:3000/ | grep -iE 'content-security|strict-transport|x-frame|x-content-type|referrer|permissions'Çıktı yorumu: Hiçbir satır dönmüyorsa uygulamanız hiçbir güvenlik başlığı göndermiyor demektir. Tarayıcıda DevTools > Network > ilgili istek > Headers sekmesinden de aynı bilgiyi görebilirsiniz. CSP ihlalleri ise Console sekmesinde ayrıca uyarı olarak çıkar.
[center][/center]
3) Content Security Policy Derinlemesine
CSP, direktif ve kaynak listelerinden oluşan bir politikadır. Direktifler noktalı virgülle ayrılır:
Content-Security-Policy: default-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'Temel direktifler:
- default-src: Ayrıca tanımlanmayan fetch direktifleri için varsayılan (script-src, style-src, img-src, connect-src, font-src vb.)
- script-src: JavaScript kaynakları; XSS'e karşı en kritik direktif
- style-src: CSS kaynakları
- connect-src: fetch, XHR, WebSocket hedefleri
- object-src 'none': Eski eklenti (object/embed) içeriğini kapatır; neredeyse her sitede önerilir
- base-uri: base etiketiyle göreli URL'lerin kaçırılmasını önler
- form-action: Formların hangi adreslere gönderilebileceği
- frame-ancestors: Sayfanızı kimin iframe içine alabileceği (clickjacking koruması)
- upgrade-insecure-requests: Sayfa içindeki http alt kaynak isteklerini https'e yükseltir
Kaynak ifadeleri:
'self''none'https://cdn.ornek.comdata:https:'unsafe-inline''unsafe-eval''strict-dynamic'Meta etiketiyle CSP: Başlık ekleyemediğiniz statik barındırmada politika HTML içine konabilir:
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; object-src 'none'; base-uri 'self'">Dikkat: meta etiketi ile frame-ancestors, report-uri ve sandbox desteklenmez ve politika yalnızca etiketten sonra yüklenen içeriğe uygulanır. Mümkünse HTTP başlığını tercih edin.
4) Nonce ve Hash ile Inline Script
Inline script'leri tamamen kaldırmak en temizidir. Kaldıramıyorsanız 'unsafe-inline' yerine nonce veya hash kullanın.
Nonce: Her yanıtta yeniden üretilen, tahmin edilemez rastgele bir değer. Başlıkta ve script etiketinde aynısı olmalıdır:
Content-Security-Policy: script-src 'nonce-R4nd0mDegerHerIstekteYeni' 'strict-dynamic'; object-src 'none'; base-uri 'self'
<script nonce="R4nd0mDegerHerIstekteYeni">
initApp();
</script>Nonce sabit kodlanırsa veya önbelleğe alınmış sayfada tekrar kullanılırsa koruma anlamını yitirir.
'strict-dynamic'Hash: Değişmeyen inline script için içeriğin SHA-256 özeti:
# Script içeriğinin (etiketler hariç, birebir aynı boşluklarla) hash'i
printf '%s' "initApp();" | openssl dgst -sha256 -binary | openssl base64
# Politikaya ekleme
Content-Security-Policy: script-src 'sha256-<yukaridaki_cikti>'Script içeriğinde tek bir boşluk değişirse hash tutmaz ve tarayıcı script'i engeller; Console'daki hata mesajı genellikle beklenen hash'i de gösterir.
[center][/center]
5) Report-Only ve İhlal Raporları
Mevcut bir uygulamaya doğrudan sıkı CSP koymak genellikle bir şeyleri bozar. Doğru yol önce Content-Security-Policy-Report-Only ile gözlemlemektir: tarayıcı hiçbir şeyi engellemez, yalnızca ihlali raporlar.
Reporting-Endpoints: csp-endpoint="https://uygulamaniz.ornek/csp-report"
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; report-to csp-endpoint; report-uri /csp-report- report-uri: Eski yöntem, spesifikasyonda kullanımdan kaldırılıyor ama hâlâ geniş destekli. Tarayıcı JSON gövdesini application/csp-report türüyle POST eder.
- report-to: Reporting API ile yeni yöntem; uç nokta adı Reporting-Endpoints başlığında tanımlanır. Geçiş döneminde ikisini birlikte kullanmak yaygındır.
Basit bir Express toplayıcı (yalnızca kendi uygulamanız için):
app.post('/csp-report',
express.json({ type: ['application/csp-report', 'application/reports+json'] }),
(req, res) => {
console.log(JSON.stringify(req.body)); // üretimde log sistemine yazın
res.sendStatus(204);
});Raporlarda violated-directive / effective-directive ve blocked-uri alanlarına bakarak hangi kaynağı politikaya eklemeniz veya koddan kaldırmanız gerektiğine karar verin. Birkaç gün temiz rapor aldıktan sonra aynı politikayı zorlayıcı başlığa taşıyın. Rapor uç noktası herkese açık olduğundan gövde boyutunu sınırlayın ve içeriğe güvenmeyin.
6) Diğer Başlıklar ve Tarayıcı Yorumu
Strict-Transport-Security (HSTS): Tarayıcı bu başlığı yalnızca HTTPS yanıtında dikkate alır; max-age süresince o alan adına http ile gitmeyi reddeder, sertifika hatasında kullanıcıya "yine de devam et" seçeneği sunmaz.
Strict-Transport-Security: max-age=31536000; includeSubDomainsX-Frame-Options: DENY veya SAMEORIGIN. ALLOW-FROM eskidir, modern tarayıcılar desteklemez. Modern karşılığı CSP frame-ancestors'tır; ikisi birlikte varsa destekleyen tarayıcılar frame-ancestors'ı esas alır. Eski tarayıcılar için ikisini birlikte göndermek zararsızdır.
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none'X-Content-Type-Options: nosniff: Tarayıcının MIME türü tahmin etmesini (sniffing) kapatır; yanlış Content-Type ile gelen script/stil engellenir.
Referrer-Policy: Başka siteye geçerken URL'nin ne kadarının Referer'da gideceğini belirler.
strict-origin-when-cross-originno-referrerPermissions-Policy: Kamera, mikrofon, konum gibi tarayıcı özelliklerini kısıtlar:
Permissions-Policy: camera=(), microphone=(), geolocation=()X-XSS-Protection: Eski tarayıcı filtresi içindir, modern tarayıcılarda kaldırılmıştır. OWASP, göndermemeyi veya "0" değerini önerir; XSS koruması için CSP kullanın.
[center][/center]
7) Uygulama Katmanında Kurulum
Express (helmet):
npm install helmetconst crypto = require('crypto');
const helmet = require('helmet');
app.use((req, res, next) => {
res.locals.cspNonce = crypto.randomBytes(16).toString('base64');
next();
});
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", (req, res) => `'nonce-${res.locals.cspNonce}'`],
objectSrc: ["'none'"],
baseUri: ["'self'"],
frameAncestors: ["'none'"],
},
},
}));
// Şablonda: <script nonce="<%= cspNonce %>">...</script>helmet varsayılan olarak bir dizi başlık ekler; hangi değerleri gönderdiğini kendi sürümünüzde curl -I ile mutlaka doğrulayın.
Django: SecurityMiddleware ve XFrameOptionsMiddleware ayarları settings.py'dedir. Django 6.0 ile yerleşik CSP desteği geldi (eski sürümlerde django-csp paketi kullanılır):
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"django.middleware.csp.ContentSecurityPolicyMiddleware", # Django 6.0+
# ...
]
SECURE_HSTS_SECONDS = 31536000 # yalnızca HTTPS tamamen hazırsa
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_REFERRER_POLICY = "strict-origin-when-cross-origin"
X_FRAME_OPTIONS = "DENY"
from django.utils.csp import CSP
SECURE_CSP_REPORT_ONLY = {
"default-src": [CSP.SELF],
"script-src": [CSP.SELF, CSP.NONCE],
"object-src": [CSP.NONE],
"report-uri": "/csp-report/",
}Şablonda nonce için csp context processor'ı (django.template.context_processors.csp) etkinleştirip
<script nonce="{{ csp_nonce }}">python manage.py check --deployPHP: header() çağrıları herhangi bir çıktıdan önce yapılmalıdır:
<?php
$nonce = base64_encode(random_bytes(16));
header("Content-Security-Policy: default-src 'self'; script-src 'nonce-$nonce' 'strict-dynamic'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'");
header("X-Content-Type-Options: nosniff");
header("Referrer-Policy: strict-origin-when-cross-origin");
?>
<script nonce="<?= htmlspecialchars($nonce) ?>">initApp();</script>[center][/center]
8) Sık Yapılan Hatalar
- script-src içinde 'unsafe-inline' bırakmak (nonce/hash varken destekleyen tarayıcılar onu yok sayar, ama tek başına korumayı boşa çıkarır)
- Aynı başlığı hem uygulamada hem ters vekilde farklı değerlerle eklemek; birden fazla CSP başlığında tarayıcı hepsini birden uygular, sonuç beklenenden sıkı olur
- Nonce'u sabit yazmak veya tam sayfa önbelleğinde kullanıcılar arasında paylaştırmak
- Report-Only'de kalıp politikayı hiç zorlayıcı moda almamak
- HTTPS hazır değilken uzun max-age ile HSTS ya da preload göndermek
- CDN alan adının tamamına izin vermek (ör. tüm bir paylaşımlı CDN); saldırgan aynı CDN'e dosya koyabiliyorsa politika aşılabilir
9) Sonuç
Güvenlik başlıkları ucuz ama güçlü bir katmandır. Sıralama önerim: önce nosniff, Referrer-Policy ve frame koruması; ardından CSP'yi Report-Only ile başlatıp raporlarla olgunlaştırmak; en son nonce tabanlı strict CSP'yi zorlamak. Politikanızı Google'ın CSP Evaluator aracıyla gözden geçirip her sürümde curl -I ile doğrulayın.
Kaynakça
- MDN - Content Security Policy (CSP)
- MDN - Content-Security-Policy başlık referansı
- W3C - Content Security Policy Level 3
- OWASP CSP Cheat Sheet
- OWASP HTTP Security Response Headers Cheat Sheet
- OWASP Secure Headers Project
- RFC 6797 - HTTP Strict Transport Security
- RFC 7034 - X-Frame-Options
- Helmet.js dokümantasyonu
- Django - Content Security Policy
- web.dev - Strict CSP
- Google CSP Evaluator
Yasal Uyarı
Bu rehber kendi uygulamalarınızı korumak içindir. Başlık ve politika testlerini yalnızca kendi sistemlerinizde veya yazılı yetkili pentest kapsamında yapın. Başkasına ait sistemlere izinsiz erişim veya müdahale TCK 243-245 ve ilgili maddeler kapsamında suç olabilir. Eğitim amaçlıdır.
Yasal uyarı: Bu içerik eğitim amaçlıdır. Yalnızca kendi sistemlerinizde veya yazılı izin aldığınız hedeflerde uygulayın (TCK 243-245).