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.
  • Kenar: 79.
  • Firefox: 52.
  • Safari: 15.4.

Kaynak

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

İç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 karma (örneğin, 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 uygulama katı bir CSP kullandığında, HTML ekleme hataları bulan saldırganlar genellikle bunları kullanarak tarayıcıyı, güvenlik açığı olan bir dokümandaki kötü amaçlı komut dosyalarını 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 İGP varsa bu İGP 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 bunun gibi bir CSP'yi "katı" ve dolayısıyla güvenli hale getirir:

  • Sitenin geliştiricisinin, kullanıcının tarayıcısında çalışmak için güvendiği <script> etiketlerini belirtmek için nonce 'nonce-{RANDOM}' veya 'sha256-{HASHED_INLINE_SCRIPT}' karmalarını kullanır.
  • Güvenilir bir komut dosyasının oluşturduğu komut dosyalarının yürütülmesine otomatik olarak izin vererek 'strict-dynamic''i, tek seferlik veya karma tabanlı bir CSP'yi dağıtmaya yönelik çabayı azaltacak şekilde 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.
  • Flash gibi tehlikeli eklentileri devre dışı bırakmak için object-src ürününü kısıtlar.
  • 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 önler.

Katı bir İGP 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

Ortalama olmayan bir CSP ile çalışma zamanında rastgele bir sayı oluşturur, bu sayıyı CSP'nize dahil eder 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 işlem yalnızca sayı tahmin edilemiyorsa çalışır ve her yanıt için çalışma zamanında yeni oluşturulur.

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 tabanlı 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 olması 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 veya diğerleri gibi çerçevelerle oluşturulmuş ve sunucu tarafı oluşturma olmadan statik olarak sunulan tek sayfalık web uygulamaları için karma tabanlı bir CSP kullanabilirsiniz.

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

CSP ayarlarken birkaç seçeneğiniz vardır:

  • Salt rapor modu (Content-Security-Policy-Report-Only) veya yaptırım modu (Content-Security-Policy). Yalnızca rapor modunda İGP henüz kaynakları engellemez. Bu nedenle sitenizdeki hiçbir şey çalışmaz ancak engellenmiş olabilecek tüm hatalarla ilgili raporlar görebilir ve raporlar alabilirsiniz. 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ın bozuk görünmesine neden olabileceğinden, yaptırım modu, taslak CSP'nizdeki 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. <meta> etiketi, yerel gelişim için İGP'nizde ufak değişiklikler yapıp sitenizi nasıl etkilediğini hızlıca görmek açısından daha kullanışlı olabilir. Ancak:
    • Daha sonra, CSP'nizi üretime dağıtırken bunu bir HTTP başlığı olarak ayarlamanızı öneririz.
    • CSP'nizi yalnızca rapor modunda ayarlamak isterseniz CSP meta etiketleri salt rapor modunu desteklemediğinden bunu bir başlık olarak ayarlamanız gerekir.

A seçeneği: Tek seferlik sayı tabanlı CSP

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 rastgele sayı oluşturma

Tek seferlik rastgele 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ısı:

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

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 anahtara dayalı bir CSP'de her <script> öğesinin, CSP başlığında belirtilen rastgele tek seferlik anahtar değeriyle eşleşen bir nonce özelliği olmalıdır. Tüm komut dosyalarında aynı tek seferlik İlk adım, bu özellikleri tüm komut dosyalarına ekleyerek CSP'nin bunlara izin vermesini sağlamaktır.

B seçeneği: Karma oluşturmaya dayalı CSP yanıt başlığı

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 bir şekilde yükleyebilirsiniz.

Komut dosyalarınızı satır içine alma ö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 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ği, bar önce yüklense bile foo öğesinin bar öncesinde yürütülmesini sağlamak için s.async = false öğesini ekler. 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ı, async komut dosyalarında olduğu gibi yalnızca komut dosyaları yürütülürken durur. Ancak bu snippet'te şunu unutmayın:

  • Komut dosyalarından biri veya her ikisi, dokümanın indirilmesi tamamlanmadan ö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. Siteniz bu kalıplardan herhangi birini kullanıyorsa bunları daha güvenli alternatifler olacak şekilde yeniden düzenlemeniz 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'i engeller: URI'lar.

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

Uygulamanız, JSON dizesi serileştirmelerini JS nesnelerine dönüştürmek için eval() kullanıyorsa bu gibi örnekleri JSON.parse() olarak yeniden düzenlemeniz gerekir. Bu işlem de 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.

Aşağıdaki katı CSP codelab'inde bunları ve bu gibi yeniden düzenlemeyle ilgili daha fazla örnek bulabilirsiniz:

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

Tarayıcı desteği

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

Kaynak

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

  • strict-dynamic kullanımı, Safari'nin önceki sürümleri için yedek olarak https: eklenmesini gerektirir. Bunu yaptığınızda:
    • strict-dynamic özelliğini destekleyen tüm tarayıcılar https: yedeğini yok sayar. Bu nedenle bu, 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üvenli olsa da javascript: URI'lerinin yerleştirilmesi gibi bazı yaygın XSS nedenlerini engeller.
  • Ç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: İGP'nizi dağıtın

CSP'nizin yerel geliştirme ortamınızda geçerli komut dosyalarını engellemediğini onayladıktan sonra, CSP'nizi önce hazırlık ve 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. CSP'nizi <meta> etiketinden daha güvenli olduğu için sunucu tarafında HTTP üstbilgisi 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 rastgele kapatırsanız ancak doğrudan ilgili <script> öğesinin gövdesine veya src parametresine bir ekleme yapılırsa.
  • 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' içeriyorsa eval(), setTimeout() ve nadiren kullanılan diğer birkaç API'ye eklemeler.

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ı: Sağlamlaştırma ve Azaltma Arasındaki Başarılı Bir Kargaşa bölümünde bulabilirsiniz.

Daha fazla bilgi