Katı İçerik Güvenliği Politikası (İGP) ile siteler arası komut dosyası kullanımını (XSS) azaltın

Lukas Weichselbaum
Lukas Weichselbaum

Tarayıcı desteği

  • Chrome: 52.
  • Edge: 79.
  • Firefox: 52.
  • Safari: 15.4.

Kaynak

Bir web uygulamasına kötü amaçlı komut dosyaları yerleştirme olanağı olan siteler arası komut dosyası çalıştırma (XSS), on yıldan uzun süredir en büyük web güvenlik açıklarından biri olmuştur.

İçerik Güvenliği Politikası (İGP), XSS'yi azaltmaya yardımcı olan ek bir güvenlik katmanıdır. Bir CSP'yi yapılandırmak için Content-Security-Policy HTTP üstbilgisini bir web sayfasına ekleyin ve kullanıcı aracısının bu sayfa için hangi kaynakları yükleyebileceğini kontrol eden değerleri ayarlayın.

Bu sayfada, çoğu yapılandırmada atlanabilir oldukları için sayfayı genellikle XSS'ye açık bırakan, yaygın olarak kullanılan ana makine izin verilenler listesi tabanlı İGP'ler yerine XSS'yi azaltmak için tek seferlik değerlere veya karma oluşturma işlemlerine dayalı bir İGP'nin nasıl kullanılacağı açıklanmaktadır.

Anahtar terim: Tek seferlik, bir <script> etiketini güvenilir olarak işaretlemek için kullanabileceğiniz, yalnızca bir kez kullanılan rastgele bir sayıdır.

Temel terim: Karma işlevi, bir giriş değerini karma adı verilen sıkıştırılmış bir sayısal değere dönüştüren matematiksel bir işlevdir. Satır içi <script> etiketini güvenilir olarak işaretlemek için bir karma oluşturma işlemi (ör. SHA-256) kullanabilirsiniz.

Tek seferlik rastgele sayılara veya karma oluşturma işlemlerine dayalı bir İçerik Güvenliği Politikası genellikle katı CSP olarak adlandırılır. Bir uygulamada katı bir CSP kullanıldığında, HTML ekleme kusurlarını bulan saldırganlar genellikle bu kusurları kullanarak tarayıcıyı, güvenlik açığı olan bir belgede kötü amaçlı komut dosyaları yürütmeye zorlayamaz. Bunun nedeni, katı CSP'nin yalnızca karma oluşturma işlemi uygulanmış komut dosyalarına veya sunucuda doğru tek seferlik değere sahip komut dosyalarına izin vermesidir. Bu sayede saldırganlar, belirli bir yanıt için doğru tek seferlik değeri bilmeden komut dosyasını çalıştıramaz.

Neden katı bir CSP kullanmalısınız?

Sitenizde script-src www.googleapis.com gibi görünen bir CSP varsa muhtemelen siteler arası saldırılara karşı etkili değildir. Bu tür bir CSP'ye izin verilenler listesi CSP'si denir. Çok fazla özelleştirme gerektirirler ve saldırganlar tarafından atlanabilirler.

Şifreleme tek seferlik anahtarlarına veya karma oluşturma işlemlerine dayalı katı CSP'ler bu tür tuzaklar oluşturmaz.

Katı CSP yapısı

Temel ve katı bir İçerik Güvenliği Politikası, aşağıdaki HTTP yanıt başlıklarından birini kullanır:

Tek seferlik anahtara dayalı katı İGP

Content-Security-Policy:
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';
Tek seferlik anahtara dayalı katı CSP'nin işleyiş şekli.

Karma oluşturmaya dayalı katı İGP

Content-Security-Policy:
  script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

Aşağıdaki özellikler, bu gibi bir CSP'yi "katı" ve dolayısıyla güvenli hale getirir:

  • Site geliştiricisinin kullanıcının tarayıcısında çalıştırılmasına güvendiği <script> etiketlerini belirtmek için tek seferlik 'nonce-{RANDOM}' veya karma oluşturma 'sha256-{HASHED_INLINE_SCRIPT}' değerleri kullanır.
  • Güvenilir bir komut dosyasının oluşturduğu komut dosyalarının otomatik olarak yürütülmesine izin vererek nonce veya karma tabanlı bir CSP dağıtma çabasını azaltmak için 'strict-dynamic' değerini ayarlar. Bu işlem, çoğu üçüncü taraf JavaScript kitaplığının ve widget'ının kullanımını da engeller.
  • URL izin verilenler listelerine dayalı olmadığından yaygın CSP atlamalarından etkilenmez.
  • Satır içi etkinlik işleyiciler veya javascript: URI'leri gibi güvenilmeyen satır içi komut dosyalarını engeller.
  • object-src, Flash gibi tehlikeli eklentileri devre dışı bırakmak için kısıtlanır.
  • base-uri etiketlerinin eklenmesi için <base> etiketlerini engellemek üzere base-uri'ü kısıtlar. Bu, saldırganların göreli URL'lerden yüklenen komut dosyalarının konumlarını değiştirmesini engeller.

