Güvenlik & SSL

    Content Security Policy (CSP) Nedir ve Nasıl Kurulur?

    CSP başlığının XSS'i nasıl azalttığını, directive yazımını ve kademeli devreye almayı anlatan uygulamalı rehber.

    13 dk okuma Güncellendi: 10 Temmuz 2026

    Bir web uygulamasında en sık rastlanan ve en yıkıcı sonuçları olan açıklardan biri XSS'tir; yani saldırganın sizin sayfanızda kendi JavaScript kodunu çalıştırabilmesi. Kod bir kez tarayıcıda çalıştığında oturum çerezlerini çalabilir, kullanıcının adına form gönderebilir, sahte bir giriş ekranı basabilir veya sayfanın tamamını ele geçirebilir. Girdileri ne kadar dikkatli temizlerseniz temizleyin, büyük bir kod tabanında gözden kaçan tek bir nokta bile bu saldırıya kapı aralar. İşte tam bu noktada devreye ikinci bir savunma hattı girer: içerik güvenlik politikası.

    Content Security Policy, kısaca CSP, sunucunun tarayıcıya gönderdiği bir HTTP başlığıdır ve şu basit ama güçlü kuralı dayatır: "Bu sayfada yalnızca benim izin verdiğim kaynaklardan gelen script, stil, resim ve diğer içerikler çalışabilir; gerisi engellensin." Yani saldırgan sayfanıza bir <script> enjekte etmeyi başarsa bile, o script sizin beyaz listenizde olmadığı için tarayıcı onu çalıştırmayı reddeder. CSP, açığı kapatmaz; açığın sömürülmesini zorlaştırır. Bu yazıda CSP'nin hangi saldırıları nasıl azalttığını, default-src, script-src, style-src gibi temel directive'lerin nasıl yazıldığını, satır içi kodu güvenle çalıştırmak için nonce ve hash yaklaşımını ve en önemlisi siteyi kırmadan Report-Only ile kademeli devreye almayı somut örneklerle anlatacağız.

    CSP Neyi Çözer? XSS ve İçerik Enjeksiyonu#

    CSP'yi anlamak için önce çözdüğü sorunu netleştirelim. Tarayıcı, bir HTML sayfasını yüklerken içindeki her <script>, <link rel=stylesheet>, <img>, <iframe> etiketini görür ve varsayılan olarak hepsine güvenir; nereden geldiğine bakmaksızın çalıştırır. Bir XSS açığında saldırgan tam olarak bunu sömürür: bir yorum alanına, bir arama kutusuna veya bir profil adına <script src="https://kotu-site.com/steal.js"></script> gibi bir dize yerleştirir ve sayfa bunu diğer kullanıcılara sunduğunda o script çalışır.

    CSP, tarayıcının bu kör güvenini bir beyaz listeyle sınırlar. Başlıkta "script yalnızca kendi alan adımdan yüklenebilir" dediğinizde, saldırganın enjekte ettiği kotu-site.com kaynaklı script politikaya uymadığı için engellenir ve konsola bir ihlal (violation) mesajı düşer. Aynı mantık başka enjeksiyon türlerine de uzanır: veri sızdıran bir <img> beacon'ı, sayfanızı görünmez bir çerçeveye gömen bir <iframe>, form verilerinizi başka bir sunucuya postalayan bir <form action> — hepsi ilgili directive'le kısıtlanabilir.

    Burada altını çizmek gereken kritik nokta şudur: CSP birincil savunma değil, ikincil savunmadır. Girdi doğrulama, çıktı kodlama (output encoding) ve şablon motorunuzun otomatik kaçış (escaping) mekanizması hâlâ birinci hattır. CSP, bunların hepsinin başarısız olduğu gün devreye giren emniyet ağıdır. Uygulama katmanındaki bir başka savunma hattı olan web uygulama güvenlik duvarını (WAF) da bu resmin bir parçası olarak düşünebilirsiniz; WAF isteği sunucuya varmadan süzerken, CSP tarayıcıda son sözü söyler.

    Politika Nasıl Yazılır? Directive Mantığı#

    CSP politikası tek bir başlık satırından oluşur ve içinde noktalı virgülle ayrılmış directive'ler yer alır. Her directive bir kaynak türünü (script-src, style-src, img-src gibi) ve o türe izin verilen kaynakların listesini tanımlar. En basit haliyle şöyle görünür:

    Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.jsdelivr.net; img-src 'self' data:
    

    Bu politikayı Türkçeye çevirelim: "Belirtilmeyen her şey için varsayılan olarak yalnızca kendi alan adım geçerli. Script'ler kendi alan adımdan ve cdn.jsdelivr.net üzerinden yüklenebilir. Resimler kendi alan adımdan ve data: URI'larından gelebilir." Buradaki default-src, bir yedek (fallback) directive'idir; belirli bir tür için özel bir kural yazmadıysanız tarayıcı default-src değerini kullanır. Bu yüzden iyi bir politika neredeyse her zaman sıkı bir default-src 'self' ile başlar, sonra istisnaları tek tek gevşetir.

    Kaynak ifadelerinde en sık kullandığınız anahtar kelimeler şunlardır: 'self' (aynı köken), 'none' (hiçbir şey), belirli bir alan adı (https://cdn.example.com), şema (https:, data:), satır içi kod için 'unsafe-inline' ve string olarak kod çalıştıran eval için 'unsafe-eval'. Son iki anahtar kelime CSP'nin tüm koruma değerini yok edebilir; onlardan mümkün olduğunca kaçınmak, birazdan anlatacağımız nonce/hash yaklaşımının varlık sebebidir.

    Temel Directive'ler ve Kaynak İfadeleri#

    Onlarca directive vardır ama pratikte politikanızın omurgasını birkaç tanesi oluşturur. Aşağıdaki tablo en çok kullanılanları ve tipik değerlerini özetler.

    DirectiveNeyi kontrol ederSık kullanılan değer
    default-srcDiğer directive'ler için yedek kural'self'
    script-srcJavaScript kaynakları'self' + nonce
    style-srcCSS kaynakları'self'
    img-srcResimler'self' data:
    font-srcYazı tipleri'self'
    connect-srcfetch, XHR, WebSocket bağlantıları'self' https://api.example.com
    frame-srcGömülü çerçeveler (iframe)'self'
    frame-ancestorsSizi kimin çerçeveleyebileceği (clickjacking)'none'
    form-actionFormların gönderilebileceği hedefler'self'
    base-uri<base> etiketinin alabileceği değer'self'
    object-src<object>, <embed> (Flash vb.)'none'

    Bu tabloda özellikle üç directive genellikle atlanır ama saldırı yüzeyini ciddi biçimde daraltır. frame-ancestors 'none' sitenizin başka bir sayfada <iframe> içine gömülmesini engelleyerek clickjacking'i önler ve eski X-Frame-Options başlığının modern karşılığıdır. base-uri 'self' bir saldırganın enjekte ettiği <base> etiketiyle tüm göreli URL'leri kendi sunucusuna yönlendirmesini engeller. object-src 'none' ise artık kullanılmayan eklenti tabanlı içerik türlerini tamamen kapatır. Modern bir politikada bu üçünün de bulunması tavsiye edilir:

    Content-Security-Policy:
      default-src 'self';
      script-src 'self';
      style-src 'self';
      img-src 'self' data:;
      font-src 'self';
      connect-src 'self';
      object-src 'none';
      base-uri 'self';
      form-action 'self';
      frame-ancestors 'none'
    

    Bir uyarı: CSP'nin frame-ancestors directive'i yalnızca gerçek bir HTTP yanıt başlığı olarak gönderildiğinde çalışır. Politikayı bir <meta http-equiv> etiketiyle koyarsanız frame-ancestors yok sayılır. Bu yüzden mümkün olduğunda CSP'yi web sunucusu seviyesinde, gerçek bir başlık olarak yayınlayın.

    Nonce ve Hash ile Satır İçi Kodu Güvenle Çalıştırmak#

    CSP'nin en zorlayıcı kısmı satır içi (inline) kodlardır. Sayfanızda <script>console.log('merhaba')</script> gibi doğrudan HTML içine yazılmış bir script veya onclick="..." gibi bir olay işleyici varsa, sıkı bir script-src 'self' bunları da engeller — çünkü tarayıcı bu kodun sizden mi yoksa saldırgandan mı geldiğini ayırt edemez. Kolaycı çözüm 'unsafe-inline' eklemektir ama bu, CSP'nin XSS'e karşı tüm faydasını sıfırlar; artık her enjekte edilmiş satır içi script de çalışır. Doğru çözüm nonce veya hash kullanmaktır.

    Nonce (number used once), her sayfa yüklemesinde sunucunun ürettiği rastgele ve tek kullanımlık bir değerdir. Bu değeri hem CSP başlığına hem de izin verdiğiniz <script> etiketine eklersiniz; ikisi eşleşirse tarayıcı o scripti çalıştırır, eşleşmeyen (yani saldırganın enjekte ettiği) her satır içi script engellenir. Saldırgan nonce'u tahmin edemez çünkü her istekte değişir.

    Content-Security-Policy: script-src 'self' 'nonce-r4nd0mAbc123XyZ'
    
    <script nonce="r4nd0mAbc123XyZ">
      // Bu blok çalışır, çünkü nonce eşleşiyor.
      initApp();
    </script>
    

    Nonce'u her istekte sunucu tarafında üretmeniz şarttır; sabit bir nonce hiçbir işe yaramaz. PHP tarafında basit bir üretim şöyle olur:

    $nonce = base64_encode(random_bytes(16));
    header("Content-Security-Policy: script-src 'self' 'nonce-{$nonce}'");
    // Şablonda: <script nonce="<?= htmlspecialchars($nonce) ?>">
    

    Hash yaklaşımı ise nonce üretemediğiniz statik sayfalar için uygundur. Satır içi script'in içeriğinin SHA-256, SHA-384 veya SHA-512 özetini alır ve bu özeti başlığa yazarsınız. İçerik birebir aynıysa çalışır; tek karakter değişse çalışmaz. Bir script'in doğru hash'ini almak için içeriğini openssl ile özetleyebilirsiniz:

    echo -n "initApp();" | openssl dgst -sha256 -binary | openssl base64
    # çıktı: örneğin  47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=
    

    Ardından başlığa script-src 'self' 'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' şeklinde eklersiniz. Nonce dinamik sayfalar için, hash ise nadiren değişen statik bloklar için daha pratiktir; ikisini aynı politikada birlikte de kullanabilirsiniz.

    Report-Only ile Siteyi Kırmadan Test Etmek#

    CSP'nin en büyük dağıtım riski, hazırladığınız politikanın meşru bir kaynağı yanlışlıkla engellemesidir. Analitik scripti, canlı destek widget'ı, harita gömülü çerçevesi, CDN üzerindeki fontlar — bunlardan birini politikaya eklemeyi unutursanız, üretim ortamında bir özellik sessizce çalışmayı bırakır. İşte bu felaketi önlemek için CSP'nin dahili bir "kuru koşu" (dry run) modu vardır: Content-Security-Policy-Report-Only.

    Politikayı bu başlıkla yayınladığınızda tarayıcı hiçbir şeyi engellemez; sadece politikayı ihlal edecek olan istekleri tespit eder, konsola yazar ve isterseniz belirlediğiniz bir uç noktaya rapor gönderir. Yani site normal çalışmaya devam ederken, "eğer bu politikayı gerçekten uygulasaydım şunları engellerdim" listesini toplarsınız.

    Content-Security-Policy-Report-Only:
      default-src 'self';
      script-src 'self' 'nonce-r4nd0mAbc123XyZ';
      report-uri /csp-report
    

    Önerilen kademeli devreye alma süreci şudur:

    1. Sıkı bir taslak politikayı Report-Only olarak yayınla.
    2. Birkaç gün gerçek trafikte ihlal raporlarını topla.
    3. Meşru olan her ihlal için ilgili kaynağı beyaz listeye ekle.
    4. Rapor akışı temizlenip yalnızca gerçek saldırı denemeleri kalınca,
       başlığı Report-Only'den gerçek Content-Security-Policy'ye çevir.
    

    Bu yaklaşımın güzelliği, aynı anda hem Report-Only hem de gerçek başlığı birlikte kullanabilmenizdir. Örneğin şu an uygulanan gevşek bir politikayı gerçek başlıkla yayınlarken, gelecekte geçmeyi planladığınız daha sıkı politikayı Report-Only olarak paralel çalıştırıp raporlarını izleyebilirsiniz. Böylece bir sonraki sıkılaştırmayı üretime almadan önce nelerin kırılacağını tam olarak görürsünüz. HTTPS'e geçişte de benzer bir kademeli mantık işlediğimiz HTTPS yönlendirme rehberimize göz atabilirsiniz.

    Raporlama ve İhlalleri İzleme#

    Report-Only ile toplanan verinin bir yere düşmesi gerekir. Klasik yöntem report-uri directive'idir; tarayıcı, her ihlalde belirttiğiniz uç noktaya bir JSON gövdesiyle POST isteği gönderir. Bu uç nokta, ihlalin hangi directive'i, hangi kaynağı ve hangi sayfayı ilgilendirdiğini içeren bir rapor alır:

    {
      "csp-report": {
        "document-uri": "https://siteadi.com/urunler",
        "violated-directive": "script-src",
        "blocked-uri": "https://analytics.baska-site.com/tracker.js",
        "line-number": 42,
        "source-file": "https://siteadi.com/urunler"
      }
    }
    

    Bu raporu toplamak için karmaşık bir altyapıya ihtiyacınız yoktur; küçük bir PHP uç noktası gövdeyi okuyup bir log dosyasına yazabilir:

    // /csp-report uç noktası
    $body = file_get_contents('php://input');
    if ($body) {
        file_put_contents(
            '/var/log/csp/reports.log',
            date('c') . ' ' . $body . PHP_EOL,
            FILE_APPEND | LOCK_EX
        );
    }
    http_response_code(204);
    

    Modern tarayıcılar report-uri'yi kademeli olarak report-to directive'i ve Reporting API lehine bırakmaktadır, ama geçiş döneminde ikisini birlikte yazmak en güvenli yaklaşımdır. Uygulamada dikkat etmeniz gereken en önemli şey gürültü sorunudur: tarayıcı eklentileri, reklam engelleyiciler ve kullanıcı script'leri sayfaya kendi kodlarını enjekte ettiği için Report-Only akışında binlerce yanlış pozitif ihlal birikebilir. Bu yüzden raporları körü körüne beyaz listeye eklemeyin; her ihlalin gerçekten sizin sitenizden mi yoksa kullanıcının tarayıcısındaki bir eklentiden mi kaynaklandığını ayırt edin.

    Web Sunucusunda CSP Yapılandırması#

    Politikayı hazırladıktan sonra onu her yanıta eklemeniz gerekir. En temiz yol, uygulama koduna dokunmadan web sunucusu seviyesinde başlığı yayınlamaktır. Nginx için server bloğuna eklenen tek bir satır yeterlidir:

    add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" always;
    

    Buradaki always anahtar kelimesi kritiktir; onsuz başlık yalnızca 2xx ve 3xx yanıtlarında eklenir, oysa 404 ve 500 gibi hata sayfalarında da korumaya ihtiyacınız vardır. Apache kullanıyorsanız mod_headers etkinken .htaccess veya sanal sunucu yapılandırmasına şunu koyarsınız:

    Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'self'"
    

    Nonce kullanan dinamik bir politikada başlığı sabit dosyaya yazamazsınız; çünkü her istekte değeri değişmelidir. Bu durumda başlığı uygulama katmanında (PHP, Node, Python) üretmeniz gerekir. Nginx'in önünde bir uygulama sunucusu çalışıyorsa, nonce'u uygulamanız üretir ve başlığı da uygulama gönderir; sunucu seviyesindeki add_header satırını bu durumda kaldırın, aksi halde iki farklı CSP başlığı çatışır. Paylaşımlı hosting ve cPanel ortamlarında CSP'yi genellikle .htaccess üzerinden statik bir politikayla yayınlarsınız; kendi sunucunuza sahip olduğunuz VDS veya yönetimli sunucu ortamlarında ise Nginx/Apache yapılandırmasına doğrudan erişerek nonce tabanlı dinamik politikaları da rahatça kurabilirsiniz.

    Değişikliği yayınlamadan önce test edin. Tarayıcının geliştirici konsolunda engellenen kaynakları görebilir veya komut satırından başlığın gerçekten döndüğünü doğrulayabilirsiniz:

    curl -sI https://siteadi.com | grep -i content-security-policy
    

    Yaygın Hatalar ve Dikkat Edilmesi Gerekenler#

    CSP'yi ilk kez kuranların düştüğü hataların başında 'unsafe-inline' bağımlılığı gelir. Birçok tema, eklenti ve analitik kodu satır içi script'e dayandığı için, en kolay yol olarak 'unsafe-inline' eklenir. Ne var ki bu tek anahtar kelime, politikanızın XSS'e karşı değerini büyük ölçüde geçersiz kılar. Eğer nonce/hash'e geçemiyorsanız, en azından satır içi script'leri harici .js dosyalarına taşımayı hedefleyin; bu, hem CSP'yi sıkılaştırır hem de tarayıcı önbelleklemesini iyileştirir.

    İkinci sık hata, çok geniş beyaz listelerdir. script-src https: yazmak, "herhangi bir HTTPS alan adından script yüklenebilir" demektir ki bu neredeyse hiç koruma sağlamaz. Aynı şekilde script-src * politikayı tamamen anlamsız kılar. Beyaz listenizi olabildiğince dar tutun; ihtiyaç duyduğunuz tam alan adlarını yazın, joker ve şema bazlı geniş izinlerden kaçının. Bir başka incelik de data: şemasıdır: script-src'de data:'ya izin vermek tehlikelidir çünkü saldırgan data: URI ile kod enjekte edebilir; data:'yı yalnızca img-src gibi zararsız yerlerde kullanın.

    Son olarak, CSP'nin bir azaltma (mitigation) katmanı olduğunu asla unutmayın. Aşağıdaki karşılaştırma, CSP'nin nerede durduğunu netleştirir:

    KatmanGöreviCSP'nin rolü
    Girdi doğrulamaKötü veriyi baştan reddetİlgili değil (birincil savunma)
    Çıktı kodlamaVeriyi güvenle ekrana basİlgili değil (birincil savunma)
    CSPZararlı kod çalışsa bile engelleİkincil emniyet ağı
    WAFİsteği sunucuya varmadan süzTamamlayıcı katman

    Yani CSP'yi kurdunuz diye kod tarafındaki güvenlik önlemlerini gevşetemezsiniz; CSP onların yerini değil, üstüne eklenen bir kat daha koruma sağlar. Güvenliğin bütününe dair daha geniş bir bakış için WAF rehberimizi ve site güvenliğini toparlayan diğer başlıkları incelemenizi öneririz.

    Sıkça Sorulan Sorular#

    CSP XSS açığını tamamen önler mi?#

    Hayır, CSP açığı ortadan kaldırmaz; açığın sömürülmesini zorlaştırır. Uygulama kodunuzda bir XSS zafiyeti hâlâ mevcut olabilir, ama iyi yazılmış bir CSP saldırganın enjekte ettiği kodun çalışmasını engelleyerek zararın büyük kısmını durdurur. Bu yüzden CSP birincil değil, ikincil bir savunma katmanı olarak konumlandırılmalı ve girdi doğrulama ile çıktı kodlamanın yerine değil, yanına eklenmelidir.

    unsafe-inline kullanmak neden bu kadar kötü?#

    Bu anahtar kelime, sayfadaki bütün satır içi script ve stillerin çalışmasına izin verir ve tarayıcı bu kodun sizden mi yoksa saldırgandan mı geldiğini ayırt edemez. Bunu eklediğiniz an, CSP'nin XSS'e karşı sağladığı korumanın neredeyse tamamını kaybedersiniz; çünkü enjekte edilmiş her satır içi script de artık izinli sayılır. Doğru çözüm, satır içi kodları harici dosyalara taşımak veya nonce ve hash yöntemleriyle yalnızca sizin ürettiğiniz belirli blokları beyaz listeye almaktır.

    Report-Only modu siteyi yavaşlatır mı veya kullanıcıya bir şey gösterir mi?#

    Hayır, Report-Only modu kullanıcı için tamamen görünmezdir ve algılanır bir performans etkisi yoktur. Tarayıcı hiçbir kaynağı engellemez, yalnızca politikayı ihlal edecek istekleri arka planda tespit eder ve isteğe bağlı olarak belirttiğiniz uç noktaya küçük bir JSON raporu gönderir. Bu sayede gerçek trafikte politikanızın nelerin çalışmayacağını, siteyi hiç kırmadan güvenle ölçebilirsiniz.

    Nonce ile hash arasında hangisini seçmeliyim?#

    Sayfalarınız sunucu tarafında dinamik olarak üretiliyorsa nonce daha pratiktir, çünkü her istekte yeni bir rastgele değer üretip hem başlığa hem script etiketine koyabilirsiniz. Buna karşılık nadiren değişen statik HTML dosyalarında nonce üretecek bir sunucu mantığı yoksa, script içeriğinin SHA-256 hash'ini almak daha uygundur. İkisi birbirini dışlamaz; aynı politikada bazı bloklar için nonce, bazıları için hash kullanabilirsiniz.

    CSP başlığını meta etiketiyle mi yoksa HTTP başlığıyla mı koymalıyım?#

    Mümkün olan her durumda gerçek bir HTTP yanıt başlığı olarak koymalısınız, çünkü frame-ancestors, report-uri ve sandbox gibi bazı directive'ler <meta http-equiv> etiketiyle çalışmaz. Meta etiketi yalnızca sunucu yapılandırmasına erişemediğiniz sınırlı durumlarda geçici bir çözümdür ve o sırada bile clickjacking koruması gibi önemli özellikleri kaybedersiniz. Kendi sunucusuna sahip ortamlarda Nginx veya Apache seviyesinde başlığı yayınlamak her açıdan daha güvenlidir.

    Mevcut sitem çok fazla üçüncü taraf script kullanıyor, yine de CSP kurabilir miyim?#

    Evet, ama yaklaşımınız kademeli olmalı. Önce sıkı bir taslak politikayı Report-Only modunda yayınlayın, birkaç gün boyunca gelen ihlal raporlarından hangi üçüncü taraf kaynakların meşru olduğunu tespit edin ve bunları tek tek beyaz listeye ekleyin. Rapor akışı yalnızca gerçek saldırı denemelerini gösterecek kadar temizlendiğinde politikayı gerçek başlığa çevirirsiniz; bu sayede analitik, canlı destek veya ödeme widget'ı gibi harici bileşenler beklenmedik şekilde çalışmayı bırakmaz.

    Kapanış#

    Content Security Policy, tek bir HTTP başlığıyla web uygulamanıza güçlü bir ikinci savunma hattı ekler; XSS ve içerik enjeksiyonunun etkisini, izin verdiğiniz kaynakları beyaz listeye alarak ciddi biçimde azaltır. İşin sırrı, sıkı bir default-src 'self' ile başlamak, satır içi kodu 'unsafe-inline' yerine nonce veya hash ile yönetmek ve en önemlisi Report-Only modunu kullanarak siteyi hiç kırmadan kademeli olarak devreye almaktır. Doğru kurulmuş bir CSP, saldırgan bir açık bulsa bile onu sömüremeyeceği bir duvara toslatır.

    Clou.TR olarak güvenli bir web varlığının tek bir başlıktan ibaret olmadığını biliyoruz. Ücretsiz SSL destekli hosting paketlerimiz, yönetimli sunucu güvenlik yapılandırması için sunucu yönetimi hizmetimiz, gelişmiş saldırı süzme için WAF ve DDoS koruma çözümlerimiz, CSP gibi başlık tabanlı önlemleri tamamlayan bütünsel bir kalkan sunar. WordPress siteniz için özel olarak sıkılaştırılmış bir altyapı arıyorsanız WordPress hosting paketlerimize göz atın; ekibimiz güvenlik başlıklarınızı doğru kurmanız için yol boyunca yanınızda.

    GüvenlikWebBaşlıklar

    Uygulamaya geçmeye hazır mısınız?

    NVMe SSD, ücretsiz SSL ve %99.9 uptime garantisiyle Clou.TR hosting ve sunucu çözümleriyle projenizi hayata geçirin.