Latte Güvenlik ile Eş Anlamlıdır
Latte, kritik Siteler Arası Komut Dosyası (XSS) güvenlik açığına karşı etkili koruma sağlayan tek PHP şablonlama sistemidir. Bu, bağlama duyarlı kaçış olarak adlandırılan özellik sayesinde gerçekleşmektedir. Hadi konuşalım,
- XSS güvenlik açığının prensibi nedir ve neden bu kadar tehlikelidir?
- Latte'yi XSS'e karşı savunmada bu kadar etkili kılan şey
- Twig, Blade ve diğer şablonların neden kolayca tehlikeye atılabileceği
Siteler Arası Komut Dosyası Oluşturma (XSS)
Siteler arası komut dosyası oluşturma (kısaca XSS) web sitelerindeki en yaygın güvenlik açıklarından biridir ve çok tehlikelidir. Bir saldırganın yabancı bir siteye kötü amaçlı bir komut dosyası (kötü amaçlı yazılım olarak adlandırılır) eklemesine ve bu komut dosyasının şüphelenmeyen bir kullanıcının tarayıcısında çalışmasına olanak tanır.
Böyle bir komut dosyası ne yapabilir? Örneğin, oturum açtıktan sonra görüntülenen hassas veriler de dahil olmak üzere, ele geçirilen siteden saldırgana rastgele içerik gönderebilir. Sayfayı değiştirebilir veya kullanıcı adına başka isteklerde bulunabilir. Örneğin, web postası olsaydı, hassas mesajları okuyabilir, görüntülenen içeriği değiştirebilir veya ayarları değiştirebilir, örneğin, gelecekteki e-postalara erişim elde etmek için tüm mesajların kopyalarını saldırganın adresine yönlendirmeyi açabilir.
XSS'nin en tehlikeli güvenlik açıkları listesinin başında yer almasının nedeni de budur. Bir web sitesinde bir güvenlik açığı keşfedilirse, istismar edilmesini önlemek için mümkün olan en kısa sürede kaldırılmalıdır.
Güvenlik Açığı Nasıl Ortaya Çıkar?
Hata, web sayfasının oluşturulduğu ve değişkenlerin yazdırıldığı yerde meydana gelir. Bir arama sayfası oluşturduğunuzu ve başlangıçta formda arama teriminin bulunduğu bir paragraf olacağını düşünün:
echo '<p>Search results for <em>' . $search . '</em></p>';
Bir saldırgan, aşağıdaki gibi HTML kodu da dahil olmak üzere herhangi bir dize yazabilir
<script>alert("Hacked!")</script>
arama alanına ve dolayısıyla $search
değişkenine
aktarılır. Çıktı herhangi bir şekilde sterilize edilmediğinden, görüntülenen sayfanın bir parçası haline gelir:
<p>Search results for <em><script>alert("Hacked!")</script></em></p>
Tarayıcı, arama dizesinin çıktısını vermek yerine JavaScript'i çalıştırır. Ve böylece saldırgan sayfayı ele geçirir.
Bir değişkene kod yerleştirmenin gerçekten de JavaScript'i çalıştıracağını, ancak bunun yalnızca saldırganın tarayıcısında gerçekleşeceğini iddia edebilirsiniz. Kurbana nasıl ulaşır? Bu açıdan bakıldığında, çeşitli XSS türlerini ayırt edebiliriz. Arama sayfası örneğimizde, yansıtılmış XSS'den bahsediyoruz. Bu durumda, kurbanın parametresinde kötü amaçlı kod içeren bir bağlantıya tıklaması için kandırılması gerekir:
https://example.com/?search=<script>alert("Hacked!")</script>
Kullanıcının bağlantıya erişmesini sağlamak biraz sosyal mühendislik gerektirse de, bu zor değildir. Kullanıcılar,
ister e-postalarda ister sosyal medyada olsun, bağlantılara fazla düşünmeden tıklar. Ve adreste şüpheli bir şey olduğu
gerçeği URL kısaltıcı tarafından maskelenebilir, böylece kullanıcı yalnızca bit.ly/xxx
adresini görür.
Bununla birlikte, depolanmış XSS veya kalıcı XSS olarak bilinen ikinci ve çok daha tehlikeli bir saldırı şekli vardır; bu durumda saldırgan, kötü amaçlı kodu sunucuda depolamayı başararak belirli sayfalara otomatik olarak eklenmesini sağlar.
Bunun bir örneği, kullanıcıların yorum gönderdiği web siteleridir. Bir saldırgan kod içeren bir gönderi gönderir ve bu kod sunucuya kaydedilir. Eğer site yeterince güvenli değilse, bu kod her ziyaretçinin tarayıcısında çalışacaktır.
Görünüşe göre saldırının amacı <script>
dizesini sayfaya yerleştirebilirsiniz. Aslında, JavaScript'i gömmenin birçok yolu
vardır. Bir HTML niteliği kullanarak gömme örneğini ele alalım. Resimlere alt
özniteliğinde yazdırılan
bir başlık ekleyebileceğiniz bir fotoğraf galerimiz olsun:
echo '<img src="' . $imageFile . '" alt="' . $imageAlt . '">';
Bir saldırganın etiket olarak akıllıca oluşturulmuş bir " onload="alert('Hacked!')
dizesi eklemesi
yeterlidir ve çıktı sterilize edilmezse, ortaya çıkan kod aşağıdaki gibi görünecektir:
<img src="photo0145.webp" alt="" onload="alert('Hacked!')">
Sahte onload
niteliği artık sayfanın bir parçası haline gelir. Tarayıcı, resim indirilir indirilmez
içerdiği kodu çalıştıracaktır. Hacklendi!
XSS'e Karşı Nasıl Savunma Yapılır?
Kara liste kullanarak bir saldırıyı tespit etmeye yönelik herhangi bir girişim, örneğin <script>
dize
vb. yetersizdir. Uygulanabilir bir savunmanın temeli, sayfa içinde yazdırılan tüm verilerin tutarlı bir şekilde
sterilize edilmesidir.
Öncelikle bu, özel anlamı olan tüm karakterlerin diğer eşleşen dizilerle değiştirilmesini içerir, buna argoda
kaçış denir (dizinin ilk karakteri kaçış karakteri olarak adlandırılır, dolayısıyla adı da buradan gelir).
Örneğin, HTML metninde, <
has a special meaning, which, if it is not to be interpreted as the beginning of a
tag, must be replaced by a visually corresponding sequence, the so-called HTML entity <
karakteri . Ve
tarayıcı bir karakter yazdırır.
Verilerin hangi bağlamda çıktılandığını ayırt etmek çok önemlidir. Çünkü farklı bağlamlar dizeleri farklı şekilde sterilize eder. Farklı karakterlerin farklı bağlamlarda özel anlamları vardır. Örneğin, HTML metninde, HTML niteliklerinde, bazı özel öğelerin içinde vb. kaçış farklıdır. Bunu birazdan ayrıntılı olarak tartışacağız.
En iyisi, kaçış işlemini doğrudan dize sayfaya yazıldığında gerçekleştirmek ve bunun gerçekten yapılmasını ve yalnızca bir kez yapılmasını sağlamaktır. İşlemin doğrudan şablonlama sistemi tarafından otomatik olarak gerçekleştirilmesi en iyisidir. Çünkü işlem otomatik olarak yapılmazsa, programcı bunu unutabilir. Ve bir ihmal, sitenin savunmasız olduğu anlamına gelir.
Ancak, XSS yalnızca şablonlardaki verilerin çıktısını etkilemez, aynı zamanda güvenilmeyen verileri düzgün bir
şekilde işlemesi gereken uygulamanın diğer bölümlerini de etkiler. Örneğin, uygulamanızdaki JavaScript'ler
innerHTML
ile birlikte kullanılmamalı, yalnızca innerText
veya textContent
kullanılmalıdır. JavaScript gibi dizeleri değerlendiren işlevlere özel dikkat gösterilmelidir. eval()
, aynı
zamanda setTimeout()
veya setAttribute()
ile onload
gibi olay özniteliklerini kullanma vb.
Ancak bu, şablonlar tarafından kapsanan kapsamın ötesine geçer.
İdeal 3 sayı savunması:**
- Veri çıktısının alındığı bağlamı tanıyın
- verileri bu bağlamın kurallarına göre sterilize eder (yani “bağlama duyarlı”)
- bunu otomatik olarak yapar
Bağlam Farkında Kaçış
Bağlam kelimesi ile tam olarak ne kastedilmektedir? Çıktısı alınacak verilerin işlenmesi için belgede kendi kuralları olan bir yerdir. Belgenin türüne (HTML, XML, CSS, JavaScript, düz metin, …) bağlıdır ve belgenin belirli bölümlerinde değişiklik gösterebilir. Örneğin, bir HTML belgesinde, çok farklı kuralların geçerli olduğu bu tür birçok yer (bağlam) vardır. Ne kadar çok olduğuna şaşırabilirsiniz. İşte ilk dördü:
<p>#text</p>
<img src="#attribute">
<textarea>#rawtext</textarea>
<!-- #comment -->
Bir HTML sayfasının ilk ve temel içeriği HTML metnidir. Buradaki kurallar nelerdir? Özel anlamlı karakterler
<
and &
bir etiketin veya varlığın başlangıcını temsil eder, bu nedenle bunları HTML
varlığıyla (<
with <
, &
with &
) değiştirerek
kaçmamız gerekir.
İkinci en yaygın bağlam, bir HTML niteliğinin değeridir. Metinden farklı olarak buradaki özel anlam, özniteliği
sınırlandıran tırnak işaretine "
or '
gider. Bunun, niteliğin sonu olarak görülmemesi için bir
varlık olarak yazılması gerekir. Öte yandan, <
karakteri bir öznitelikte güvenle kullanılabilir
çünkü burada özel bir anlamı yoktur; bir etiketin veya yorumun başlangıcı olarak anlaşılamaz. Ancak dikkat edin, HTML'de
öznitelik değerlerini tırnak işaretleri olmadan yazabilirsiniz, bu durumda bir dizi karakterin özel anlamı vardır, bu
nedenle bu başka bir ayrı bağlamdır.
Bu sizi şaşırtabilir, ancak özel kurallar <textarea>
ve <title>
<
character need not (but can) be escaped unless followed by /
. Ama bu daha çok bir merak konusu.
HTML yorumlarının içinde ilginçtir. Burada, HTML varlıkları kaçış için kullanılmaz. Yorumlarda nasıl kaçış yapılacağını belirten bir spesifikasyon bile yoktur. Sadece biraz tuhaf kurallara uymanız ve içlerindeki belirli karakter kombinasyonlarından kaçınmanız gerekir.
JavaScript veya CSS'yi HTML içine gömdüğümüzde olduğu gibi, bağlamlar da katmanlı olabilir. Bu işlem iki farklı şekilde yapılabilir: öğe veya öznitelik:
<script>#js-element</script>
<img onclick="#js-attribute">
<style>#css-element</style>
<p style="#css-attribute"></p>
İki yol ve iki farklı türde veri kaçışı. İçinde <script>
ve <style>
öğelerinde, HTML yorumlarında olduğu gibi, HTML varlıkları kullanılarak kaçış gerçekleştirilmez. Bu öğelerin
içindeki verileri kaçarken, tek bir kural vardır: metin sırasıyla </script
ve </style
dizisini içermemelidir.
Öte yandan, style
ve on***
öznitelikleri HTML varlıkları kullanılarak öncelenir.
Ve elbette, gömülü JavaScript veya CSS içinde, bu dillerin kaçış kuralları geçerlidir. Dolayısıyla,
onload
gibi bir öznitelikteki bir dize önce JS kurallarına göre, sonra da HTML öznitelik kurallarına göre
öncelenir.
Ugh… Gördüğünüz gibi, HTML bağlam katmanları olan çok karmaşık bir belgedir ve veriyi tam olarak nerede (yani hangi bağlamda) çıktıladığımı bilmeden, bunu nasıl doğru yapacağımı bilemem.
Bir Örnek İster misiniz?
Bir dize alalım Rock'n'Roll
.
HTML metninde çıktısını alırsanız, bu durumda herhangi bir ikame yapmanıza gerek yoktur, çünkü dize özel anlamı olan herhangi bir karakter içermez. Tek tırnak içine alınmış bir HTML niteliğinin içine yazarsanız durum farklıdır. Bu durumda, tırnak işaretlerini HTML varlıklarına kaçmanız gerekir:
<div title='Rock'n'Roll'></div>
Bu kolaydı. Bağlam katmanlı olduğunda, örneğin dize JavaScript'in bir parçası olduğunda çok daha ilginç bir durum ortaya çıkar.
Bu yüzden önce bunu JavaScript'in içine yazıyoruz. Yani, onu tırnak içine alıyoruz ve aynı zamanda \
karakterini kullanarak içerdiği tırnaklardan kaçıyoruz:
'Rock\'n\'Roll'
Kodun bir şey yapmasını sağlamak için bir fonksiyon çağrısı ekleyebiliriz:
alert('Rock\'n\'Roll');
Bu kodu bir HTML belgesine eklersek <script>
başka hiçbir şeyi değiştirmemize gerek yoktur, çünkü
yasak </script
dizisi mevcut değildir:
<script> alert('Rock\'n\'Roll'); </script>
Ancak, bunu bir HTML niteliğine eklemek istiyorsak, yine de tırnak işaretlerini HTML varlıklarına kaçmamız gerekir:
<div onclick='alert('Rock\'n\'Roll')'></div>
Ancak, iç içe geçmiş bağlam sadece JS veya CSS olmak zorunda değildir. Genellikle bir URL de olabilir. URL'lerdeki
parametreler, özel karakterler %
ile başlayan dizilere dönüştürülerek kaçılır. Örnek:
https://example.org/?a=Jazz&b=Rock%27n%27Roll
Ve bu dizeyi bir öznitelikte çıktıladığımızda, yine bu bağlama göre kaçış uygularız ve &
with
&
yerine koyarız:
<a href="https://example.org/?a=Jazz&b=Rock%27n%27Roll">
Buraya kadar okuduysanız, tebrikler, yorucu oldu. Artık bağlamların ve kaçışın ne olduğu hakkında iyi bir fikriniz var. Ve karmaşık olması konusunda endişelenmenize gerek yok. Latte bunu sizin için otomatik olarak yapıyor.
Latte vs Naif Sistemler
Bir HTML belgesinde kaçış işleminin nasıl düzgün bir şekilde yapılacağını ve bağlamı, yani verinin çıktısını nerede aldığınızı bilmenin ne kadar önemli olduğunu gösterdik. Başka bir deyişle, bağlama duyarlı kaçış nasıl çalışır. Bu, işlevsel XSS savunması için bir ön koşul olsa da, Latte, PHP için bunu yapan tek şablonlama sistemidir.
Günümüzde tüm sistemler otomatik kaçış özelliğine sahip olduğunu iddia ederken bu nasıl mümkün olabilir? Bağlamı bilmeden otomatik kaçış, yanlış bir güvenlik duygusu yaratan bir saçmalıktır.
Twig, Laravel Blade ve diğerleri gibi şablonlama sistemleri şablonda herhangi bir HTML yapısı görmez. Bu nedenle, bağlamları da görmezler. Latte ile karşılaştırıldığında, kör ve naiftirler. Sadece kendi işaretlemelerini ele alırlar, diğer her şey onlar için alakasız bir karakter akışıdır:
░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░
░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░░░
░░░░░░░░░░░░░░░░░░░{{ text }}░░░░
- in text: <span>{{ text }}</span>
- in tag: <span {{ text }} ></span>
- in attribute: <span title='{{ text }}'></span>
- in unquoted attribute: <span title={{ text }}></span>
- in attribute containing URL: <a href="{{ text }}"></a>
- in attribute containing JavaScript: <img onload="{{ text }}">
- in attribute containing CSS: <span style="{{ text }}"></span>
- in JavaScriptu: <script>var = {{ text }}</script>
- in CSS: <style>body { content: {{ text }}; }</style>
- in comment: <!-- {{ text }} -->
Naif sistemler sadece mekanik olarak < > & ' "
karakterlerini HTML varlıklarına dönüştürür, bu da
çoğu kullanımda geçerli bir kaçış yoludur, ancak her zaman değil. Bu nedenle, aşağıda göstereceğimiz gibi çeşitli
güvenlik açıklarını tespit edemez veya önleyemezler.
Latte şablonu sizinle aynı şekilde görür. HTML ve XML'i anlar, etiketleri, nitelikleri vb. tanır. Ve bu nedenle, bağlamlar arasında ayrım yapar ve verileri buna göre ele alır. Böylece kritik Siteler Arası Komut Dosyası Açığına karşı gerçekten etkili bir koruma sunar.
Canlı Gösteri
Solda Latte'deki şablonu, sağda ise oluşturulan HTML kodunu görebilirsiniz. $text
değişkeni, her seferinde
biraz farklı bir bağlamda olmak üzere birkaç kez çıktılanır. Ve bu nedenle biraz farklı şekilde öncelenmiştir. Şablon
kodunu kendiniz düzenleyebilirsiniz, örneğin değişkenin içeriğini vb. değiştirebilirsiniz. Deneyin:
Bu harika değil mi! Latte bağlama duyarlı kaçışı otomatik olarak yapar, böylece programcı:
- düşünmek ya da veriden nasıl kaçacağını bilmek zorunda değildir
- Yanlış olamaz.
- bunu unutamam
Bunlar Latte'nin çıktı alırken ayırt ettiği ve veri işlemeyi özelleştirdiği tüm bağlamlar bile değildir. Şimdi daha ilginç durumların üzerinden geçeceğiz.
Naif Sistemler Nasıl Hacklenir
Bağlam farklılaştırmasının ne kadar önemli olduğunu ve Latte'nin aksine naif şablonlama sistemlerinin neden XSS'ye karşı yeterli koruma sağlamadığını göstermek için birkaç pratik örnek kullanacağız. Örneklerde Twig'i naif bir sistemin temsilcisi olarak kullanacağız, ancak aynı şey diğer sistemler için de geçerlidir.
Öznitelik Güvenlik Açığı
Yukarıda gösterdiğimiz gibi HTML özniteliğini kullanarak sayfaya kötü amaçlı kod enjekte etmeye çalışalım. Twig'de bir resim görüntüleyen bir şablonumuz olsun:
<img src={{ imageFile }} alt={{ imageAlt }}>
Öznitelik değerlerinin etrafında tırnak işareti olmadığına dikkat edin. Kodlayıcı bunları unutmuş olabilir, ki bu sadece olur. Örneğin, React'te kod tırnak işaretleri olmadan bu şekilde yazılır ve dil değiştiren bir kodlayıcı tırnak işaretlerini kolayca unutabilir.
Saldırgan, akıllıca oluşturulmuş bir dizeyi foo onload=alert('Hacked!')
resim başlığı olarak ekler.
Twig'in bir değişkenin HTML metin akışı içinde mi, bir niteliğin içinde mi, bir HTML yorumunun içinde mi, vb. mi
yazdırıldığını söyleyemediğini zaten biliyoruz; kısacası, bağlamlar arasında ayrım yapmaz. Ve sadece mekanik olarak
< > & ' "
karakterlerini HTML varlıklarına dönüştürür. Böylece ortaya çıkan kod şöyle
görünecektir:
<img src=photo0145.webp alt=foo onload=alert('Hacked!')>
Bir güvenlik açığı oluşturuldu!
Sahte bir onload
özelliği sayfanın bir parçası haline gelmiştir ve tarayıcı resmi indirdikten hemen sonra
bu özelliği çalıştırır.
Şimdi Latte'nin aynı şablonu nasıl ele aldığını görelim:
<img src={$imageFile} alt={$imageAlt}>
Latte şablonu sizinle aynı şekilde görür. Twig'in aksine, HTML'yi anlar ve bir değişkenin tırnak içinde olmayan bir öznitelik değeri olarak yazdırıldığını bilir. Bu yüzden onları ekler. Bir saldırgan aynı başlığı eklediğinde, ortaya çıkan kod aşağıdaki gibi görünecektir:
<img src="photo0145.webp" alt="foo onload=alert('Hacked!')">
Latte XSS'yi başarıyla engelledi.
JavaScript'te Değişken Yazdırma
Bağlama duyarlı kaçış sayesinde PHP değişkenlerini JavaScript içinde yerel olarak kullanmak mümkündür.
<p onclick="alert({$movie})">{$movie}</p>
<script>var movie = {$movie};</script>
$movie
değişkeni 'Amarcord & 8 1/2'
dizesini depolarsa aşağıdaki çıktıyı üretir. HTML
ve JavaScript'te ve ayrıca onclick
niteliğinde kullanılan farklı kaçışlara dikkat edin:
<p onclick="alert("Amarcord & 8 1\/2")">Amarcord & 8 1/2</p>
<script>var movie = "Amarcord & 8 1\/2";</script>
Bağlantı Kontrolü
Latte, src
veya href
niteliklerinde kullanılan değişkenin bir web URL'si (yani HTTP protokolü)
içerip içermediğini otomatik olarak kontrol eder ve güvenlik riski oluşturabilecek bağlantıların yazılmasını
engeller.
{var $link = 'javascript:attack()'}
<a href={$link}>click here</a>
Yazıyor:
<a href="">click here</a>
Kontrol, nocheck filtresi kullanılarak kapatılabilir.
Latte'nin Sınırları
Latte, tüm uygulama için eksiksiz bir XSS koruması değildir. Latte'yi kullanırken güvenlik hakkında düşünmeyi bırakırsanız mutsuz oluruz. Latte'nin amacı, bir saldırganın bir sayfanın yapısını değiştirememesini, HTML öğelerini veya niteliklerini kurcalayamamasını sağlamaktır. Ancak çıktısı alınan verilerin içerik doğruluğunu kontrol etmez. Ya da JavaScript davranışının doğruluğunu. Bu, şablonlama sisteminin kapsamı dışındadır. Özellikle kullanıcı tarafından girilen ve dolayısıyla güvenilmeyen verilerin doğruluğunu kontrol etmek programcı için önemli bir görevdir.