Sıkı bir İGP'yi benimseyin

Katı bir İGP uygulamak için:

  1. Uygulamanızın nonce tabanlı mı yoksa karma oluşturma tabanlı mı bir CSP ayarlamasına karar verin.
  2. Katı CSP yapısı bölümündeki CSP'yi kopyalayıp uygulamanızda yanıt üstbilgisi olarak ayarlayın.
  3. CSP ile uyumlu olmayan kalıpları kaldırmak için HTML şablonlarını ve istemci tarafı kodunu yeniden yapılandırın.
  4. İGP'nizi dağıtın.

Sitenizde CSP'nin olup olmadığını ve XSS'ye karşı etkili olacak kadar katı olup olmadığını kontrol etmek için bu süreçte Lighthouse (--preset=experimental işaretiyle 7.3.0 ve sonraki sürümler) En İyi Uygulamalar denetimini kullanabilirsiniz.

Lighthouse raporunda, zorunlu modda hiç CSP bulunmadığına dair uyarı
Sitenizde CSP yoksa Lighthouse bu uyarıyı gösterir.

1. Adım: Nonce veya karma oluşturma tabanlı bir CSP'ye ihtiyacınız olup olmadığına karar verin

İki tür katı CSP'nin işleyiş şekli:

Tek seferlik sayı tabanlı CSP

Tek kullanımlık sayıya dayalı bir CSP'de çalışma zamanında rastgele bir sayı oluşturur, bu sayıyı CSP'nize ekler ve sayfanızdaki her komut dosyası etiketiyle ilişkilendirirsiniz. Saldırgan, sayfanıza kötü amaçlı bir komut dosyası dahil edemez veya çalıştıramaz. Bunun için, komut dosyasının doğru rastgele sayısını tahmin etmesi gerekir. Bu yöntem yalnızca sayı tahmin edilemiyorsa ve her yanıt için çalışma zamanında yeni oluşturuluyorsa işe yarar.

Sunucuda oluşturulan HTML sayfaları için tek seferlik rastgele sayı tabanlı bir CSP kullanın. Bu sayfalarda her yanıt için yeni bir rastgele sayı oluşturabilirsiniz.

Karma oluşturmaya dayalı CSP

Karma oluşturmaya dayalı bir CSP için her satır içi komut dosyası etiketinin karması CSP'ye eklenir. Her komut dosyasının farklı bir karması vardır. Bir saldırgan, sayfanıza kötü amaçlı bir komut dosyası ekleyemez veya çalıştıramaz. Bunun nedeni, komut dosyasının çalışması için karma değerinin CSP'nizde bulunması gerektiğidir.

Statik olarak sunulan veya önbelleğe alınması gereken HTML sayfaları için karma oluşturmaya dayalı bir CSP kullanın. Örneğin, Angular, React gibi çerçevelerle oluşturulan ve sunucu tarafı oluşturma olmadan statik olarak sunulan tek sayfalık web uygulamaları için karma oluşturmaya dayalı bir CSP kullanabilirsiniz.

2. adım: Katı bir CSP ayarlayın ve komut dosyalarınızı hazırlayın

