Content-Type ve Status Code Leakage

Ziyahan Albeniz - 08 Nisan 2019 -

Dönerse senindir sözünden hareketle, HTTP Status Kodu ve Content-Type Leak araştırmasını inceliyoruz. "terjanq" nicki ile 20 Mart'ta yayınlanan araştırmayı ele aldık.

Content-Type ve Status Code Leakage

Web sayfalarından dönen yanıtlar, o kaynağı talep eden kullanıcının yetkisine, tarayıcı tipine, talep edilen kaynağın sunucuda var olup olmamasına, kaynağın yerinin değişip değişmediğine göre çeşitli farklılıklar arz edebilir.

Örneğin HTTP protokolüne sadık bir tasarımda, kullanıcının istediği kaynak hedef sunucuda yoksa 404 HTTP status kodu yani Not Found ile yanıt verilmektedir. Aynı şekilde talep edilen kaynağı ziyaretçi görmeye yetkili değilse, bu defa 403 numaralı kod ile mukabele edilmektedir. Biraz daha insaflı bir sunucu ile muhatap iseniz 403 numaralı kod ile kapıyı yüzünüze çarpmak yerine, kibar bir dille 401 kodu vererek yetki doğrulaması yapmanız gerektiği size hatırlatılır.

Aynı şekilde sunucular sundukları kaynağın browserda nasıl render edileceğini, yani nasıl gösterileceği konusunda da tarayıcıya yardımcı olmak namına Content-Type header'ı ile dönen içeriğin tipini belirtmektedir.

Peki Content-Type ve HTTP status kodları web güvenliği açısından ne ifade etmektedir?

Bu haftaki yazımızda 20 Mart tarihinde yayınlanan bir write-up'dan hareketle Content-Type ve HTTP status kod sızıntısını ele alacak, birkaç risk senaryosu ile birlikte değerlendireceğiz.

Kendisi için vaka-ı adiyeden sayılan bug bounty serüvenlerinden birinde terjanq namı ile meşhur araştırmacımız kullanıcının sistemde yetkili olup olmadığına bağlı olarak aynı kaynağa istek yapıldığında iki farklı sonuç döndüğünü tespit ediyor. Şayet kullanıcı talep edilen kaynağı görmeye yetkili ise Content-Type: text/html yanıtını alırken; aynı kaynağa yetkisiz bir erişim sağlamak istediğinde, yani tarayıcıda o kaynak için kaydedilmiş cookie vb. çevre değişkenlerini göndermediğinde bu defa Content-Type'ın olmadığı bir yanıt aldığını belirtiyor.

Burada bir parantez açmakta fayda var. Content-Type header'ının HTTP yanıtında set edilmediği durumlarda bu içeriği yorumlamak tarayıcının insafına bırakılmakta ve bu durum XSS gibi zafiyetlere yol açmaktadır. Bu hususta daha önce Netsparker Türkiye Blogu'nda yayınlanan Content-Type Sniffing ve X-Content-Type-Options güvenlik headerı açıklamalarımızı okuyabilirsiniz.

Araştırmacımız bu gözleminden hareketle, web sayfalarının çeşitli durumlara karşılık aynı kaynak için farklılaştırdığı yanıtı algılamanın genel-geçer bir yöntemi olup olmadığı sorusunun peşine düşüyor ve enfes bir blog post ile güvenliğe meraklı zihinlere adeta bir ziyafet çekiyor.

typemustmatch Attribute'ü

Araştırmacımızın önemli duraklarından biri typemustmatch attribute'ünün keşfi oluyor. Mozilla Developer Network'a referans verdiği attribute'ün ayrıntılarını da yazısında paylaşıyor.

typemustmatch attribute'ü bir boolean attribute. HTML elemanlarının aşina olduğumuz anahtar değer çifti ile set edilen diğer attribute'lerinden farklı olarak boolean attributeler HTML elemanında var oldukları andan itibaren, o eleman için vaad ettikleri özelliği aktif hale getirilirler.

typemustmatch boolean attribute'ü object elemanının yüklediği kaynağın tipi konusunda tarayıcının daha katı davranmasını, tip uyuşmazlığında bu kaynağın yüklenmesinin durdurulmasını talep ediyor.

Devam edelim.

Type headerında belirtilen tipe sıkı sıkıya itaat edilmesi isteniyorsa, yani yüklenen kaynakta dönen Content-Type bizim type attribute'ünde belirttmiş olduğumuz type'dan farklı ise bu kaynağın yüklenmesini typemustmatch boolean attribute'ünü set ederek engelleyebiliriz.

Yavaş yavaş bu bilgi ifşasına yol açan tekniğin etli kısmına geliyoruz. Eğer bir site authenticate olan kullanıcılar ile olmayan kullanıcılara farklı Content-Type'ına sahip yanıtlar dönüyorsa bunu typemustmatch attribute'ünü kaynak yüklemesini gerçekleştiren HTML elemanına set edebiliriz.

Fakat bu noktada araştırmalarını derinleştiren terjanq bu attribute'ün sadece Firefox tarayıcılarda çalıştığını farkediyor.

Şimdi araştırmanın bir başka can alıcı noktasına geliyoruz. Evet tarayıcımız talimatımızı anladı ve tip uyuşmasının gerçekleşmediği kaynağın yüklenmesini reddetti. Bu durumu nasıl algılayacağız?

