CSP (Content Security Policy) Nedir?

Web sayfası ve uygulamalarında, farklı bir kaynakta barındırılan dosyaları (css, js, font, vb.) çağırdığımızda istemesek de bazı olası güvenlik problemleri için de bir kapı aralamış olabiliriz.

AA

Sürece bir de kullanıcıların sayfaya veri ekleyebilecekleri (formlar, vb.) senaryoları dahil ettiğimizde risk düzeyi daha da artmakta. Bu gibi durumları önlemek adına girdi ve çıktıların denetimi neredeyse bir zorunluluk haline gelmekte. Peki, bu süreci nasıl yönetmeli ve web sayfasını veya uygulamanın hangi kaynakları güvenli kabul edeceğine nasıl karar vereceğiz?

Content Security Policy (CSP)

Adından da anlaşılacağı üzere, Content Security Policy (CSP) bir içerik güvenliği ile ilgili kurallar barındıran bir güvenlik politikası. Bu kurallar bütünü sayesinde Cross-Site Scripting (XSS) saldırıları gibi yaygın güvenlik problemlerine karşı bir koruma oluşturmak amacıyla geliştirilmekte1 2 3. Elbette CSP'i bütüncül bir çözüm olarak ele almak doğru olmayacaktır. Çünkü, bu politika için tarayıcı uyumluluğu bir ön koşuldur4. CSP sürekli geliştirilen bir kurallar bütününe sahip olduğu için farklı sürümlere sahip. Dolayısıyla, tarayıcı desteği ile ilgili kontrolleri CSP25 ve CSP36 üzerinden yapmakta fayda var. Yine bu uyum çerçevesinde bazı tarayıcı uzantılar (extension) ve yer imleri (bookmarklets) ile ilgili sorunlarla karşılaşılması muhtemel7.

CSP (Content Security Policy)

CSP'de istisna tanımında whitelist (beyaz liste) olarak ifade edebileceğimiz yönergeler kullanılmaktadır. Bu sayede, yalnızca izin verilen kaynaklar belirtilen direktifler doğrultusunda işlemler gerçekleştirebilirler, kurallar dışında kalan kaynaklar ve kaynak tipleri ise çalışırılmazlar. İstisna tanımlarını ve direktirleri HTTP response'ları ile beraber dönen CSP talimatları ile belirtmek yeterli olacaktır2 3.

Direktif (Directive) Direktif Türü (Directives Types) Kontrollü kaynak türü (Controlled resource type)
child-src fetch Dokümanlar (frames), [Shared]Workers
connect-src fetch Script URL işlemleri
default-src fetch Tüm kaynaklar (fallback)
font-src fetch Fontlar
frame-src fetch deprecated. frames
img-src fetch Görseller
manifest-src fetch Manifest dosyaları
media-src fetch Medya dosyaları (audio, video)
prefetch-src fetch prefetched ya da prerendered URL işlemleri
object-src fetch Plug-in formatları (object, embed)
script-src fetch Script dosyaları
script-src-elem fetch Script istekleri ve blokları
script-src-attr fetch Satır içi olay işleyiciler (Inline event handlers)
style-src fetch Stylesheet dosyaları
style-src-elem fetch Satır içi style tanımları
style-src-attr fetch Satır içi style özellik tanımları
worker-src fetch [Shared]Workers
base-uri document Base element
plugin-types document Plug-in türleri (application/x-shockwave-flash, pplication/pdf, vb.)
sandbox document Response kontolü. Diğer direktiflerden farklı olarak sayfanın kendisini ele alır. Popupları engellemek, formları durdurmak, javascript işlemlerini engellemek gibi amaçlarla kullanılabilir8
form-action navigation Form action'ları
frame-ancestors navigation frame, object, embed
navigate-to navigation URL tanımları (a, form, window.location, window.open, vb.)
report-uri reporting deprecated
report-to reporting direktif raporlama9. Belirtilen direktiflerin ihlali durumunda bir JSON nesnesi gösterilen URL'e POST edilir