CSP ayarlama konusunda birkaç seçeneğiniz vardır:

  • Yalnızca raporlama modu (Content-Security-Policy-Report-Only) veya yaptırım modu (Content-Security-Policy). Yalnızca raporlama modunda CSP henüz kaynakları engellemez. Bu nedenle, sitenizde hiçbir şey bozulmaz ancak engellenecek öğeler için raporlar alabilir ve hataları görebilirsiniz. Yerel olarak CSP'nizi ayarlarken bu durum pek önemli değildir çünkü her iki mod da hataları tarayıcı konsolunda gösterir. Bir kaynağın engellenmesi sayfanızı bozuk gösterebileceğinden, yaptırım modu, taslak CSP'nizin engellediği kaynakları bulmanıza yardımcı olabilir. Yalnızca rapor modu, sürecin ilerleyen aşamalarında en kullanışlı hale gelir (5. Adım'a bakın).
  • Başlık veya HTML <meta> etiketi. Yerel geliştirme için CSP'nizde ayarlamalar yapmak ve bunun sitenizi nasıl etkilediğini hızlıca görmek üzere <meta> etiketi daha kullanışlı olabilir. Ancak:
    • Daha sonra, CSP'nizi üretime dağıtırken HTTP üst bilgisi olarak ayarlamanızı öneririz.
    • CSP'nizi yalnızca rapor modunda ayarlamak istiyorsanız CSP meta etiketleri yalnızca rapor modunu desteklemediğinden bunu başlık olarak ayarlamanız gerekir.

Uygulamanızda aşağıdaki Content-Security-Policy HTTP yanıt üst bilgisini ayarlayın:

Content-Security-Policy:
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

CSP için tek seferlik sayı oluşturma

Tek seferlik sayı, sayfa yükleme başına yalnızca bir kez kullanılan rastgele bir sayıdır. Tek seferlik değere dayalı CSP, yalnızca saldırganlar tek seferlik değeri tahmin edemiyorsa XSS'yi azaltabilir. CSP tek seferlik rastgele sayı:

  • Kriptografik olarak güçlü bir rastgele değer (ideal olarak 128 bitten uzun)
  • Her yanıt için yeni oluşturulur.
  • Base64 kodlu

Sunucu tarafı çerçevelere CSP tek seferlik rastgele sayı eklemeyle ilgili bazı örnekleri aşağıda bulabilirsiniz:

const app = express();

app.get('/', function(request, response) {
  // Generate a new random nonce value for every response.
  const nonce = crypto.randomBytes(16).toString("base64");

  // Set the strict nonce-based CSP response header
  const csp = `script-src 'nonce-${nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'none';`;
  response.set("Content-Security-Policy", csp);

  // Every <script> tag in your application should set the `nonce` attribute to this value.
  response.render(template, { nonce: nonce });
});

<script> öğelerine nonce özelliği ekleme

Tek seferlik rastgele değere dayalı CSP'de her <script> öğesinin, CSP başlığında belirtilen rastgele tek seferlik değerle eşleşen bir nonce özelliği olmalıdır. Tüm komut dosyalarında aynı tek seferlik İlk adım, CSP'nin izin vermesi için bu özellikleri tüm komut dosyalarına eklemektir.

Uygulamanızda aşağıdaki Content-Security-Policy HTTP yanıt üst bilgisini ayarlayın:

Content-Security-Policy:
  script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

Birden fazla satır içi komut dosyası için söz dizimi aşağıdaki gibidir: 'sha256-{HASHED_INLINE_SCRIPT_1}' 'sha256-{HASHED_INLINE_SCRIPT_2}'.

Kaynaklı komut dosyalarını dinamik olarak yükleme

Satır içi komut dosyası kullanarak üçüncü taraf komut dosyalarını dinamik olarak yükleyebilirsiniz.

Komut dosyalarınızı satır içi olarak ekleme örneği.
CSP tarafından izin verilen
<script>
  var scripts = [ 'https://example.org/foo.js', 'https://example.org/bar.js'];

  scripts.forEach(function(scriptUrl) {
    var s = document.createElement('script');
    s.src = scriptUrl;
    s.async = false; // to preserve execution order
    document.head.appendChild(s);
  });
</script>
Bu komut dosyasının çalışması için satır içi komut dosyasının karma değerini hesaplamanız ve {HASHED_INLINE_SCRIPT} yer tutucusunun yerine CSP yanıt üstbilgisine eklemeniz gerekir. Karma oluşturma işlemlerinin miktarını azaltmak için tüm satır içi komut dosyalarını tek bir komut dosyasında birleştirebilirsiniz. Bunun nasıl çalıştığını görmek için bu örneğe ve koduna bakın.
CSP tarafından engellendi
<script src="https://example.org/foo.js"></script>
<script src="https://example.org/bar.js"></script>
CSP, dinamik olarak eklenmedikleri ve izin verilen bir kaynakla eşleşen integrity özelliğine sahip olmadıkları için bu komut dosyalarını engeller.

Komut dosyası yüklemeyle ilgili dikkat edilmesi gereken noktalar

Satır içi komut dosyası örneğinde, bar önce yüklenirse bile foo'un bar'den önce yürütülmesini sağlamak için s.async = false eklenir. Bu snippet'te, komut dosyaları dinamik olarak eklendiğinden s.async = false, komut dosyaları yüklenirken ayrıştırıcıyı engellemez. Ayrıştırıcı, yalnızca async komut dosyalarında olduğu gibi komut dosyalarının yürütülmesi sırasında durur. Ancak bu snippet'te şunu unutmayın:

  • Komut dosyalarından biri veya ikisi birden, doküman indirilmeden önce yürütülebilir. Belgenin, komut dosyaları çalıştırıldığında hazır olmasını istiyorsanız komut dosyalarını eklemeden önce DOMContentLoaded etkinliğini bekleyin. Bu, komut dosyalarının yeterince erken indirilmemesi nedeniyle performans sorununa yol açıyorsa sayfanın daha başında önyükleme etiketleri kullanın.
  • defer = true hiçbir işlem yapmaz. Bu davranışa ihtiyacınız varsa komut dosyasını gerektiğinde manuel olarak çalıştırın.

3. adım: HTML şablonlarını ve istemci tarafı kodunu yeniden yapılandırın

Komut dosyalarını çalıştırmak için satır içi etkinlik işleyiciler (onclick="…", onerror="…" gibi) ve JavaScript URI'leri (<a href="javascript:…">) kullanılabilir. Bu, XSS hatası bulan bir saldırganın bu tür HTML'leri yerleştirebileceği ve kötü amaçlı JavaScript çalıştırabileceği anlamına gelir. Tek seferlik veya karma oluşturma tabanlı CSP, bu tür işaretlemelerin kullanılmasını yasaklar. Sitenizde bu kalıplardan herhangi biri kullanılıyorsa bunları daha güvenli alternatiflerle yeniden yapılandırmanız gerekir.

CSP'yi önceki adımda etkinleştirdiyseniz CSP uyumlu olmayan bir deseni her engellediğinde konsolda CSP ihlallerini görebilirsiniz.

Chrome geliştirici konsolundaki CSP ihlali raporları.
Engellenen kodla ilgili Console hataları.

Çoğu durumda, çözüm basittir:

Satır içi etkinlik işleyicileri yeniden yapılandırın

CSP tarafından izin verilen
<span id="things">A thing.</span>
<script nonce="${nonce}">
  document.getElementById('things').addEventListener('click', doThings);
</script>
CSP, JavaScript kullanılarak kaydedilen etkinlik işleyicilere izin verir.
CSP tarafından engellendi
<span onclick="doThings();">A thing.</span>
CSP, satır içi etkinlik işleyicileri engeller.

javascript: URI'lerini yeniden düzenleme

CSP tarafından izin verilen
<a id="foo">foo</a>
<script nonce="${nonce}">
  document.getElementById('foo').addEventListener('click', linkClicked);
</script>
CSP, JavaScript kullanılarak kaydedilen etkinlik işleyicilere izin verir.
CSP tarafından engellendi
<a href="javascript:linkClicked()">foo</a>
CSP, javascript: URI'lerini engeller.

JavaScript'inizden eval() öğesini kaldırın

Uygulamanız, JSON dize serileştirmelerini JS nesnelerine dönüştürmek için eval() kullanıyorsa bu örnekleri JSON.parse() olarak yeniden yapılandırmanız gerekir. Bu yöntem aynı zamanda daha hızlıdır.

eval()'nin tüm kullanımlarını kaldıramıyorsanız yine de katı bir tek seferlik değere dayalı CSP ayarlayabilirsiniz ancak 'unsafe-eval' CSP anahtar kelimesini kullanmanız gerekir. Bu da politikanızın güvenliğini biraz azaltır.

Bu ve bu tür yeniden yapılandırmanın daha fazla örneğini bu katı CSP kod laboratuvarında bulabilirsiniz:

4. Adım (İsteğe bağlı): Eski tarayıcı sürümlerini desteklemek için yedek çözümler ekleyin

Tarayıcı desteği

  • Chrome: 52.
  • Edge: 79.
  • Firefox: 52.
  • Safari: 15.4.

Kaynak

Eski tarayıcı sürümlerini desteklemeniz gerekiyorsa:

  • strict-dynamic'ü kullanmak için Safari'nin önceki sürümleri için yedek olarak https: eklemeniz gerekir. Bunu yaptığınızda:
    • strict-dynamic'ü destekleyen tüm tarayıcılar https: yedek seçeneğini yoksayar. Bu nedenle, bu durum politikanın gücünü azaltmaz.
    • Eski tarayıcılarda, harici kaynaklı komut dosyaları yalnızca HTTPS kaynağından geliyorsa yüklenebilir. Bu, katı bir CSP'den daha az güvenlidir ancak yine de javascript: URI'lerinin enjeksiyonu gibi bazı yaygın XSS nedenlerini önler.
  • Çok eski tarayıcı sürümleriyle (4 yıldan eski) uyumluluk sağlamak için yedek olarak unsafe-inline ekleyebilirsiniz. CSP tek seferlik rastgele sayı veya karma oluşturma mevcutsa tüm son tarayıcılar unsafe-inline içeriğini yoksayar.
Content-Security-Policy:
  script-src 'nonce-{random}' 'strict-dynamic' https: 'unsafe-inline';
  object-src 'none';
  base-uri 'none';

5. Adım: CSP'nizi dağıtın

CSP'nizin yerel geliştirme ortamınızdaki meşru komut dosyalarını engellemediğini onayladıktan sonra CSP'nizi aşamalı olarak, ardından üretim ortamınıza dağıtabilirsiniz:

  1. (İsteğe bağlı) Content-Security-Policy-Report-Only üstbilgisini kullanarak CSP'nizi yalnızca raporlama modunda dağıtın. Yalnızca rapor modu, CSP kısıtlamalarını uygulamaya başlamadan önce üretimde yeni bir CSP gibi önemli değişiklikleri test etmek için kullanışlıdır. Yalnızca rapor modunda İGP'niz uygulamanızın davranışını etkilemez ancak tarayıcı, İGP'nizle uyumlu olmayan kalıplarla karşılaştığında yine de konsol hataları ve ihlal raporları oluşturur. Böylece, son kullanıcılarınız için nelerin bozulacağını görebilirsiniz. Daha fazla bilgi için Reporting API başlıklı makaleyi inceleyin.
  2. CSP'nizin son kullanıcılarınız için sitenizi bozmayacağından emin olduğunuzda Content-Security-Policy yanıt üstbilgisini kullanarak CSP'nizi dağıtın. <meta> etiketinden daha güvenli olduğu için CSP'nizi sunucu tarafında bir HTTP üst bilgisi kullanarak ayarlamanızı öneririz. Bu adımı tamamladıktan sonra CSP'niz uygulamanızı XSS'den korumaya başlar.

Sınırlamalar

Katı bir CSP, genellikle XSS'yi azaltmaya yardımcı olan güçlü bir ek güvenlik katmanı sağlar. Çoğu durumda CSP, javascript: URI'leri gibi tehlikeli kalıpları reddederek saldırı yüzeyini önemli ölçüde azaltır. Ancak, kullandığınız CSP türüne (nonce, karma oluşturma, 'strict-dynamic' ile veya olmadan) bağlı olarak CSP'nin uygulamanızı korumadığı durumlar da vardır:

  • Bir komut dosyasını tek seferlik olarak atarsanız ancak doğrudan gövdeye veya söz konusu <script> öğesinin src parametresine bir ekleme varsa.
  • Dinamik olarak oluşturulan komut dosyalarının (document.createElement('script')) konumlarına, bağımsız değişkenlerinin değerlerine göre script DOM düğümü oluşturan tüm kitaplık işlevleri dahil olmak üzere enjeksiyon varsa. Buna jQuery'nin .html() gibi bazı yaygın API'leri ve jQuery 3.0'dan önceki sürümlerdeki .get() ve .post() dahildir.
  • Eski AngularJS uygulamalarında şablon ekleme varsa Bir AngularJS şablonuna kod ekleyebilen bir saldırgan, bu şablonu rastgele JavaScript yürütmek için kullanabilir.
  • Politika 'unsafe-eval', eval(), setTimeout() ve nadiren kullanılan birkaç diğer API'ye yönelik enjeksiyonlar içeriyorsa.

Geliştiriciler ve güvenlik mühendisleri, kod incelemeleri ve güvenlik denetimleri sırasında bu tür kalıplara özellikle dikkat etmelidir. Bu durumlar hakkında daha fazla bilgiyi İçerik Güvenliği Politikası: Güçlendirme ve Azaltma Arasında Başarılı Bir Karmaşa başlıklı makalede bulabilirsiniz.

Daha fazla bilgi