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

  • 52
  • 79
  • 52
  • 15,4

Kaynak

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

İçerik Güvenliği Politikası (CSP), XSS'yi azaltmaya yardımcı olan ek bir güvenlik katmanıdır. CSP yapılandırmak için bir web sayfasına Content-Security-Policy HTTP üst bilgisini 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 atlanabilirler. Bu nedenle sayfayı genellikle XSS'ye maruz bırakan, yaygın olarak kullanılan ana makine izin verilenler listesi tabanlı CSP'ler yerine, XSS'yi azaltmak için nonce'lara veya karmalara dayalı bir CSP'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.

Anahtar terim: Karma işlevi, bir giriş değerini karma adı verilen sıkıştırılmış sayısal bir 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.

Nonce veya hash değerine dayanan İçerik Güvenliği Politikası genellikle katı CSP olarak adlandırılır. Bir uygulamada katı bir CSP kullanıldığında, HTML yerleştirme hataları bulan saldırganlar genellikle bunları, tarayıcıyı güvenlik açığı içeren bir belgedeki kötü amaçlı komut dosyalarını yürütmeye zorlamak için kullanamazlar. Bunun nedeni, katı CSP'nin yalnızca sunucuda oluşturulan doğru tek seferlik değere sahip karma oluşturma işlemi uygulanmış komut dosyalarına veya komut dosyalarına izin vermesidir. Böylece saldırganlar, belirli bir yanıt için doğru tek seferlik rastgele sayıyı bilmeden komut dosyasını yürütemez.

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

Sitenizde script-src www.googleapis.com gibi bir İGP varsa muhtemelen siteler arası üzerinde etkili olmayacaktır. Bu CSP türüne izin verilenler listesi CSP adı verilir. Çok fazla özelleştirme gerektirirler ve saldırganlar tarafından atlanabilirler.

Kriptografik tek seferlik rastgele sayılara veya karmalara dayanan katı CSP'ler bu tehlikelerden kaçınır.

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:

Temel olmayan katı CSP

Content-Security-Policy:
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';
Nonce tabanlı katı bir CSP'nin işleyiş şekli

Karma tabanlı katı CSP

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:

  • Site geliştiricisinin kullanıcının tarayıcısında yürütmeye güvendiği <script> etiketlerini belirtmek için nonces 'nonce-{RANDOM}' veya karma değerleri 'sha256-{HASHED_INLINE_SCRIPT}' kullanır.
  • 'strict-dynamic''i, güvenilir bir komut dosyasının oluşturduğu komut dosyalarının yürütülmesine otomatik olarak izin vererek "nonce" veya karma tabanlı CSP dağıtma çabasını azaltır. Bu işlem aynı zamanda çoğu üçüncü taraf JavaScript kitaplığı ve widget'ının kullanımını da kaldırır.
  • URL izin verilenler listelerine dayanmadığı için yaygın CSP atlamalarından etkilenmez.
  • Satır içi etkinlik işleyiciler veya javascript: URI'ler gibi güvenilmeyen satır içi komut dosyalarını engeller.
  • Flash gibi tehlikeli eklentileri devre dışı bırakmak için object-src politikasını kısıtlar.
  • <base> etiketlerinin yerleştirilmesini engellemek için base-uri politikasını 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 CSP benimseyin

Katı bir CSP benimsemek için şunları yapmanız gerekir:

  1. Uygulamanızın nonce veya karma tabanlı bir CSP ayarlamasına karar verin.
  2. Katı CSP yapısı bölümünden CSP'yi kopyalayın ve uygulamanız genelinde bir yanıt başlığı olarak ayarlayın.
  3. CSP ile uyumlu olmayan kalıpları kaldırmak için HTML şablonlarını ve istemci tarafı kodu yeniden düzenleyin.
  4. CSP'nizi dağıtın.

Sitenizin bir İGP'ye sahip olup olmadığını ve XSS'e karşı etkili olacak kadar katı olup olmadığını kontrol etmek için bu süreç boyunca Lighthouse (--preset=experimental işaretli sürüm 7.3.0 ve üzeri) En İyi Uygulamalar denetimini kullanabilirsiniz.

Yaptırım modunda hiç CSP bulunamadığına dair Lighthouse raporu uyarısı.
Sitenizde CSP yoksa Lighthouse bu uyarıyı gösterir.

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

İki katı CSP türünün işleyiş şekli şöyledir:

Nonce tabanlı CSP

Nonce tabanlı bir CSP ile çalışma zamanında rastgele bir sayı oluşturur, bu sayıyı İGP'nize dahil eder ve sayfanızdaki her komut dosyası etiketiyle ilişkilendirirsiniz. Bir saldırgan, sayfanıza kötü amaçlı bir komut dosyası ekleyemez veya mevcut bir komut dosyasını çalıştıramaz, çünkü bu komut dosyası için doğru rastgele sayıyı tahmin etmesi gerekir. Bu yalnızca sayı tahmin edilemezse çalışır ve her yanıt için çalışma zamanında yeni oluşturulur.

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

Karma tabanlı CSP