Tabloda görüldüğü üzere CSP direktirleri ile script ve style dosyalarının yanı sıra, embed, iframe işlemleri de kontrol edilebilmekte6. İlgili tablodaki sütun tanımlarını içeren kullanımı şu şekilde özetleyebiliriz: Content-Security-Policy: <Direktif> <Kontrollü Kaynaklar>. Elbette bu tanımlama sunucu konfigürasyon tanımlarının yanı sıra back-end ve/veya meta etiketleri ile de yapılabilir10 11.

Yukarıdaki açıklama ve tablo temel alınarak şu şekilde basit bir örnek tanımlama yapabiliriz; Content-Security-Policy: script-src 'self' https://apis.google.com.

// PHP
header("Content-Security-Policy: script-src 'self' https://apis.google.com");
// JavaScript
app.use(function (req, res, next) {
  res.setHeader(
    'Content-Security-Policy',
    "script-src 'self' https://apis.google.com"
  );
  next();
});
<!-- HTML //-->
<meta http-equiv="Content-Security-Policy" content="script-src 'self' https://apis.google.com" />

Meta kullanımında frame-ancestors, sandbox, report-uri direktifleri tanımlanamaz.

Bu CSP tanımı içerisinde yer alan self web sayfamızda yalnızca kendi kaynağımızdan (origin) ve https://apis.google.com kaynağıdan çağırılan script'lerin çalıştırılmasına izin vermektedir. Tanım içerisinde inline-script yer almadığı için olay tetikleyiciler aracılığı ile script çalıştırmak mümkün olmayacaktır.

Elbette self dışında kullanılabilecek farklı kaynak tanımları da mevcut. Bu tanımlar '...' (tek tırnak) içerisinde tanımlanırlar. Aksi durumda, değer bir alan adı tanımı gibi değerlendirilir.

self
Alt alan adları (subdomain) da dahil, mevcut origin ile eşleşir.
none
Hiçbir kaynağa izin verilmeyeceğini belirtir. Örneğin, font-src: 'none' değeri hiçbir kaynaktan font eklenemeyeceğini ifade eder.
unsafe-inline
Inline JavaScript ve CSS kullanımlarına izin verir.
unsafe-eval
eval() gibi string-to-JavaScript metotlarının kullanımına izin verir.

Direktif kullanımında eğer bir değer belirtilmezse, ilgili tanım * kullanımında olduğu gibi tüm kaynaklara izin verecektir. Örneğin, style-src için bir değer belirtilmemişse style-src: ön tanımlı olarak * değerini almış kabul edilir ve tüm kaynaklardan style dosyalarının yüklenmesine izin verir.

default-src tabloda da belirtildiği üzere, tüm kaynakları kapsayan bir direktif. Bu sayede, -src ile biten pek çok direktifi tek satırda kontrol etmek mümkün. Örneğin, default-src için kaynak olarak 'self' tanım yaparsak aşağıdaki gibi bir sonuç elde etmiş oluruz.

// Content-Security-Policy: default-src 'self'

Content-Security-Policy: connect-src 'self';
                         font-src 'self';
                         frame-src 'self';
                         img-src 'self';
                         manifest-src 'self';
                         media-src 'self';
                         prefetch-src 'self';
                         object-src 'self';
                         script-src-elem 'self';
                         script-src-attr 'self';
                         style-src-elem 'self';
                         style-src-attr 'self';
                         worker-src 'self'

Elbette bu tanımlama değişkenlik gösterebilir. Örneğin, default-src için kaynak olarak 'self' ve sonrasında sonrasında farklı bir direktif(ler) için farklı bir kaynağı değer olarak verebiliriz12.

// Content-Security-Policy: default-src 'self'; script-src-elem https://example.com
Content-Security-Policy: connect-src 'self';
                         font-src 'self';
                         frame-src 'self';
                         img-src 'self';
                         manifest-src 'self';
                         media-src 'self';
                         prefetch-src 'self';
                         object-src 'self';
                         script-src-elem https://example.com;
                         script-src-attr 'self';
                         style-src-elem 'self';
                         style-src-attr 'self';
                         worker-src 'self'

Aynı direktif tanımları arasından, verilen son değer uygulanır.

Elbette yapılabilecek işlemler sadece bunlar değil. Örneğin, CSP2 ile birlikte nonce ve hash gibi dinamik tanımlamalar yapamk5 ve CSP3 ile birlikte strict-dynamic özelliklerinden de faydalanmak mümkün6 13.

