CSP'de Yeni Bir Bypass: "Kevgir Misin Be Kardeşlik?"

Kategori: Web Güvenliği - Güncellenme: 28 Mayıs 2018 - Netsparker Security Team

Content-Security-Policy istemci taraflı güvenliğin önemli köşe taşlarından biri. Üçüncü versiyonu ile yeni özellikler eklenen CSP, XSS başta olmak üzere frame injection, protokol downgrading, Clickjacking, mixed content gibi bir dizi zafiyete karşı koruma mekanizması sunmaktadır. CSP'nin ayrıntıları için Netsparker Türkiye Blogu'nda yayınlanan yazımızı okuyabilirsiniz.

CSP bugün kullanılmakta olan üçüncü versiyonuna erişinceye dek önemli merhalelerden geçti. CSP 2.0 ile birlikte nonce ve hash desteği eklendi. Buna göre bir script blogu hash değerine göre whitelist edilebilecek, yahut bir script bloguna atanan nonce değeri ile, script blogu güvenli addedilecekti. Fakat CSP 3.0'a gelinceye kadar en önemli whitelist tercihi URL idi.

Fakat bunun da bir dizi sıkıntılı tarafı vardı. Örneğin web uygulamanızda güvensiz bir JSONP endpointi varsa, saldırgan bu nokta üzerinden de injection yapıp CSP'yi bypass ediyordu. Yahut sitede bir Open Redirection zafiyeti varsa, saldırgan bu zafiyeti kullanarak kendi kontrolündeki bir siteden script yüklemesi yapılmasını sağlayabiliyordu.

Yukarıdaki noktaları, bu kadar kusur kadı kızında da olur, diye sineye çekeceksek, CSP'nin görevini hakkıyla yaptığını söyleyebiliriz. Üstelik bu kati duruşu kimi zaman web sayfalarının mevcut işleyişlerini dahi olumsuz etkileyebiliyordu.

Şöyle ki Google Map scriptini sayfanıza embed ettiğinizi, bu kod bloğunu nonce ile whitelist ettiğinizi düşünün. Fakat bu script'in DOM'a bir dizi başka eleman eklemek isteyeceğini, inline script çağrıları yapacağını biliyoruz. Bütün bu noktalarda çağrı yapılan tüm kaynakları whitelist ettik, kod bloklarını nonce ya da hash ile whitelist ettik. Fakat bu defa CSP'nin gardını düşürdük, demektir.

Google 2016'da yayınladığı bir araştırmayla tam da bu soruna çözüm bularak, yukarıda zikrettiğimiz risklerinden ötürü URL whiteliste başvurmadan nasıl bir script blogunu güvenilir addedip, bu script blogu ile beraber DOM'a eklenecek diğer kaynakları unsafe-inline, unsafe-eval gibi güvensiz yöntemlere başvurmadan ekleyebileceğimiz sorusuna cevap verdi: strict-dynamic

CSP talimatları arasına eklenen strict-dynamic ile, nonce ile whitelist ettiğimiz kod bloğu tarafından DOM'a eklenecek elemanlara da izin vermiş oluyoruz.

Content-Security-Policy: script-src 'nonce-random-123' 'strict-dynamic';

<script src="http://example.com/map.js" nonce=random-123></script>

Şimdi resme diğer taraftan, saldırgan tarafından bakalım. CSP talimatında herhangi bir URL whitelist yok. CSP talimatı, tabiatı gereği inline script çağrımlarına da izin vermeyecek. Peki bu sayfada bir XSS zafiyeti bulan saldırgan ne yapsın? Üzerine bir bardak soğuk su mu içsin?

Tabii ki hayır! İçimizi bir bardak su değil, Masato Kinugawa ismindeki araştırmacı serinletiyor. Masato'nun raporladığı bulguya göre Firefox tarayıcılarda (<60) strict-dynamic'e rağmen XSS zafiyetini exploit edebilmek mümkün. Hem de tarayıcıyı kötü emellerimize alet ederek.