Karma tabanlı CSP'de her satır içi komut dosyası etiketinin karması CSP'ye eklenir. Her komut dosyasının farklı bir karma değeri vardır. Bir saldırgan, sayfanıza kötü amaçlı bir komut dosyası ekleyemez veya mevcut bir komut dosyasını çalıştıramaz. Çünkü bir komut dosyasının çalışması için karmasının CSP'nizde olması gerekir.

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

2. Adım: Katı bir CSP belirleyin ve komut dosyalarınızı hazırlayın

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

  • Yalnızca rapor modu (Content-Security-Policy-Report-Only) veya yaptırım modu (Content-Security-Policy). Salt rapor modunda, CSP henüz kaynakları engellemediğinden sitenizdeki hiçbir öğe bozulmaz ancak hataları görebilir ve engellenmiş olabilecek her şey için rapor alabilirsiniz. Yerel olarak, CSP'nizi ayarlarken bu önemli değildir, çünkü her iki mod da tarayıcı konsolundaki hataları gösterir. Herhangi bir durumda yaptırım modu, taslak CSP bloklarınızın kaynaklarını bulmanıza yardımcı olabilir çünkü bir kaynağı engellemek sayfanızın bozuk görünmesine neden olabilir. Salt rapor modu, işlemin ilerleyen aşamalarında en çok işinize yarar (5. adıma bakın).
  • Başlık veya HTML <meta> etiketi. Yerel geliştirmede <meta> etiketi, İGP'nizde ince ayar yapmak ve sitenizin nasıl etkilendiğini hızlıca görmek için daha kullanışlı olabilir. Ancak:
    • Daha sonra, CSP'nizi üretimde dağıtırken HTTP başlığı olarak ayarlamanızı öneririz.
    • CSP meta etiketleri salt rapor modunu desteklemediğinden CSP'nizi yalnızca rapor modunda ayarlamak isterseniz bunu başlık olarak ayarlamanız gerekir.

A seçeneği: Nonce 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 başına yalnızca bir kez kullanılan rastgele bir sayıdır. Nonce tabanlı bir CSP, yalnızca saldırganların tek seferlik değeri tahmin edememesi durumunda XSS'yi hafifletebilir. CSP tek seferlik rastgele sayısı:

  • Kriptografik olarak güçlü rastgele değer (ideal olarak 128 bit uzunluğunda)
  • Her yanıt için yeni oluşturulan
  • Base64 kodlu

Sunucu tarafı çerçevelerde CSP tek seferlik rastgele sayısının nasıl ekleneceğiyle ilgili bazı örnekleri burada 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 ekleyin

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

Seçenek B: Karma Tabanlı 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 şöyledir: 'sha256-{HASHED_INLINE_SCRIPT_1}' 'sha256-{HASHED_INLINE_SCRIPT_2}'.

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

Tarayıcılarda CSP karmaları yalnızca satır içi komut dosyaları için desteklendiğinden tüm üçüncü taraf komut dosyalarını bir satır içi komut dosyası kullanarak dinamik olarak yüklemeniz gerekir. Kaynaklı komut dosyalarına ilişkin karmalar tarayıcılar arasında iyi desteklenmez.

Komut dosyalarınızı satır içine alma örneği.
CSP tarafından izin veriliyor
<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ını sağlamak için satır içi komut dosyasının karmasını hesaplamanız ve {HASHED_INLINE_SCRIPT} yer tutucusunu değiştirerek bunu CSP yanıt başlığına eklemeniz gerekir. Karma sayısını azaltmak için tüm satır içi komut dosyalarını tek bir komut dosyasında birleştirebilirsiniz. Bunu uygulamalı olarak 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>
Yalnızca satır içi komut dosyalarına karma oluşturma işlemi uygulanabildiğinden CSP bu komut dosyalarını engeller.

Komut dosyası yüklenirken 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 özelliğini 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 şunları unutmayın:

  • Komut dosyalarından biri veya her ikisi, dokümanın indirilmesi tamamlanmadan önce yürütülebilir. Komut dosyaları yürütüldüğünde dokümanın hazır olmasını istiyorsanız komut dosyalarını eklemeden önce DOMContentLoaded etkinliğinin gerçekleşmesini bekleyin. Bu durum, komut dosyalarının indirilmeye yeterince erken başlamaması nedeniyle bir performans sorununa neden oluyorsa, sayfada ön yükleme etiketlerini daha erken kullanın.
  • defer = true, hiçbir şey yapmaz. Böyle bir 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ı kodu yeniden düzenleyin

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. Yani, XSS hatası bulan bir saldırgan, bu tür HTML yerleştirebilir ve kötü amaçlı JavaScript yürütebilir. Nonce veya hash tabanlı CSP'ler bu tür işaretlemelerin kullanımını yasaklar. Siteniz bu kalıplardan herhangi birini kullanıyorsa bunları daha güvenli alternatifler halinde yeniden düzenlemeniz gerekir.

Önceki adımda CSP'yi etkinleştirdiyseniz CSP uyumsuz bir kalıbı her engellediğinde konsolda CSP ihlallerini görebilirsiniz.

