Sitenizi bozmadan Content Security Policy
Enjekte edilen scriptleri durduran bir CSP kurun: report-only ile yayın, nonce ve strict-dynamic, önemli direktifler ve ihlalleri adım adım giderme.
CSP ne yapar
Siteler arası betik çalıştırma (XSS), bir saldırganın kendi JavaScript kodunu sizin sayfanızın içinde çalıştırabilmesiyle ortaya çıkar; örneğin bir yorum alanı üzerinden ya da kaçış karakterleri uygulanmadan sayfaya geri yazılan bir URL parametresi üzerinden. Tarayıcı, bu scripti sizin yazdığınız scriptten hiçbir şekilde ayırt edemez; ikisi de aynı sayfanın parçasıdır. W3C tarafından tanımlanan Content Security Policy ise, tarayıcıya hangi script ve içerik kaynaklarının meşru olduğunu önceden söylemenizi sağlar.
CSP ikinci bir savunma hattıdır. XSS için asıl çözüm hâlâ çıktıyı kaçışlamak ve gelen girdiyi temizlemektir; CSP ise bu katmanlardan birinde bir hata gözden kaçtığında ortaya çıkacak hasarı sınırlar. Bunun yanında sitenizin başka sayfalarca çerçevelenip çerçevelenemeyeceğini, formların nereye gönderilebileceğini ve sayfanın karışık içerik yükleyip yükleyemeyeceğini de denetler.
Önemli direktifler
| Direktif | Neyi denetler | Tipik değer |
|---|---|---|
default-src | Listelenmeyen fetch direktifleri için yedek | 'self' |
script-src | JavaScript kaynakları | 'nonce-…' 'strict-dynamic' |
style-src | Stil sayfaları ve satır içi stiller | 'self' (satır içi stiller için ayrıca nonce) |
img-src | Görseller | 'self' data: |
connect-src | fetch, XHR, WebSocket hedefleri | 'self' https://api.example.com |
font-src | Web fontları | 'self' |
frame-src | Sayfanızın gömdüğü çerçeveler | Belirli hostlar veya 'none' |
object-src | <object> ve <embed> gibi eklentiler | 'none' |
base-uri | <base> öğesi | 'none' veya 'self' |
form-action | Formların nereye gönderilebileceği | 'self' |
frame-ancestors | Sayfanızı kimin çerçeveleyebileceği (clickjacking) | 'self' veya 'none' |
upgrade-insecure-requests | http:// alt kaynaklarını https:// olarak yeniden yazar | (değer yok) |
frame-ancestors, sandbox ve raporlama direktifleri yalnızca HTTP başlığında çalışır, bir <meta http-equiv> etiketinde ise hiçbir etkileri olmaz. Bu yüzden mümkün olan her yerde politikayı başlık olarak gönderin. frame-ancestors, güncel tarayıcılarda eski X-Frame-Options başlığının da yerini alır; yine de geçiş döneminde ikisini birden göndermek zararsızdır.
Host izin listeleri ve nonce karşılaştırması
İlk CSP uygulamaları izin verilen script hostlarını tek tek listeliyordu: script-src 'self' https://cdn.example.net https://analytics.example.org. Google mühendislerinin yaptığı geniş çaplı araştırma, bu tür politikaların büyük çoğunluğunun aşılabildiğini gösterdi; çünkü izin verilen CDN ve analitik hostları çoğu zaman bir saldırganın kötüye kullanabileceği hazır scriptler veya JSONP uç noktaları da sunar. Uzun izin listelerinin bakımı ayrıca zordur ve yapı kırılgan hale gelir: siteye eklenen her yeni üçüncü taraf script, politikada ayrı bir değişiklik gerektirir.
Nonce tabanlı bir politika ise scriptlere bulundukları konuma göre değil, rastgele üretilmiş bir değere göre güven verir. Sunucu her yanıt için tahmin edilemeyen yeni bir nonce üretir ve bunu hem politika başlığına hem de sayfadaki her meşru <script> etiketine ekler. Sayfaya sonradan enjekte edilen bir script bu nonce değerini bilemeyeceği için tarayıcı tarafından hiç çalıştırılmaz.
Content-Security-Policy: script-src 'nonce-4AEemGb0xJptoIGFP3Nd' 'strict-dynamic'; object-src 'none'; base-uri 'none'
<script nonce="4AEemGb0xJptoIGFP3Nd" src="/assets/app.js"></script>
<script nonce="4AEemGb0xJptoIGFP3Nd">window.appConfig = { locale: "en" };</script>'strict-dynamic', bu güveni, güvenilen bir scriptin kendi yüklediği scriptlere de genişletir; örneğin sayfaya başka etiketler ekleyen bir tag manager'a, bu etiketlerin hostlarını tek tek listelemeye gerek kalmadan. 'strict-dynamic' destekleyen tarayıcılar, aynı direktifteki host izin listelerini ve 'unsafe-inline' değerini tamamen yok sayar; bu da söz konusu değerleri çok eski tarayıcılar için yedek olarak eklerken modern tarayıcılardaki politikayı hiç zayıflatmamanızı sağlar.
Content-Security-Policy:
script-src 'nonce-{RANDOM}' 'strict-dynamic' https: 'unsafe-inline';
object-src 'none';
base-uri 'none';
frame-ancestors 'self';
report-to cspNonce her yanıtta benzersiz olmalıdır
Statik sayfalar için hash değerleri
Sunucu tarafında render edilmeyen statik siteler her yanıta yeni bir nonce ekleyemez, çünkü sayfa istek anında üretilmez. Bunun yerine her satır içi scriptin tam içeriğinin SHA-256 hash değerini, örneğin 'sha256-…' biçiminde politikada listeleyin ya da satır içi scriptleri kendi origin'inizden sunulan ayrı dosyalara taşıyın. 'self' üzerinden yüklenen harici scriptlerin o zaman hiç nonce değerine ihtiyacı olmaz.
Hiçbir şeyi bozmadan yayına alma
- Envanter. Sitenin kullandığı bütün scriptleri, stilleri, fontları, çerçeveleri ve API uç noktalarını; analitik, sohbet widget'ı ve ödeme formu gibi üçüncü taraf hizmetler dahil olmak üzere tek bir listede toplayın.
- Taslağı çıkarın. İlk sürümü elle yazmak zorunda değilsiniz. HTTP Başlık Denetleyicisi aracında, Content-Security-Policy bulgusunun altında bir oluşturucu vardır: sayfanın her kaynak türünü hangi origin'lerden yüklediğini girin, satır içi stile ya da satır içi scripte ihtiyaç olup olmadığını belirtin; araç başlığı, nginx satırını ve Apache satırını yazar, ardından ürettiği politikayı aynı kontrolden geçirir; böylece kendi ürettiği politikadaki bir zayıflık da hemen yanında raporlanır. Oluşturucu bilerek report-only modunda başlar.
- Report-only. Politikayı
Content-Security-Policy-Report-Onlyolarak gönderin. Tarayıcılar ihlalleri raporlar ama hiçbir şeyi engellemez. - Raporları toplayın. Bir raporlama uç noktası yapılandırın, ayrıca önemli sayfalarla kritik kullanıcı akışlarında tarayıcı konsolunu açıp gelen uyarılara bakın.
- Politikayı değil, sayfayı düzeltin. Satır içi olay işleyicilerini (
onclick=) script dosyalarına taşıyın, meşru satır içi scriptlere nonce ekleyin ve artık kullanılmayan üçüncü taraf scriptleri tamamen kaldırın. - Zorunlu kılın. Gelen raporlar yalnızca tarayıcı eklentilerini ve arka plan gürültüsünü gösterdiğinde
Content-Security-Policybaşlığına geçin. Politikayı daha da sıkılaştırmak isterseniz, report-only başlığını daha katı bir aday politikayla birlikte açık tutmaya devam edin. - İzleyin. Raporlamayı kalıcı olarak açık bırakın; yeni özellikler ve üçüncü taraf tarafındaki değişiklikler zaman içinde yeni ihlaller üretecektir.
Reporting-Endpoints: csp="https://example.com/csp-reports"
Content-Security-Policy-Report-Only: script-src 'nonce-{RANDOM}' 'strict-dynamic'; object-src 'none'; base-uri 'none'; report-to cspReporting-Endpoints başlığıyla birlikte kullanılan report-to bugünkü güncel mekanizmadır. Eski report-uri direktifi kullanımdan kaldırılmıştır ama report-to desteklemeyen tarayıcılar tarafından hâlâ anlaşıldığı için birçok site geçiş sürecinde ikisini birden gönderir. Raporlar JSON biçiminde gelir; sayfaya script enjekte eden tarayıcı eklentilerinden kaynaklanan gürültüyü baştan bekleyin ve raporları blocked-uri ile source-file alanlarına göre filtreleyin.
Sık görülen ihlaller ve çözümleri
| İhlal | Neden | Çözüm |
|---|---|---|
| Satır içi script engellendi | Nonce içermeyen <script> | Yanıta özel nonce ekleyin ya da kodu bir dosyaya taşıyın |
| Satır içi olay işleyici engellendi | onclick="…" öznitelikleri | Dinleyicileri JavaScript tarafında addEventListener ile bağlayın |
javascript: URL engellendi | href="javascript:…" gibi bağlantılar | Dinleyicisi olan bir buton kullanın |
eval engellendi | eval veya new Function kullanan kütüphaneler | Kütüphaneyi güncelleyin; 'unsafe-eval' son çaredir |
| Stil engellendi | Satır içi style= öznitelikleri veya <style> blokları | Stil sayfalarına taşıyın ya da <style> öğesine nonce verin |
| Bağlantı engellendi | connect-src içinde olmayan API veya analitik uç noktası | Tam origin değerini ekleyin |
| Çerçeve engellendi | Gömülü video, harita veya ödeme çerçevesi | Hostu frame-src içine ekleyin |
İhlalleri risk sırasına göre giderin: önce script ihlalleri, ardından bağlantılar ve çerçeveler, en son da stiller. Script kaynakları enjekte edilen kodun çalışıp çalışamayacağını doğrudan belirler, stil ihlallerinin çoğu ise yalnızca kozmetik sorunlardır.
script-src içine nonce veya hash kullanmadan 'unsafe-inline' eklemeye direnin. Bu değer, tam olarak CSP'nin durdurması beklenen enjekte satır içi scriptlere izin verir ve HTTP Başlık Denetleyicisi bunu zayıf politika olarak raporlar. style-src içindeki 'unsafe-inline' ise çok daha küçük bir risktir ve satır içi stiller temizlenene kadar sık başvurulan bir tavizdir.
Uygulamalı örnek: gerçek bir politikayı sıkılaştırmak
Yıllar içinde parça parça bir politika biriktirmiş, example.com adresindeki bir tanıtım sitesini düşünün. Politika çalışıyor ama HTTP Başlık Denetleyicisi bunu zayıf olarak işaretliyor; çünkü script-src içinde hem 'unsafe-inline' hem de bir joker karakter var. Aşağıda hem başlangıç noktası hem de birkaç yinelemede ulaşabileceği politika yer alıyor.
Content-Security-Policy: default-src 'self' https:; script-src 'self' 'unsafe-inline' https://*.example.net https://analytics.example.org; style-src 'self' 'unsafe-inline'; img-src * data:Sorunları adlandırmak kolay. 'unsafe-inline' enjekte edilen her satır içi scriptin çalışmasına izin verir, https://*.example.net ortak bir CDN alan adı altındaki her hosta güvenir, default-src https: listelenmeyen her şey için herhangi bir HTTPS origin'ine izin verir ve img-src * görsellerin her yerden yüklenmesine izin vererek görsel URL'leri üzerinden veri sızdırılmasına zemin hazırlar. Bunların üstüne politikada hiç object-src, base-uri veya frame-ancestors tanımı yok.
Content-Security-Policy:
default-src 'self';
script-src 'nonce-{RANDOM}' 'strict-dynamic' https: 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https://analytics.example.org;
connect-src 'self' https://analytics.example.org;
object-src 'none';
base-uri 'none';
form-action 'self';
frame-ancestors 'self';
report-to cspBuraya ulaşmak politikada değil, sitenin kendisinde üç değişiklik gerektirdi: satır içi onclick işleyicileri ana script dosyasına taşındı, analitik yükleyici nonce değerini almaya başladı ve eval gerektiren eski bir widget başka bir çözümle değiştirildi. style-src içindeki 'unsafe-inline' şimdilik belgelenmiş bir taviz olarak yerinde kalıyor. Nonce destekleyen tarayıcılarda script-src içindeki https: ve 'unsafe-inline' girdileri zaten yok sayılır, yani yalnızca çok eski tarayıcıların çalışmaya devam etmesini sağlarlar.
| Yineleme | Değişiklik | Report-only sonucu |
|---|---|---|
| 1 | object-src 'none'; base-uri 'none'; frame-ancestors 'self' eklendi | İhlal yok |
| 2 | Host izin listesi nonce ve 'strict-dynamic' ile değiştirildi | Satır içi işleyiciler ve bir eval raporlandı |
| 3 | İşleyiciler script dosyasına taşındı, eval kullanan widget değiştirildi | Yalnızca tarayıcı eklentisi gürültüsü |
| 4 | default-src, img-src ve connect-src sıkılaştırıldı | İki analitik uç noktası eklendi, sonra temiz |
Scriptlerin ötesinde
Clickjacking
frame-ancestors 'none' hiçbir sitenin sizin sayfanızı çerçeve içine almasına izin vermez; 'self' ise yalnızca kendi alan adınızdaki sayfalara bu izni verir. Modern tarayıcılarda X-Frame-Options: DENY ve SAMEORIGIN değerlerinin yerini tamamen alır.
Karışık içerik
upgrade-insecure-requests, tarayıcının sayfadaki http:// ile başlayan görselleri, scriptleri ve stilleri otomatik olarak HTTPS üzerinden almasını sağlar. Eski içeriğin taşınması sırasında, sayfanın kendisi için kullanılan HSTS ile birlikte işe yarar.
Trusted Types
require-trusted-types-for 'script', destekleyen tarayıcıların innerHTML gibi tehlikeli DOM hedeflerine aktarılan metin değerlerini, onaylı bir politikadan gelmedikleri sürece reddetmesini sağlar. Böylece nonce değerlerinin yakalayamadığı DOM tabanlı XSS saldırılarını hedef alır. Uygulamada kod değişikliği gerektirir ve en iyisi ana politika iyice oturduktan sonra eklenmesidir.
Politikayı sağlıklı tutmak
CSP tek seferlik bir proje değildir. Her yeni özellik, her pazarlama aracı veya gömülü widget politikada bir değişiklik gerektirebilir ve sahibi belli olmayan bir politika, artık hiçbir şeyi korumaz hale gelene kadar sessizce istisna biriktirmeye eğilimlidir. Bu yüzden politikaya net bir sahip atayın, onu uygulamanın yanında sürüm kontrolünde tutun ve üzerinde yapılan her değişikliği bir kod değişikliği gibi gözden geçirin.
İki alışkanlık politikayı uzun vadede sıkı tutar. Birincisi, yeni bir kaynak eklerken her zaman en dar biçimi tercih edin: connect-src içinde joker karakter yerine tam origin, yeni bir script hostu yerine nonce. İkincisi, ihlal raporlarını ayda bir düzenli olarak gözden geçirin: ani bir artış çoğu zaman kimseye haber verilmeden yeni bir üçüncü taraf script eklendiğini ya da bir enjeksiyon denemesinin engellendiğini gösterir; her iki durumu da bilmekte fayda vardır.
Son olarak, politikayı dağıtım sürecinin bir parçası olarak test edin. Önemli sayfaları isteyen ve Content-Security-Policy başlığı eksikse ya da nonce olmadan 'unsafe-inline' içeriyorsa pipeline'ı başarısız kılan basit bir kontrol, başlığı sessizce düşüren bir proxy yapılandırma değişikliği gibi kazara oluşan gerilemeleri daha üretime çıkmadan yakalar.
Framework'ler, CDN'ler ve tag manager'larla CSP
Sunucu tarafında render eden framework'lerin çoğunda, ürettikleri her script etiketine nonce enjekte etmenin hazır bir yolu vardır; framework'ün belgelerinde CSP veya nonce desteğini arayın. Statik dosyalara derlenen tek sayfa uygulamaları ise scriptlere yalnızca 'self' üzerinden izin veren ve hiç satır içi script içermeyen bir politikayla gayet iyi çalışır. Derleme araçları da çoğu zaman satır içi bootstrap scriptlerinden kaçınacak şekilde yapılandırılabilir.
CDN'ler ve edge platformları başlık ekleyebilir veya var olan başlıkları yeniden yazabilir; bu pratiktir ama aynı anda iki farklı politikanın yürürlükte kalmasına da yol açabilir. Yanıtta iki CSP başlığı varsa tarayıcı ikisini birden uygular ve bir kaynağın her iki politika tarafından da izinli olması gerekir. Bu yüzden her dağıtımdan sonra nihai yanıtı HTTP Başlık Denetleyicisi ile kontrol edin.
Tag manager'lar bu tablonun en zor durumudur; çünkü pazarlama ekipleri hiçbir kod değişikliği yapmadan siteye script ekleyebilir. 'strict-dynamic' ile, nonce üzerinden güvenilen bir tag manager'ın eklediği scriptlere izin verilir; bu işleri yürütür ama aynı zamanda tag manager erişimi olan herkesin sitenizde kod çalıştırabileceği anlamına gelir. O erişimi production dağıtım yetkisiyle aynı ciddiyetle koruyun.
SSS
CSP'yi meta etiketinde kullanabilir miyim?
Kısmen. <meta http-equiv="Content-Security-Policy"> fetch direktiflerinin çoğunu destekler ama frame-ancestors, sandbox ve raporlamayı desteklemez. Eksiksiz seçenek HTTP başlığıdır.
CSP çıktı kaçışlamanın yerini tutar mı?
Hayır. Kaçışlama ve temizleme XSS'i kaynağında durdurur; CSP bir şey gözden kaçtığında etkiyi sınırlar.
Politikam neden Google Analytics'i ya da bir sohbet widget'ını engelliyor?
Bu scriptler başka scriptler yükler ve kendi uç noktalarına bağlanır. Yükleyici için 'strict-dynamic' ile birlikte bir nonce kullanın ve belgelerinde listelenen uç noktaları connect-src ve img-src içine ekleyin.
report-uri ile report-to arasındaki fark nedir?
report-uri, bir URL alan eski ve kullanımdan kaldırılmış direktiftir. report-to ise Reporting-Endpoints başlığında adlandırılan bir uç noktaya atıf yapar. İkisini birden göndermek tüm tarayıcılardan rapor akmasını sürdürür.
style-src içindeki 'unsafe-inline' tehlikeli mi?
script-src içindekinden çok daha az, ama enjekte edilen stiller yine de veri sızdırma numaralarında veya arayüz sahteciliğinde kullanılabilir. Bunu geçici bir taviz olarak görün.
Bir sayfadaki tüm scriptler için aynı nonce değerini kullanabilir miyim?
Evet. Yanıt başına bir nonce, o yanıttaki her meşru script etiketi tarafından paylaşılır. Olmaması gereken şey, aynı nonce değerinin yanıtlar ya da kullanıcılar arasında, örneğin bir sayfa önbelleği yüzünden yeniden kullanılmasıdır.
CSP siteyi yavaşlatır mı?
Kullanıcılar için ölçülebilir bir miktarda değil. Tarayıcı her kaynağı politikayla karşılaştırır, bu da ucuz bir işlemdir. Asıl iş ekip tarafındadır: politikayı yeni özellikler ve üçüncü taraflarla uyumlu tutmak.
Yalnızca JSON döndüren bir API CSP göndermeli mi?
Zararı yoktur ve bir yanıtın yanlışlıkla HTML olarak render edilmesine karşı korur. API'ler için minimal bir default-src 'none'; frame-ancestors 'none' yaygındır.
Raporlardaki tarayıcı eklentisi gürültüsünü nasıl ele almalıyım?
Eklentiler script ve stil enjekte eder; bunlar chrome-extension gibi blocked-uri değerleriyle ya da tanımadığınız satır içi kaynaklarla ihlal olarak görünür. Raporları özetlerken bunları ayıklayın; sitenizdeki sorunlar değildir.
Report-only ne kadar süre çalışmalı?
Tüm önemli sayfalar ve kullanıcı akışları denenene ve raporlar yalnızca gürültü gösterene kadar; aktif bir site için genellikle bir ila birkaç hafta.