Akıllara HTML elemanlarının onload ve onerror event'ları geliyor olabilir. Bilindiği gibi desteklediği HTML elemanlarında onload ve onerror olayları kaynak yüklenmesinin başarılı ya da başarısız olduğu durumlarda tetiklenen iki olay. Ancak object elemanı maalesef bu olayları desteklemiyor.

Object elemanının text özelliği bu durumda imdadımıza yetişebilir.

object type= data= typemustmatch> not_loaded </object>

not_loaded metninin yer aldığı object elemanının text özelliği, data'da belirtilen kaynak yüklenmediği durumlarda tarayıcının gösterdiği metindir.

Yazının başından itibaren Content-Type'a vurgu yaptığımızı, HTTP status code'un bu işin neresinde olduğunu sorduğunuzu duyar gibiyim.

Object elemanı typemustmatch attribute'ünün set edilmesi ile beraber tip uyuşmazlığında kaynak yüklemesini reddedeceği gibi, dönen HTTP yanıtı 200 durum koduna sahip değilse de kaynak yüklemeyi reddediyor. Bir taşla iki kuş vurmuş oluyoruz.

Ancak object elemanının text özelliğinden hareketle bunu algılamak zor, çünkü object elemanının text değerine erişebilmek için bir attribute referansı yok.

Peki ne yapmalıyız?

Araştırmacımız object elemanının clientHeight ve clientWidth özelliklerinin kaynak yüklemelerinde değiştiğini belirtiyor. Kaynak henüz yüklenmediğinde ise bu attribute'lerin değeri 0 olarak varsayılan bir değere sahip. Dolayısıyla bu attribute'lerdeki değer dönüşümlerini izleyerek kaynağın yüklenip yüklenmediğini kestirebiliriz.

Peki ama object'nin talep ettiği kaynak ne zaman yüklendi, yükleme ne zaman bitti bunu nasıl anlayacağız ve bu clientHeight ve clientWidth özelliklerinin değer ölçümlerini hangi aşamada gerçekleştireceğiz? Biraz yukarıda söz ettiğimiz gibi object header'ı iyi güzel ama onload ve onerror gibi olayları desteklemiyor.

Her ne kadar object elemanı onload ve onerror eventlarına sahip değilse de window nesnesi sahip. Bir window nesnesi sahip olduğu tüm elemanlar yüklendiğinde bu olayı fırlatıyor.

Nitekim aynı şekilde davranarak clientHeight ve clientWidth attribute'lerindeki değer değişimini bu olay üzerinden yakalayarak Content-Type ve HTTP Status Code durumlarından bilgi elde edebiliyor.

Araştırmacının https://jsfiddle.net/terjanq/vy9Lwj8h adresi üzerinden yayınladığı proof-of-concept'e göz atabilirsiniz.

Web Sitelerinize Karşı Yönelen Böyle Bir Tehditin Önüne Nasıl Geçebilirsiniz?

Peşinen söylemekte yarar var, böyle bir tehditin önüne geçebilmek kolay değil.

Bu tarz bilgi ifşaatları zaman tabanlı olabileceği gibi, yazı boyunca açıklamaya çalıştığımız üzere Content-type ve HTTP status kod bazlı da olabilir.

Content-type konusunda yukarıda da sözünü ettiğimiz content sniffing sorunlarından ötürü zaten titiz davranmak zorundasınız.

HTTP status kod meselesinde de HTTP spesifikasyonuna birebir uymak yerine yani olmayan kaynaklarda 404, yetkisiz erişimlerde 403 dönmek gibi, custom error ve bilgi sayfaları hazırlayıp bunları 200 kodu ile paylaşabilirsiniz.

Bir diğer önemli çözüm önerisi ise gelen istekteki Referer headerı kontrol etmek. Tahmin edeceğiniz gibi böylesi zafiyetleri sömüren istekler saldırgan kontrolündeki bir site üzerinden yapılacağı için Referer istek header'ını kontrol etmeniz bir miktar çözüm sağlayabilir.

Saldırgan Referer header'ını arbitrary bir değer ile değiştiremez mi?

Evet ancak limitleri var. Bir defa saldırgan böylesi bir saldırı için istekleri sizin tarayıcınızın vekilliğinde yapmak zorunda. Same Origin Policy gereği bu istekleri farklı bir origin'e yaparken Referer header'ını arbitrary yani keyfi bir değer ile değiştiremeyecek.

Fakat Referer check'inde aşağıdaki gibi hatalı bir kod kullandı iseniz o takdirde Referer header'ının tümünü değiştiremese bile, URL'in bir kısmına beklediğiniz değeri ekleyerek, sizin kodunuzu bypass edip bu "güvenlik" tedbirini de aşabilir.

Örneğin StackOverFlow'da üstelik onaylı bir cevap olarak paylaşılan aşağıdaki gibi bir kod sizin Referer check gardınızı da düşürecektir:

$ref = $_SERVER['HTTP_REFERER'];
if (strpos($ref, 'example.com') !== FALSE) {
   redirect to wherever example.com people should go
}

Saldırgan kendi kontrolündeki şöyle bir siteyi ziyaret etmenizi sağlayarak bu engeli aşacaktır:

http://www.attacker.com/hello_world?example.com

Kaynak: https://stackoverflow.com/questions/5032889/checking-php-referrer

Content-type ve HTTP status kod leakage konusundaki araştırmanın ayrıntıları için lütfen tıklayınız.