Chrome geliştirici konsolundaki CSP ihlal raporları.
Engellenen kod için konsol hataları.

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

Satır içi etkinlik işleyicilerini yeniden düzenleme

CSP tarafından izin veriliyor
<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şleyicilerini engeller.

javascript: URI'lerini yeniden düzenleyin

CSP tarafından izin veriliyor
<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.

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

Uygulamanız, JSON dizesi serileştirmelerini JS nesnelerine dönüştürmek için eval() kullanıyorsa bu tür örnekleri JSON.parse() olarak yeniden düzenlemeniz gerekir. Bu da daha hızlı bir işlemdir.

Tüm eval() kullanımlarını kaldıramıyorsanız yine de katı bir nonce tabanlı CSP ayarlayabilirsiniz ancak 'unsafe-eval' CSP anahtar kelimesini kullanmanız gerekir. Bu da politikanızın biraz daha az güvenli olmasına neden olur.

Bu katı CSP codelab'inde bu tür yeniden düzenlemelerle ilgili bunları ve daha fazla örneği bulabilirsiniz:

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

Tarayıcı Desteği

  • 52
  • 79
  • 52
  • 15,4

Kaynak

Daha eski tarayıcı sürümlerini desteklemeniz gerekiyorsa:

  • strict-dynamic kullanımı, Safari'nin önceki sürümleri için yedek olarak https: öğesinin eklenmesini gerektirir. Bunu yaptığınızda:
    • strict-dynamic özelliğini destekleyen tüm tarayıcılar https: yedeğini yoksayar. Bu nedenle bu, politikanın gücünü azaltmaz.
    • Eski tarayıcılarda, harici kaynaklı komut dosyaları yalnızca HTTPS kaynağından gelirlerse yüklenebilir. Bu, katı bir CSP'den daha az güvenli olsa da javascript: URI'larının yerleştirilmesi gibi bazı yaygın XSS nedenlerini önler.
  • Çok eski tarayıcı sürümleriyle (4 yıl ve üzeri) uyumluluğu sağlamak için unsafe-inline ürününü yedek olarak ekleyebilirsiniz. CSP tek seferlik rastgele sayısı veya karma değeri mevcutsa son tarayıcıların tümü unsafe-inline yönergesini 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 doğruladıktan sonra, CSP'nizi hazırlık ortamına, ardından üretim ortamınıza dağıtabilirsiniz:

  1. (İsteğe bağlı) Content-Security-Policy-Report-Only başlığını kullanarak CSP'nizi yalnızca rapor modunda dağıtın. Yalnızca rapor modu, üretimde yeni bir CSP gibi zarar verebilecek değişiklikleri, İGP kısıtlamalarını uygulamaya başlamadan önce test etmek için kullanışlıdır. Salt rapor modunda, CSP'niz uygulamanızın davranışını etkilemez ancak tarayıcı, CSP'nizle uyumlu olmayan kalıplarla karşılaştığında konsol hataları ve ihlal raporları oluşturmaya devam eder. Böylece son kullanıcılarınız için neyin bozulduğunu görebilirsiniz. Daha fazla bilgi için Reporting API sayfasına bakın.
  2. İGP'nizin son kullanıcılarınız için sitenizi kesintiye uğratmayacağından emin olduğunuzda Content-Security-Policy yanıt başlığını kullanarak İGP'nizi dağıtın. CSP'nizi <meta> etiketinden daha güvenli olduğu için sunucu tarafı 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. CSP çoğu durumda javascript: URI'leri gibi tehlikeli kalıpları reddederek saldırı yüzeyini önemli ölçüde azaltır. Bununla birlikte, kullandığınız CSP türüne ('strict-dynamic' olan veya olmayan tek seferlik rastgele sayılar, karmalar) göre CSP'nin uygulamanızı korumadığı durumlar da vardır:

  • Bir komut dosyasını tek tek girerseniz ancak <script> öğesinin gövdesine veya src parametresine doğrudan bir ekleme yapılır.
  • Bağımsız değişkenlerin değerlerine göre script DOM düğümü oluşturan tüm kitaplık işlevleri dahil olmak üzere, dinamik olarak oluşturulan komut dosyalarının (document.createElement('script')) konumlarına yerleştirme yapılıyorsa. Buna jQuery'nin .html()'sinin yanı sıra jQuery < 3.0'daki .get() ve .post() gibi bazı yaygın API'ler dahildir.
  • Eski AngularJS uygulamalarında şablon yerleştirme olup olmadığı. AngularJS şablonuna veri ekleyebilen saldırganlar, bu şablonu rastgele JavaScript çalıştırmak için kullanabilir.
  • Politika 'unsafe-eval' içeriyorsa eval(), setTimeout() ve nadiren kullanılan başka birkaç API'ye ekleme yapılır.

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 durumlarla ilgili daha fazla bilgiyi İçerik Güvenliği Politikası: Sağlamlaştırma ve Azaltma Arasında Başarılı Bir Sorun bölümünde bulabilirsiniz.

Daha fazla bilgi