Direktif Tanımlama

Birden fazla direktifi ; (noktalı virgül) ile, bir direktifin alacağı çoklu değerler ise tek boşluk (space) ile birbirinden ayrılır; script-src 'self' https://example.com; style-src https://www.example.com.

Ek olarak, direktif değerlerini gruplandırarak da tanımlamak mümkün. Elbette gruplandırmaların kendi içerisinde istisnalar da oluşturabileceğini ve bu nedenle güvenlik sorunlarının oluşabileceğini unutmamak gerekir7 14.

// Sadece hostname tanımı
script-src example.com

// Sadece scheme tanımı
script-src https:
script-src data:

// Wilcard tanımı
script-src www.example.com:*
script-src *.example.com:*

Örnek Kullanım

Google Tag Manager Live ve Preview mode için farklı kaynak kullanımlarına ihtiyaç duymakta. Genel olarak, stabil bir şekilde çalışabilmesi için inline script, inline eval() ve inline styles kullanımlarına ihtiyaç duymaktadır. Aşağıdaki örnekler çeşitli kaynaklardan derlenmiştir15 16 17 18 19. İlgili kodları zaman içerisinde geribildirimlerle genişletmeye ve güncellemeye devam edeceğim.

// Google Analytics
script-src 'self' https://www.google-analytics.com;
img-src https://www.google-analytics.com www.google-analytics.com https://stats.g.doubleclick.net;
connect-src https://www.google-analytics.com www.google-analytics.com https://stats.g.doubleclick.net;
// Google Tag Manager
script-src 'unsafe-eval' 'unsafe-inline' https://tagmanager.google.com https://www.googletagmanager.com;
style-src 'unsafe-inline' https://tagmanager.google.com https://fonts.googleapis.com;
img-src 'unsafe-inline' https://ssl.gstatic.com;
// Google Ads & Remarketing
script-src: https://www.googleadservices.com https://googleads.g.doubleclick.net https://www.google.com;
img-src: https://googleads.g.doubleclick.net https://www.google.com;
frame-src: https://bid.g.doubleclick.net;
// Facebook Pixel
script-src: https://connect.facebook.net;
img-src: https://www.facebook.com;
child-src: https://www.facebook.com https://staticxx.facebook.com;
connect-src: https://www.facebook.com/tr;
form-action: https://connect.facebook.net;
// Google Fonts
style-src: https://fonts.googleapis.com;
font-src: https://fonts.gstatic.com;
// Hotjar
img-src: http://*.hotjar.com https://*.hotjar.com http://*.hotjar.io https://*.hotjar.io;
script-src: http://*.hotjar.com https://*.hotjar.com http://*.hotjar.io https://*.hotjar.io 'unsafe-eval' 'unsafe-inline';
connect-src: http://*.hotjar.com:* https://*.hotjar.com:* http://*.hotjar.io https://*.hotjar.io wss://*.hotjar.com;
frame-src: https://*.hotjar.com http://*.hotjar.io https://*.hotjar.io;
font-src: http://*.hotjar.com https://*.hotjar.com http://*.hotjar.io https://*.hotjar.io;
// Disqus
default-src 'none' ;
script-src 'self' disqus.com disqus-csp.disqus.com c.disquscdn.com;
style-src 'self' c.disquscdn.com;
img-src 'self' referrer.disqus.com c.disquscdn.com;
connect-src links.services.disqus.com;
child-src disqus.com;
frame-src disqus.com;
upgrade-insecure-requests;
block-all-mixed-content;

Eğer report-uri kullanacaksanız JSON çıktısı için Hookbin20 veya Pipedream21 servislerinden faydalanabilirsiniz.

Son olarak

W3C tarafından paylaşılan kaynaklarda pek çok örnek yer almakta. Ancak, elbette özel çözümlere ihtiyaç duyacağınız çok fazla senaryo ile karşılaşacağınızdan eminim. Bu gibi durumlarda kullanılmak üzere Google tarafından paylaşılan csp-evaluator ile doğrulama işlemlerini gerçekleştirebilirsiniz.