CSP - Content Security Policy
CSP, web uygulamalarımızı başta XSS olmak üzere, bir dizi güvenlik zafiyetine karşı korumak için ek bir güvenlik katmanı sunmaktadır. Elbette cürmü, CSP'nin desteklendiği browser'lar kadardır. Dolayısı ile mevzu bahis zafiyetler için (örneğin, XSS) tek başına yeterli olmayacak; CSP'nin desteklenmediği bir browserda, sitenizdeki zafiyet yine istismar edilebilecektir. CSP bir Derinliğine Savunma (defense-in-depth) olarak değerlendirilip, zafiyetin giderilmesi konusunda gerekli işlemler mutlaka yapılmalıdır.
Öyle sanıyorum Same Origin Policy (SOP) gibi katı bir mekanizmanın varlığına rağmen, web güvenliğinin yumuşak karnı Javascript ve stil dosyaları gibi dışarıdan yüklenen kaynakların bizim sayfamızın orjin'inde / context'inde çalışıyor oluşudur.
Sadece bu değil, kullanıcılardan aldığımız ve yeterli girdi (validation - filtreleme/sanitasyon) ve çıktı (encoding) denetimi yapmadan sayfalarımıza dahil ettiğimiz veriler de yine sayfamızın context'inde çalışmaktadır.
Peki web sayfamızın hangi kaynakları yükleyip, hangi kaynakları çalıştırabileceğine nasıl karar vereceğiz?
Bu noktada karşımıza Content Security Policy (CSP) çıkıyor. Adından da anlaşılacağı üzere içerik güvenliği ile ilgili politikalar / kurallar.
CSP, web uygulamalarımızı başta XSS olmak üzere, bir dizi güvenlik zafiyetine[1] karşı korumak için ek bir güvenlik katmanı sunmaktadır. Elbette cürmü, CSP'nin desteklendiği browser'lar kadardır.[2] Dolayısı ile mevzu bahis zafiyetler için (örneğin, XSS) tek başına yeterli olmayacak; CSP'nin desteklenmediği bir browserda, sitenizdeki zafiyet yine istismar edilebilecektir. CSP bir Derinliğine Savunma (defense-in-depth) olarak değerlendirilip, zafiyetin giderilmesi konusunda gerekli işlemler mutlaka yapılmalıdır.
CSP'de kurallar tanımlarken whitelist (beyaz liste) olarak bilinen bir yaklaşım kullanılmaktadır. Whitelist yaklaşımı sayesinde, yalnızca kabul ettiğimiz kaynakları belirtip, bu kural ile eşleşmeyen tüm kaynakların kullanımını engelleyebiliriz. Bu kaynakların hangileri olacağını HTTP response'ları ile beraber döndürdüğümüz CSP talimatlarında belirtmemiz yeterli.
Content-Security-Policy: script-src 'self' https://apis.google.com
Örneğin yukarıdaki CSP talimatı sayesinde, web sayfamızda yalnızca kendi originimizden (self) ve https://apis.google.com üzerinden yüklenecek scriptlere izin veriyoruz. Inline script çağrıları, olay tetikleyicileri vasıtası ile script çalıştırmak bu durumda mümkün olmayacaktır.
CSP talimatları sayesinde, yalnızca belirttiğimiz kaynaklardan yapılacak script ve style yüklemelerini değil; hangi kaynaklardan yapılan embeding'lere izin verileceğini; hangi kaynakların iframe olarak yükleneceğini ya da hangi kaynakların bizim sayfamızı iframe olarak yükleyebileceği başta olmak üzere daha pek çok HTML unsuru için browser sathında kontrol mekanizması sunmaktadır.
Bu yazıda CSP 1 'e ek olarak, CSP 2 ile birlikte gelen, nonce, hash ve yeni birkaç direktif ve CSP 3 ile birlikte gelen strict-dynamic özelliği tanıtılacaktır.
Örnek bir CSP headerı aşağıdaki gibidir:
Content-Security-Policy: KONTROL_ALANI degerler
CSP headerı ile birlikte, aşağıda sıralayacağımız kontrol alanlarındaki kaynak kullanımlarını sınırlandırabilir, tanımlayabiliriz.
base-uri: Base elementi, sayfadaki tüm relative URL'lerin kendisine çözümleneceği absolute URL'i belirten bir değerdir. <base> element'inde kullanılacak değer ile ilgili kısıt tanımlamamıza yardımcı olur. Böylece Base Tag Hijacking saldırıları engellenebilir.
child-src: Deprecated olan frame-src yerine kullanılır.[3] Sayfaya embed edilecek olan framelerin alabileceği kaynak değerleri tanımlar. Sayfamızı Frame Injection saldırılarına karşı koruyabilmek için, ek bir tedbir olarak kullanabiliriz.
connect-src: XHR, WebSockets, EventSource ile bağlanılabilecek kaynakları kısıtlar.
font-src: Fontların yüklenebileceği kaynakları belirtir.
form-action: Form tagları için geçerli action'ları belirtir.
frame-ancestors: Mevcut sayfayı frame, iframe, embed ve applet olarak yükleyebilecek kaynakları belirtir. X-Frame-Options'ın muadilidir. Clickjacking vb UI Redressing saldırılarını engellememize yardımcı olur.
frame-src: Deprecate edilmiş bir özelliktir. Bunun yerine child-src kullanılabilir.[4]
img-src: Resimlerin yüklenebileceği kaynakları tanımlar.
media-src: Video ve ses yüklenebilecek kaynakları tanımlar/kısıtlar.
object-src: Flash ve diğer plugin'lerin yükleneceği kaynakları tanımlar/kısıtlar.
plugin-types: Yüklenebilecek plugin tiplerinini belirler/limitler.
report-uri: CSP'nin ihlal edildiği durumda, raporun gönderileceği adresi belirtir.
style-src: Stil dosyaları için kaynak tanımlaması/kısıtlaması yapar.
upgrade-insecure-requests: HTTP isteklerini HTTPs olarak değiştirir.
Bu değerlerde varsayılan olarak benimsenen yaklaşım "Wide Open" olarak bilinen yaklaşımdır. Yani CSP headerlarında, yukarıdaki alanlar için bir değer belirtilmez ise, bu alanlar için herhangi bir kaynaktan yapılan yüklemelere izin verilecektir. Örneğin style-src için bir değer belirtilmezse, bu style-src: * olarak kabul edilecek ve tüm kaynaklardan stil yüklemeye izin verilecektir.
Bu davranışı değiştirmek için default-src direktifini kullanabiliriz. Burada tanımlanan değer, -src ile biten pek çok direktifi override ederek, bu direktifler için default bir değer tanımlar. Eğer default-src'i http://www.example.com olarak tanımladı isek ve font-src'ye bir değer vermediysek bu durumda fontlar da yalnızca https://www.example.com adresinden yüklenebilir.
Aşağıdakiler, default-src tanımınının override edemeyeceği direktiflerdir:
base-uri
form-action
frame-ancestors
plugin-types
report-uri
sandbox
Bir veya birden çok direktifi bir HTTP header'ı içerisinde, her talimatı birbirinden noktalı virgül (;) ile ayırarak kullanılabiliriz.
script-src https://host1.com https://host2.com; style-src https://www.example.com
Bir direktifin alacağı muhtelif değerler, boşluk ile birbirinden ayrılmalıdır:
script-src https://host1.com https://host2.com;
Önemli birkaç nokta:
CSP her sayfa için özel tanımlanmalı ve bu sayfanın HTTP yanıtında gönderilmelidir.
Bu, her sayfaya yönelik, onun spesifik ihtiyaçları için en optimum policy'yi tanımlamamıza yardımcı olacaktır.
Direktif tanımlalarında, bir dizi esneklikler mevcut.
FQDN olarak belirtebiliriz:
script-src https://www.example.com:443
Sadece hostname belirtebiliriz:
script-src example.com
Ya da sadece şema olarak belirtebiliriz:
script-src https:
script-src data:
Wilcardlar, port ve URL'in sadece subdomain kısmında kullanılabilmektedir:
script-src www.example.com:*
script-src *.example.com:*
Yine source tanımlarken, bazı özel anahtar kelimeler kullanabiliriz.
'none' : Hiçbiri. Örneğin, object-src: 'none' talimatı nedeniyle sayfada hiçbir obje embed edilemeyecektir.
'self': Mevcut sayfanın origin'i ile eşleşir. Subdomainler de dahil, başka hiçbir kaynaktan yükleme yapılamaz.
'unsafe-inline': inline Javascript ve CSS kullanımına izin verir.
'unsafe-eval': eval gibi Text-to-Javascript fonksiyonların kullanımına izin verir.
Bu anahtar kelimeler tek tırnaklar içerisinde kullanılmalıdır. Aksi takdirde, örneğin sadece self değeri belirtilirse, bu normal bir hostname gibi algılanır ve self kelimesi içeren hostname'lere yetki verilmiş olur.
script-src none
script-src self
Sandbox
Buraya kadar gördüğümüz direktiflerden farklı olarak, sandbox özelliği, sayfanın yüklediği kaynakları değil, sayfanın kendisini kısıtlamaktadır. Bu direktif kullanıldığında, sayfa "sandbox" attribute'ına sahip bir iframe içerisinde yüklenmiş gibi davranmaktadır. Böylece sayfada, scriptlerin çalışıp çalışmayacağını, formların gönderilip gönderilmeyeceğini, sayfanın benzersiz (unique) bir origin'de değerlendirilip değerlendirilmeyeceği talimatı, bu özellik ile birlikte verilmektedir.
Content-Security-Policy: sandbox allow-scripts;
CSP 'i Meta Tagları İçerisinde Tanımlamak
CSP için tercih edilen yöntem HTTP response'larında direktifleri belirtmek olsa da, CSP'leri meta tagları olarak da belirtebiliriz. Özellikle paylaşımlı hostinglerde ve sunucu headerlarına müdahale edemediğimiz durumlarda:
<meta http-equiv="Content-Security-Policy" content="default-src https://cdn.example.net;
child-src 'none'; object-src 'none'">
Meta tagları ile CSP tanımlarken, aşağıdaki direktifler kullanılamaz:
frame-ancestors, sandbox, report-uri
Inline Injection
Origin temelli bir kısıtlama/izin mekanizması ile pek çok sorun çözülebilirdi. Fakat origin temelli bir kısıtlama kullanılsa bile, CSP'nin varlık nedenlerinden biri olan XSS için hala büyük bir boşluk var, Inline Injection:
<script>alert(123);</script>
<a href= onclick="javascript:alert(123)">Click me!</a>
<img src=1 onerror="alert(1);"/>
CSP bu problemi, inline scriptleri engelleyerek çözmektedir. CSP sadece script tagları arasına gömülmüş kodları değil, inline olay tetikleyecilerini ve javascript: URL'lerini de engellemektedir.
Bu sebeple script tagları arasındaki kodlarımızı harici dosyalar olarak yeniden sayfalarımızda yüklememiz gerekecektir.
Bu birkaç iyi sonuca daha sebep olabilir:
Birincisi, external dosyaların browser tarafından cachelenmesi, sitemizin performansını olumlu yönde etkileyecektir.
İkincisi kodlarımız daha düzenli hale gelecektir. Ayrıca external dosyalarımızı minimize ederek, kodumuzda saldırganlar üzerinde görece yıldırıcı bir etkiye sahip olan obfuscation, minimizing işlemi yapılmış olacaktır.
Bütün bu faydalarına rağmen inline Javascript ve CSS'i hala kullanmak istiyorsanız, ilgili kaynaklara izin verilen direktiflerde bunu hususen belirtmelisiniz:
script-src: 'unsafe-inline'; style-src: 'unsafe-inline'
Ya da inline/embeded scriptleriniz için kriptografik nonce'lar kullanabilirsiniz:
<script nonce=EDNnf03nceIOfn39fn3e9h3sdfa>
//Some inline code I cant remove yet, but need to asap.
</script>
Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'
Güvenlik tedbiri olarak nonce'lar her istek için yeniden oluşturulmalı, tekrarlanamaz ve tahmin edilemez olmalıdır.
Nonce'ların yanı sıra hash'ler de kullanılabilir.
Hash'lerde ise, script tagına "nonce" gibi bir attribute eklemek yerine, script taglarında kullanılan script için bir hash hesaplaması yaparak, ilgili header'da bu hash'e izin verdiğimizi belirtiyoruz:
<script>alert('Hello, world.');</script>
Content-Security-Policy: script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng='
CSP, sha256, sha384 ve sha512 hash tiplerini desteklemektedir.
* Önemli bir not, JS kodunun hash'i hesaplanırken <script> tagları dahil edilmemelidir. Boşluklar, büyük-küçük harfler, whitespace karakterler hash hesaplanırken'ın hash'e dahil olduğu unutulmamalıdır. Örneğin:
header("Content-Security-Policy: script-src 'self' 'sha256-".base64_encode(hash('sha256', 'alert("allowed");', true))."'");
Eval=Evil
Girdi olarak kabul ettiği metinleri, döküman context'i içerisinde çalıştıran eval, new Function(), setTimeOut, setInterval fonksiyonlarının kullanımları CSP tarafından otomatik olarak engellenmiştir.
Bu kodumuzda birkaç değişikliği zaruri hale getirmektedir.
- Eğer JSON parsing işlemleri eval ile yapılıyor ise, bunun yerine JSON.parse fonksiyonunu kullanılmalıdır.
- setTimeout veya setInterval fonksiyonlarında kullandığımız string'leri, inline fonksiyonlara çevirmeliyiz:
yerinesetTimeout("document.querySelector('a').style.display = 'none';", 10);
setTimeout(function () { document.querySelector('a').style.display = 'none'; }, 10);
- Template sisteminiz, new Function() gibi jenerik fonksiyonları kullanıyorsa out-of-box olarak CSP 'yi destekleyen Angular gibi bir sistem kullanabilirsiniz. Template sisteminiz precompilation'ı destekliyorsa bu da kullanılabilir.
Eğer, eval gibi Text-to-Javascript fonksiyonlarının kullanımı kaçınılmaz ise, "unsafe-eval" talimatını kullanarak, CSP güvenlik mekanizmasında koca bir delik açabilirsiniz:
script-src: 'unsafe-eval'
report-uri
CSP, güvenlik ihlallerini raporlamakla kalmaz, istediğimiz takdirde, belirteceğimiz bir çağrı adresine bu güvenlik ihlallerini raporlayabilir. Bunu yapmak için CSP'nin report-uri özelliğini kullanabiliriz.
Content-Security-Policy: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;
Bu belirtimle birlikte CSP, aşağıdaki gibi bir JSON datayı, belirttiğimiz adrese POST metodu ile gönderecektir.
{
"csp-report": {
"document-uri": "http://example.org/page.html",
"referrer": "http://evil.example.com/",
"blocked-uri": "http://evil.example.com/evil.js",
"violated-directive": "script-src 'self' https://apis.google.com",
"original-policy": "script-src 'self' https://apis.google.com; report-uri
http://example.org/my_amazing_csp_report_parser"
}
}
document-uri: İhlalin olduğu sayfayı
referer: Sayfamızın referer'ini
blocked-uri: Bloke edilen kaynağı
violated-directive: İhlal edilen güvenlik direktifini.
origin-policy: CSP direktiflerini belirtir.
Böylesine katı bir biçimde uygulanan güvenlik politikasını, web uygulamanızın bir parçası haline getirmekte tereddütleriniz olabilir. Report özelliği sayesinde, CSP'yi kısıtlayıcı modda değil, yalnızca ihlallerin bildirilmesi amacıyla kullanabilirsiniz. Böylece CSP'yi uygulamanıza bir güvenlik mekanizması olarak dahil etmeden önce, uygulamanızdaki hal ve gidişatı da izlemiş olursunuz:
Content-Security-Policy-Report-Only: default-src 'self'; ...; report-uri
/my_amazing_csp_report_parser;
Arzu ederseniz, kısıtlayıcı direktifler ile birlikte, raporlamayı da kullanabilirsiniz.
Best Practice olarak, henüz web sitenizde kullanılmayan bir CSP'yi önce Report modunda devreye alarak, uygulamanızın CSP entegrasyonuna ne kadar hazır olduğunu ve bu muazzam değişikliğin sayfalarınızı nasıl etkileyeceği görebilirsiniz. Github, Dropbox gibi örnek hikayeler bu hususta faydalı olacaktır.
Ömrüne bereket CSP!
Google tarafından yayınlanan son makalede[5], CSP tanımlarındaki whitelist yaklaşımının yumuşak karnını detaylı analizleri ile birlikte incelenirken; buna karşılık, nonce-based olarak adlandırılan yeni bir yaklaşım önerilmektedir.
Araştırma 1 milyon 687 bin hostname'i ve 26 bin tekil CSP headerı kapsıyor. Bugüne kadarki en büyük güvenlik analizlerinden biri olan rapora göre, CSP'yi bypass etmek için tespit edilen 3 yaygın yöntem analiz ediliyor. Bunlar Open Redirection, Insecure JSONP Endpoint ve AngularJS CSP Compability Mode.
Script yüklemelerinde, whitelist'e alınan 15 domainin 14'ünde insecure endpoint zafiyeti belirlendiği belirtilen raporda, bu noktalardan yapılacak atakların CSP policy'lerini etkisiz hale getirmesinin kaçınılmaz olduğu ifade ediliyor.[6]
CSP Bypass - Open Redirection:
Content-Security-Policy: script-src example.org partially-trusted.org/foo/bar.js
// Allows loading of untrusted resources via:
<script src="//example.org?redirect=partially-trusted.org/evil/script.js">
CSP Bypass - Insecure JSONP Endpoint
<script src="/api/jsonp?callback=evil"></script>
CSP Bypass - AngularJS CSP Compability Mode
<script src="angular.js"></script>
<div ng-app>{{ executeEvilCodeInUnsafeSandbox() }} </div>
strict-dynamic
Whitelist temelli CSP politikaları kullanmak yerine, nonce-based olarak isimlendirilen bir yaklaşım öneren Google, dinamik yüklemeler ile birlikte, CSP policylerimizi kevgire çevirmeden, duruma hakim olmamızı sağlıyor. Bunun için CSP spesifikasyonuna strict-dynamic kullanımını öneren Google, kendi browserlarında bu yöntemi desteklemeye başladı bile. Google dışında Opera tarafından da strict-dynamic kullanımı desteklenmektedir.
Hep birlikte bu yöntemin nasıl çalıştığını inceleyelim. example.com/map.js üzerinden bir script yükleyeceğiniz. Bunun için bir CSP tanımladığımız varsayalım:
Content-Security-Policy: script-src example.com;
example.com'a güveniyoruz ve çalışma zamanında DOM'a yükleyeceği nesneler CSP engeline takıldığı için, 'unsafe-inline' direktifleriyle, CSP'imizde bize göre küçük bir esneklik, saldırganlara göre ise koca bir gedik açtık:
Content-Security-Policy: script-src example.com 'unsafe-inline';
Üstelik bunu yaparken daha en başından, example.com domainini beyaz listeye alarak, example.com 'da olaşabilecek başka saldırı parametrelerinden de sitemizdeki CSP korumasının bypass edilmesine kapı araladık.
Example.com 'da bir Open Redirection olduğunu varsayalım:
example.com/redirectme.php?go=http://attacker.com/bad.js
Bunun yerine, nonce-based CSP 'i kullansa idik, pek çok saldırı parametresini ortadan kaldıracaktık:
Content-Security-Policy: script-src 'nonce-random-123' 'strict-dynamic';
<script src="http://exampe.com/map.js" nonce=random-123></script>
Sadece example.com/map.js üzerinden yüklenen script'e güvendiğimizi, script'in yüklendiği bloka bir nonce değeri atayarak, güvenli olarak belirttik. Yine strict-dynamic talimatları ile de, bu blok içerisinden yapılacak yüklemelere izin verdiğimizi belirtmiş olduk.
Böylece hem koca bir example.com etki alanını whitelist'e almak zorunda kalmadık; hem de dinamik yüklemeler için başka saldırılara da kapı aralayarak 'unsafe-inline', 'unsafe-eval' gibi talimatları kullanmamış olduk.
Geçmişin Ağır Yükü: Geriye Uyum!
Nonce sadece CSP 2 ile birlikte destekleniyor. strict-dynamic yapısı ise, CSP 3 ile birlikte gelen özelliklerden.
Nonce'ı kullandığınız takdirde, eğer ziyaretçi browserı CSP 2'yi desteklemiyorsa ne olacak?
Buna çare olarak, yani bir geriye uyum olarak nonce ile birlikte 'unsafe-inline' talimatı da kullanılması öneriliyor.
Endişeniz yersiz değil! Buradaki amaç kısaca şu.
Content-Secutity-Policy: script-src 'nonce-random123' 'unsafe-inline'
<script nonce=random123>
console.log("code works");
</script>
Nonce kullanıldığında, unsafe-inline kullanımı browser tarafından ignore ediliyor. Yani CSP 2 ve üzeri sürümleri destekleyen browser'larda bu satırdaki 'unsafe-inline' talimatı yok sayılacak. Nonce talimatının tanınmadığı, yani CSP 1'in desteklendiği browser'larda ise 'unsafe-inline' çalışacak ve sayfanınızın bütünlüğü bozulmayacak, yani kodlarınız çalışacak.
strict-dynamic'de ise geriye uyum için önerilen implementasyon şöyle:
Content-Security-Policy: script-src 'nonce-random123' 'strict-dynamic' https: http:
strict-dynamic kullanımı ile birlikte, CSP 3 ve üzeri sürümlerin desteklendiği browser'larda, CSP talimatındaki https: ve http: talimatları ignore edilecek.
Kuşbakışı CSP
Web uygulamanıza CSP implemente etmek için https://report-uri.io/home/generate adresini kullanarak, çoklu seçme alanından arzu ettiğiniz CSP konfigürasyonunu belirleyebilirsiniz.
Web uygulamanıza implemente ettiğiniz CSP 'nin özet bir değerlendirmesini ise Mozilla Firefox 43[7] sürümü ile birlikte gelen Developer Toolbar üzerinden security csp komutunu kullanarak yapabilirsiniz.
Bunlara ilaveten Google'ın yakın bir zamanda duyurduğu CSP Mitigator'ı kullanarak, web sayfanızdaki CSP direktiflerini test edebilir, eklemeyi düşündüğünüz CSP talimatlarının sayfanızda hangi çakışmalara yol açacağını görebilirsiniz.[8]
Netsparker CSP'yi Nasıl Raporluyor?
Netsparker güvenlik check'lerine son release ile birlikte Content-Security-Policy 'yi de ekledik. CSP'nin implemente edilip edilmediğinden, hatalı implementasyon uyarılarına kadar; güvenli olmayan CSP direktiflerinden, hassas verilerin sızmasına yol açabilecek CSP konfigürasyonlarına kadar pek çok kontrol scan esnasında yapılıyor ve sonuçlar Information seviyesinde raporlanıyor.
Information seviyesinde raporlanan sonuçlarda, CSP implementasyonu ile ilgili uyarıları OWASP Proactive Controls C9 yani Leverage Security Frameworks and Libraries kapsamında sınıflandırırken; hatalı ya da güvensiz bir biçimde set edilen CSP headerları ise, OWASP A5 yani Security Misconfiguration olarak sınıflandırıyoruz.
Bunun tek istisnası, report-uri ile birlikte, CSP ihlallerini bildirmek için unsecure yani HTTP ya da farklı bir domain kullanıyorsanız, bunları OWASP A6 - Sensitive Data Exposure olarak sınıflandırıyoruz.
Kaynaklar
- http://www.html5rocks.com/en/tutorials/security/content-security-policy/#disqus_thread
- https://report-uri.io/home/generate
- https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#tab=Headers
- https://security.googleblog.com/2016/09/reshaping-web-defenses-with-strict.html
- https://content-security-policy.com/
- https://csp-evaluator.withgoogle.com/
[2] http://caniuse.com/#search=content%20security%20policy
[3] frame-src , CSP 3 ile birlikte deprecated listesinden çıkarıldı.
[4] frame-src , CSP 3 ile birlikte deprecated listesinden çıkarıldı.
[5] CSP Is Dead, Long Live CSP! On the Insecurity of Whitelists and the Future of Content Security Policy
[6] "We observe that 14 out of the 15 domains most commonly whitelisted for loading scripts contain unsafe endpoints; as a consequence, 75.81% of distinct policies use script whitelists that allow attackers to bypass CSP."
[7] https://hacks.mozilla.org/2015/10/inspecting-security-and-privacy-settings-of-a-website/
[8] https://security.googleblog.com/2016/09/reshaping-web-defenses-with-strict.html