CSP Şakaya Gelmez, Büyük Bir Ciddiyetle Set Edeceksin Şöyle Değil Mesela…
Web sitelerinizin emniyet kemeri niteliğindeki Content-Security Policy (CSP) yeterince doğru kullanıyor muyuz? CSP kullanımında yapılan hatalar güvenliğinizde büyük gedikler açabilir. Doğru CSP kullanımı ve araştırmalara dayalı örnekleri sizler için yazdık.
2011 yılında hayatımıza giren Content-Security-Policy yani kısa adıyla CSP XSS başta olmak üzere bir dizi zafiyete karşı istemci taraflı güvenliğin önemli yapı taşlarından biri oldu.
Özellikle de son zamanlarda bypass teknikleriyle adından söz ettiren CSP, Google ekibinin öncü çabalarıyla 3.0 sürümünde bu eksikleri giderdi. Her yeni bypass haberinden sonra browser üreticileri CSP 'yi güçlendirmeye devam ediyor.
Fakat CSP'nin bypasslara yol açan sorunları dışında yanlış kullanımları da kritik sonuçlara yol açabiliyor. Güvenlik standartlarının kötülerin elinde nasıl kabusa dönüştüğüne Haftanın Hackleri'nde zaman zaman değiniyoruz. Örneğin Public Key Pinning teknolojisinin bir header injection ile birleştiğinde, bırakınız sitenizi güvenli hale getirmeyi, sitenize erişimi dahi felç edebileceğini RansomPKP örneğinde görmüştük.
Dolayısıyla güvenlik sadece ürün değil süreçtir yaklaşımından hareketle, bu sürecin en önemli kısmının kullanılan mekanizmayı doğru anlamak ve implemente etmek olduğunu söyleyebiliriz.
Useless CSP adındaki sitede araştırmacılar üşenmemiş, web sayfalarındaki yanlış CSP implementasyonlarını gün be gün raporlamışlar. İşte onlardan bazıları ve yol açacağı muhtemel riskler.
31 Ağustos 2018 tarihinde The New Yorker gazetesinin HTTP yanıtından alınan CSP headerı:
Content-Security-Policy: default-src https: data: 'unsafe-inline' 'unsafe-eval';
child-src https: data: blob:;
connect-src https: data: blob:;
font-src https: data:;
img-src https: blob: data:;
media-src blob: data: https:;
object-src https:;
script-src https: data: blob: 'unsafe-inline' 'unsafe-eval';
style-src https: 'unsafe-inline';
block-all-mixed-content;
upgrade-insecure-requests;
report-uri https://capture.condenastdigital.com/csp/the-new-yorker
Görüldüğü gibi unsafe-inline ve unsafe-eval gibi CSP 'nin gardını düşüneren yani inline script çalışmasına, event attributelarından, script çalışmasına imkân veren bu CSP talimatı sitenin istemci taraflı güvenliğinde koca bir delik açıyor. Anlaşılan o ki yukarıdaki talimatın tek iyi tarafı bağlantıları https olarak zorlaması.
Peki ne yapılmalı idi?
CSP 3.0 daha önce Netsparker Türkiye Blogu'nda yer verdiğimiz gibi kod bloklarının whitelist edilmesi için strict-dynamic gibi bir seçeneğe imkân veriyordu. Üstelik CSP 3.0'un desteklendiği tarayıcılarda unsafe-inline ve unsafe-eval gibi güvensiz opsiyonlar da strict-dynamic sayesinde override ediliyordu.
Yine yukarıda script-src default-src alanlarında data: protokolünün whitelist edilmesi aşağıdaki gibi bir saldırıyı mümkün kılacaktır:
www.victim.com/index.php?jsfile=data:,alert(1)
<script src="data:,alert(1);"></script>
18 Temmuz'da yakalanan bir CSP headerı ise Google'ın blog servisi Blogger'a ait:
content-security-policy: script-src 'self' *.google.com *.google-analytics.com 'unsafe-inline' 'unsafe-eval' *.gstatic.com *.googlesyndication.com *.blogger.com *.googleapis.com uds.googleusercontent.com https://s.ytimg.com https://i18n-cloud.appspot.com www-onepick-opensocial.googleusercontent.com www-bloggervideo-opensocial.googleusercontent.com www-blogger-opensocial.googleusercontent.com https://www.blogblog.com;
report-uri /cspreport
Google CSP 3.0'ın lokomotifi olan bir şirket olmasına rağmen bu kötü pratik ile doğrusu bizleri üzdü. Muhtemelen servisler çalışma zamanında DOM ile etkileşime girmek ve yeni elemanlar eklemek zorunda oldukları için kullanılan unsafe-inline ve unsafe-eval talimatları risk arzediyor. Google, kullanıcılara önerdiği şekliyle strict-dynamic talimatını kullanabilirdi.
Peki CSP İmplementasyonunda Nelere Dikkat Etmeliyiz?
Daha fazla örneğe Useless CSP sitesi üzerinden ulaşabilirsiniz.
CSP İmplementasyonunda Sık Karşılaşılan Hatalar ve Çözüm Önerileri
İlim Kendin Bilmektir: Report-Only CSP Monitoring Mode
Nasıl ki güvensiz opsiyonlarla set edilen bir CSP headerı zırhınızda koca bir delik açacaksa, yanlış set edilen bir CSP headerı da sitenizin işleyişini de felç edebilir. Bugün web dünyasında siteler üçüncü parti kaynaklara ziyadesi ile bağımlı. Sadece üçüncü parti kaynaklardan değil, tarayıcılardan bir siteye yapabileceğimiz maksimum concurrent limitinden dolayı, performans iyileştirmeleri için kendi kontrolü altındaki subdomainler üzerinden örneğin static.example.com, scripts.example.com gibi domainler üzerinden bu yükleme işlemlerini gerçekleştiriyorlar.
Yahut inline script çağrımları, event handlerlar içerisinde script kullanımları bir hayli yoğun.
Kullandığınız tüm üçüncü parti bağımlılıkları, CDN adreslerini whitelist etmeniz gerekiyor. Evet subdomainler size ait olsa bile, onları da yine CSP talimatınızda whitelist etmeniz gerekiyor.
CSP kullanırken sorun yaşayıp yaşamayacağını bilmenizin müneccim olmak dışında da yolları var: CSP Report-Only mode.
CSP kurallarını sitenizde uygulamaya geçirmeden önce bir best-practice olarak Report-Only modda kullanabilirsiniz. Report-Only modda kullanılan CSP headerının browser için herhangi bir dayatması yoktur, talimata uymayan istekler, kod blokları report-uri attribute'ünde belirttiğiniz end-point'e raporlanır. Böylece siz de CSP talimatını aktif hâle getirmeden eksik olduğunuz noktaları görebilir, whitelist etmeniz yahut başka bir çağrı ile değiştirmeniz gereken bağımlılıkları öğrenebilirsiniz. Dahası CSP'nin hiç hoşlanmadığı inline script kullanımlarına karşı kodunuzu daha düzenli hale getirerek, yani bu inline script ve style kullanımlarını script ve stil dosyalarında muhafaza ederek kodunu daha düzenli hâle getirebilirsiniz.
Örnek bir kullanım:
Content-Security-Policy-Report-Only: script-src 'self'; report-uri /my_amazing_csp_report_parser;
Yukarıdaki kullanım script yüklemelerinde sadece sitenin şema host ve port'dan oluşan originini yetkilendirir. Bunun dışında kalan tüm originlerden yapılacak script yüklemeleri, inline script çağrıları, event attributelarındaki script kodlarında CSP bir uyarı tetikleyecek, bu uyarı report-uri attribute'ünde belirtlen end-point'e gönderilecektir.
Report-Only ile ilgili önemli bir not daha aktararak diğer maddelere geçelim. Bildiğiniz üzere CSP'yi hem bir response header olarak, hem de meta tagları kullanarak set edebilirsiniz. Fakat CSP'yi Report-Only modda kullanmak isterseniz, CSP talimatlarınızı meta tagları arasında tanımlayamazsınız.
Daha fazla ayrıntıya Content-Security-Policy makalemizden ulaşabilirsiniz.
Uygulanmayan Kural, Kural Değildir: CSP Kuralları Report-Only Modda Uygulanmaz
Yukarıda da belirttiğimiz gibi Report-Only modda kullanılan CSP talimatları browser üzerinde zorlayıcı değildir. Web uygulamasının işleyişindeki hayati etkileri nedeniyle, Report-Only mod sorunsuz bir implementasyonu gözlemlemek için tasarlanmıştır. CSP Report-Only modun herhangi bir zorlayıcılığı olmadığını bilerek, sitemiz için kararlaştırdığımız CSP talimatlarını Report-Only modu dışında set etmek gerekir.
Örnek bir kullanım:
Content-Security-Policy: script-src 'self'; report-uri /my_amazing_csp_report_parser;
default-src 'nin limitleri
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. 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.
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 tiplerini 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 vermedi isek bu durumda fontlar da yalnızca https://www.example.com adresinden yüklenebilir.
Aşağıdakiler, default-src tanımınının fallback olarak kullanılmayacağı direktiflerdir:
base-uri
form-action
frame-ancestors
plugin-types
report-uri
sandbox
object-src'yi Unutmayın!
CSP'nin başta XSS olmak üzere bir dizi zafiyete karşı istemci taraflı önemli bir güvenlik mekanizması olduğunu söylemiştik. XSS denilince akla script execution dolayısıyla script ve style yüklenebilecek kaynakların sınırlandırılması geliyor olabilir. Oysa Javascript kodu çalıştırabilecek başka HTML elemanları da mevcut. Örneğin <embed>, <object> ve <applet> elemanları.
default-src genel geçer bir fallback mekanizması olduğundan ve yukarıda arz ettiğimiz pek çok girdi noktası için fallback işlevi göreceğinden kullanmaktan kaçınabilirsiniz. Muhtemelen default-src'yi hiçbir durumda none olarak set etmek istemeyeceksiniz. Ama haklı olarak plugin yüklemelerini engellemek isteyebilirsiniz. Bu durumda default-src talimatınız ne olursa olsun, object-src'yi 'none' olarak set edebilirsiniz. Bu seçeneği kullandığınızda sayfanız bağlamında plugin yüklemeleri engellenecektir.
Örnek bir kullanım:
Content-Security-Policy: default-src 'self'; object-src 'none';
CSP Keywordlerine Dikkat
CSP, talimatlarda kullanabileceğiniz 'none','self', 'unsafe-inline','unsafe-eval' gibi keyword seçenekleri sunar.
'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 adındaki hostname'i whitelist etmiş olur.
script-src none
script-src self
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, sunucu konfigürasyonuna 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
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'
Büyük Kararlar, Büyük Değişimler : Kodlarımızı Biraz Düzenleyelim
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 hâlâ 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 hâle 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 hâlâ kullanmak istiyorsanız, ilgili kaynaklara izin verilen direktiflerde bunu hususen belirtmelisiniz:
script-src: 'unsafe-inline'; style-src: 'unsafe-inline'
nonce mu hash mi?
CSP talimatlarını kullanırken girdi noktaları için güvenilir kaynakları sadece origin bazlı whitelist etmekle sınırlı değilsiniz. Kod bloklarını nonce ya da hash özetlerini kullanarak da whitelist edebilirsiniz.
Aşağıdaki gibi bir kod bloğumuz olduğunu varsayalım:
<script nonce=EDNnf03nceIOfn39fn3e9h3sdfa>
//Some inline code I cant remove yet, but need to asap.
</script>
Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'
Bu kod bloğuna atadığınız bir nonce değeri ile kod bloğunu whitelist edebilmek mümkün. Güvenlik tedbiri olarak nonce'lar her istek için yeniden oluşturulmalı, tekrarlanamaz ve tahmin edilemez olmalıdır.
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.
Modası Geçmiş CSP Talimatları
X-Content-Security-Policy ve X-Webkit-CSP talimatları deprecate edilmiştir. Bunun yerine Content-Security-Policy talimatını kullanmalısınız.
CSP 3'a Hazır Mısınız?
1 milyon 687 bin hostname'i ve 26 bin tekil CSP headerı kapsayan araştırma. bugüne kadarki en büyük güvenlik analizlerinden biri olarak dikkatleri çekti. Araştırmada CSP'yi bypass etmek için tespit edilen 3 yaygın yöntem analiz ediliyordu. Bu yöntemeler sırası ile Open Redirection, Insecure JSONP Endpoint ve AngularJS CSP Compability Mode idi.
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 ediliyordu.
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 araştırma ile birlikte desteklemeye başladı.
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ı parametreleri ile sitemizdeki CSP korumasının bypass edilmesine kapı araladık.
Şimdi de 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 domainini whitelist'e almak zorunda kalmadık; hem de dinamik yüklemeler için başka saldırılara da kapı aralayabilecek 'unsafe-inline', 'unsafe-eval' gibi talimatları kullanmamış olduk.
İstikbal Köklerdedir: 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.
Bu çalışmada özetlenen noktaların doğru bir CSP implementasyonu konusunda yardımcı olmasını diliyoruz.
CSP konusunda Netsparker Türkiye Blogu'nda yayınlanan makalemize ulaşmak için lütfen tıklayınız.