Burada küçük bir ayrıntıya yer vermek gerekiyor: require.js

Require.js bilindiği üzere Javascript dosyaları ve modül yüklemelerini gerçekleştiren bir kütüphane.

<meta http-equiv="Content-Security-Policy" content="default-src 'none';script-src 'nonce-secret' 'strict-dynamic'">
<!-- XSS START -->
<script data-main="data:,alert(1)"></script>
<!-- XSS END -->
<script nonce="secret" src="require.js"></script>

require.js kütüphanesi yüklenip, data-main attribute'ü olan bir script elemanı bulduğunda, bu attribute'deki Javascript komutunu çalıştırıyor. data-main attribute'ünden hareketle DOM'da aşağıdaki gibi bir değişiklik yapıyor:

var node = document.createElement('script');
node.src = 'data:,alert(1)';
document.head.appendChild(node);

Buraya kadar olan kısmın Firefox ile alakası yok. Sadece saldırgan, hedef sitede require.js scriptinin olduğunu tespit ediyorsa ve script'ini enjekte edebileceği nokta require.js'in yüklenmesinden önce ise bu yöntemi kullanabilir.

Ama Firefox'a gelince bir başka durum göze çarpıyor: Legacy Extension'lar.

Firefox bazı özellikleri browsera Legacy Extension dediği uzantılar üzerinden ekliyor. Mesela XUL (XML User Interface Language) bir extension olarak Firefox 57 ile birlikte kaldırıldı. Çünkü gerçek anlamda bir web extension değildi. Bu sebeple tarayıcının internal mekanizmasına eklendi.

Nasıl ki web extensionlar style dosyaları gibi, script dosyaları gibi kimi kaynakları içeriyorlarsa legacy extensionlar için de aynı şey geçerli. Eklentilerin sahip olduğu bu kaynaklara tüm siteler üzerinden erişilmesini istiyorsak bunu, eklentiye ait Manifest dosyasındaki web_accessible_resources ayarı ile yapabilmek mümkündü:

"web_accessible_resources": [
  "images/my-image.png"
]

Legacy Extensionlar söz konusu olduğunda bunu contentaccessible ayarı ile yapabilmek mümkün. Bu ayarı da chrome.manifest dosyasından yapıyoruz. Manifest dosyasına, örneğin 64 bit bir sistemde, aşağıdaki çağrı ile kaynaklara erişebilmek mümkün:

jar:file:///C:/Program%20Files%20(x86)/Mozilla%20Firefox/browser/omni.ja!/chrome/chrome.manifest

resource devtools devtools/modules/devtools/
resource devtools-client-jsonview resource://devtools/client/jsonview/ contentaccessible=yes

Yukarı aktardığımız dosyadan alıntılanan iki satır. Bu satırlar sayesinde resource:// psuedo protokolü vasıtası ile erişebileceğiniz kaynaklar tanımlanıyor. Örneğin resource://devtools kaynağına erişmek istediğinizde devtool/modules/devtools/ dizinine erişeceksiniz. Bir tür mapping yani. Dizin ise fiziksel olarak şurada: jar:file:///C:/Program%20Files%20(x86)/Mozilla%20Firefox/browser/omni.ja!/chrome/devtools/modules/devtools/

Aynı şekilde contentaccessible=yes olarak işaretlenen son satırda da resource://devtools-client-jsonview/ URL'i bir başka dizine işaret ediyor. İşte bu dizin ihtiyaç duyduğumuz require.js dosyasını içeren dizin.

Firefox tarayıcıda (<60) saldırının son hali aşağıdaki gibi olacak:

<meta http-equiv="Content-Security-Policy" content="default-src 'none';script-src 'nonce-secret' 'strict-dynamic'">
<!-- XSS START -->
<script data-main="data:,alert(1)"></script>
<script  src="resource://devtools-client-jsonview/lib/require.js"></script>
<!-- XSS END →

Unutmadan not edelim, Firefox 60 ile beraber zafiyet fixlenmiş olsa da, dizin yolları aynı dosyaları içermeye devam etmektedir:

CSP'de Yeni Bir Bypass - 01

Muhtemelen şunu merak edecekseniz. strict-dynamic kullanıldı, peki neden CSP talimatı nonce içermeyen bu script bloğuna izin verdi?

CSP'nin tasarımı gereği, ne kadar katı kurallarla set ediliyor olursa olsun, user-agent yani browser tarafından eklenen bu webaccessible kaynaklara karşı daha müsamahalı davranıyor. Zira bu extensionları ekleyen kullanıcının bu işlemlere peşinen izin verdiği varsayılıyor. Firefox'daki resource protokolü de bu kapsamda değerlendiriliyor.

Policy enforced on a resource SHOULD NOT interfere with the operation of user-agent features like addons, extensions, or bookmarklets. These kinds of features generally advance the user’s priority over page authors, as espoused in [HTML-DESIGN]. (https://www.w3.org/TR/CSP3/#extensions)

Araştırmanın ayrıntıları için lütfen tıklayınız.

srcdoc Attribute'ü Üzerine Kısa Bir Not

Srcdoc özelliği HTML5 ile gelen bir özellik. Edge 18 ile beraber tüm majör tarayıcılarda destekleniyor. Ayrıntılar için lütfen tıklayınız.

Frame içerisinde gösterilecek içeriğin HTML kodunu aktarabileceğimiz bir attribute.

Peki güvenlik açısından söz konusu edilmeye değer tarafı neresi?

Birincisi, iframe içerisinde görüntülecek HTML içeriği barındırdığı için srcdoc'da encode edilmiş halde tutulan içerik, frame içerisine aktarılmadan önce decode ediliyor:

<html>
<head>
<body>
<iframe srcdoc="&lt;script&gt;alert(document.domain)&lt;/script&gt;">
</body>
</html>

CSP'de Yeni Bir Bypass - 02

Kritik olan bu iframe'in origin türetiminin (inheritance), iframe'i içeren domain üzerinden yapılıyor olması. Dolayısıyla buradaki bir kod, SOP gereği iframe'i içeren DOM'a da erişebilecek.

İkincisi srcdoc attribute'ü pek çok blacklistin atlatılmasında kullanılabileceği gibi, bu context'de yani attribute context'inde kullanılan encoding'i de boşa çıkaracaktır.

Peki çözüm?

Iframelerde mutlaka sandbox attribute'ü kullanılmalı.

<iframe sandbox src="http://www.externalresource.com"></iframe>

sandbox attribute'ü de tıpkı srcdoc gibi HTML5 ile gelen frame özelliklerinden. Iframe sandbox attribute'ü ile set edildiğinde aşağıdaki kısıtlar frame ile birlikte yüklenen kaynaklar için uygulanır:

Frame içerisinden form gönderimleri, pop-up pencereler, script çalışmaları ve top level navigasyon engellenir. Iframe ile yüklenen kaynak aynı originden bile olsa, browser tarafından unique origin, yani farklı ve tekil bir origin olarak değerlendirilir.

sandbox attribute'ü ile birlikte kullanacağınız parametreler:

allow-pointer-lock
allow-popups
allow-same-origin
allow-scripts
allow-top-navigation

Örneğin aşağıdaki kod ile, iframe içerisinde yüklenen kaynaklardaki form gönderimlerine izin verilmektedir:

<iframe sandbox="allow-forms" src="http://www.externalresource.com"></iframe>

Kodumuza dönecek olursak:

<html>
<head>
<body>
<iframe sandbox srcdoc="&lt;script&gt;alert(document.domain)&lt;/script&gt;">
</body>
</html>

CSP'de Yeni Bir Bypass - 03

Bir şey dememe gerek yok! Görüyorsunuz script çalışması engellendi. :)

Netsparker

Tam isabet, hızlı ve kolay kullanım

DEMO SÜRÜMÜNÜ İNDİR