Test: XSS Güvenlik Açığından Kendinizi Koruyabilir misiniz?
Güvenlik testinde bilginizi sınayın! Bir saldırganın HTML sayfasının kontrolünü ele geçirmesini engelleyebilir misiniz?

Tüm görevlerde aynı soruyu çözeceksiniz: XSS güvenlik açığı
oluşmaması için $str
değişkenini bir HTML sayfasında nasıl
doğru bir şekilde yazdırırsınız? Savunmanın temeli kaçış
(escaping) işlemidir, bu da özel anlamı olan karakterleri karşılık
gelen dizilerle değiştirmek anlamına gelir. Örneğin, <
karakterinin özel bir anlamı olduğu (bir etiketin başlangıcını işaret
eder) HTML metnine bir dize yazdırırken, onu HTML varlığı
<
ile değiştiririz ve tarayıcı <
sembolünü doğru bir şekilde görüntüler.
Dikkatli olun, çünkü XSS güvenlik açığı çok ciddidir. Bir saldırganın sayfanın veya hatta kullanıcı hesabının kontrolünü ele geçirmesine neden olabilir. Bol şans ve HTML sayfasını güvende tutmayı başarmanız dileğiyle!
İlk Üç Soru
Birinci, ikinci ve üçüncü örnekte hangi karakterlerin ve nasıl işlenmesi gerektiğini belirtin:
1) <p><?= $str ?></p>
2) <input value="<?= $str ?>">
3) <input value='<?= $str ?>'>
Eğer çıktı hiçbir şekilde işlenmezse, görüntülenen sayfanın bir
parçası haline gelir. Bir saldırgan değişkene
'foo" onclick="evilCode()'
dizesini sokarsa ve çıktı
işlenmezse, öğeye tıklandığında kodunun çalışmasına neden olur:
$str = 'foo" onclick="evilCode()'
❌ işlenmemiş: <input value="foo" onclick="evilCode()">
✅ işlenmiş: <input value="foo" onclick="evilCode()">
Bireysel örneklerin çözümü:
<
ve&
karakterleri HTML etiketi ve varlığının başlangıcını temsil eder, bunları<
ve&
ile değiştiririz"
ve&
karakterleri nitelik değerinin sonunu ve HTML varlığının başlangıcını temsil eder, bunları"
ve&
ile değiştiririz'
ve&
karakterleri nitelik değerinin sonunu ve HTML varlığının başlangıcını temsil eder, bunları'
ve&
ile değiştiririz
Her doğru cevap için bir puan alırsınız. Elbette, her üç durumda da diğer karakterleri varlıklarla değiştirmek mümkündür, bu bir sorun yaratmaz, ancak gerekli değildir.
Soru No. 4
Devam ediyoruz. Bu bağlamda değişkeni yazdırırken hangi karakterlerin değiştirilmesi gerekiyor?
<input value=<?= $str ?>>
Çözüm: Gördüğünüz gibi, burada tırnak işaretleri eksik. En kolayı
sadece tırnak işaretlerini eklemek ve ardından önceki sorudaki gibi kaçış
işlemi yapmaktır. İkinci bir çözüm de vardır, o da dizedeki boşluğu ve
etiket içinde özel anlamı olan tüm karakterleri, yani >
,
/
, =
ve bazı
diğerleri HTML varlıklarıyla değiştirmektir.
Soru No. 5
Şimdi işler ilginçleşmeye başlıyor. Bu bağlamda hangi karakterlerin işlenmesi gerekiyor:
<script>
let foo = '<?= $str ?>';
</script>
Çözüm: <script>
etiketi içinde, kaçış kurallarını
JavaScript belirler. HTML varlıkları burada kullanılmaz, ancak özel
bir kural geçerlidir. Peki hangi karakterleri kaçırıyoruz? JavaScript dizesi
içinde, onu sınırlayan '
karakterini elbette ters eğik
çizgiyle kaçırırız, yani onu \'
ile değiştiririz. JavaScript
çok satırlı dizeleri desteklemediğinden (yalnızca template
literal olarak), satır sonu karakterlerini de kaçırmamız gerekir. Ancak
dikkatli olun, normal \n
ve \r
karakterlerine ek
olarak, JavaScript unicode karakterleri \u2028
ve
\u2029
'u da satır sonu olarak kabul eder, bunları da
kaçırmamız gerekir. Ve son olarak belirtilen özel kural: dizede
</script
bulunmamalıdır. Bu, örneğin
<\/script
ile değiştirilerek önlenebilir.
Eğer bunu biliyorsanız, tebrikler.
Soru No. 6
Aşağıdaki bağlam sadece önceki bir varyasyon gibi görünüyor. Sizce işleme farklı olacak mı?
<p onclick="foo('<?= $str ?>')"></p>
Çözüm: Burada yine JavaScript dizelerindeki kaçış kuralları
geçerlidir, ancak HTML varlıklarının kullanılmadığı önceki bağlamın
aksine, burada tam tersi kaçış işlemi yapılır. Yani, önce JavaScript
dizesini ters eğik çizgilerle kaçırırız ve ardından özel anlamı olan
karakterleri ("
ve &
) HTML varlıklarıyla
değiştiririz. Dikkat, doğru sıra önemlidir.
Gördüğünüz gibi, aynı JavaScript literali <script>
öğesinde farklı, bir nitelikte farklı kodlanabilir!
Soru No. 7
JavaScript'ten HTML'ye geri dönelim. Yorum içinde hangi karakterleri ve nasıl değiştirmemiz gerekiyor?
<!-- <?= $str ?> -->
Çözüm: HTML (ve XML) yorumu içinde, <
,
&
, "
ve '
gibi tüm geleneksel özel
karakterler görünebilir. Yasak olan, ve bu sizi şaşırtabilir,
--
karakter çiftidir. Bu dizinin kaçış işlemi
belirtilmemiştir, bu yüzden onu nasıl değiştireceğiniz size kalmıştır.
Aralarına boşluk koyabilirsiniz. Veya örneğin ==
ile
değiştirebilirsiniz.
Soru No. 8
Sonuna yaklaşıyoruz, bu yüzden soruyu değiştirelim. Bu bağlamda değişkeni yazdırırken neye dikkat etmek gerektiğini düşünmeye çalışın:
<a href="<?= $str ?>">...</a>
Çözüm: Kaçış işlemine ek olarak, URL'nin javascript:
gibi
tehlikeli bir şema içermediğini doğrulamak da önemlidir, çünkü bu
şekilde oluşturulan bir URL tıklandığında saldırganın kodunu
çağırır.
Soru No. 9
Son olarak, gerçek uzmanlar için bir inci. Bu, modern bir JavaScript
framework'ü, özellikle Vue kullanan bir uygulamanın örneğidir. Bakalım
#app
öğesi içinde değişkeni yazdırırken neye dikkat etmeniz
gerektiğini düşünebilecek misiniz:
<div id="app">
<?= $str ?>
</div>
<script src="http://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
...
})
</script>
Bu kod, #app
öğesine oluşturulacak bir Vue uygulaması
oluşturur. Vue, bu öğenin içeriğini şablonu olarak anlar. Ve şablon
içinde, değişkenin yazdırılması veya javascript kodunun çağrılması
anlamına gelen (örn. {{ foo }}
) çift süslü parantezleri yorumlar.
Yani, #app
öğesi içinde, <
ve
&
karakterlerine ek olarak, {{
çiftinin de özel
bir anlamı vardır, bunu Vue'nun kendi etiketi olarak yorumlamaması için
başka bir karşılık gelen diziyle değiştirmemiz gerekir. Ancak HTML
varlıklarıyla değiştirme bu durumda yardımcı olmaz. Nasıl başa
çıkılır? Bir hile işe yarar: parantezlerin arasına boş bir HTML yorumu
{<!-- -->{
ekleriz ve Vue böyle bir diziyi
görmezden gelir.
Test Sonuçları
Testte nasıl performans gösterdiniz? Kaç doğru cevabınız var? En az 4 soruya doğru cevap verdiyseniz, en iyi %8'lik çözücüler arasındasınız – tebrikler!
Ancak, web sitenizin güvenliğini sağlamak için çıktıyı tüm durumlarda doğru bir şekilde işlemek zorunludur.
Sıradan bir HTML sayfasında ne kadar farklı bağlamın ortaya çıkabileceğine şaşırdıysanız, bilin ki hepsinden bahsetmedik bile. O zaman test çok daha uzun olurdu. Yine de, şablon sisteminiz bunu halledebiliyorsa, her bağlamda kaçış uzmanı olmanıza gerek yoktur.
Öyleyse onları deneyelim.
Şablon Sistemleri Nasıl Performans Gösteriyor?
Tüm modern şablon sistemleri, yazdırılan tüm değişkenleri otomatik olarak kaçıran otomatik kaçış (autoescaping) işleviyle övünür. Eğer bunu doğru yaparlarsa, web siteniz güvendedir. Eğer yanlış yaparlarsa, web sitesi tüm ciddi sonuçlarıyla birlikte XSS güvenlik açığı riskine maruz kalır.
Bu testin sorularından popüler şablon sistemlerini test edeceğiz, otomatik kaçışlarının ne kadar etkili olduğunu görmek için. PHP için şablon sistemlerinin dTest'i başlıyor.
Twig ❌
İlk sırada, en sık Symfony framework'ü ile birlikte kullanılan Twig (sürüm 3.5) şablon sistemi var. Ona
tüm test sorularını cevaplama görevini vereceğiz. $str
değişkeni her zaman hileli bir dize ile doldurulacak ve yazdırmasıyla nasıl
başa çıktığına bakacağız. Sonuçları sağda görüyorsunuz.
Cevaplarını ve davranışını oyun alanında keşfedin de
inceleyebilirsiniz.
{% set str = "<'\"&" %}
1) <p>{{ str }}</p>
2) <input value="{{ str }}">
3) <input value='{{ str }}'>
{% set str = "foo onclick=evilCode()" %}
4) <input value={{ str }}>
{% set str = "'\"\n\u{2028}" %}
5) <script> let foo = '{{ str }}'; </script>
6) <p onclick="foo('{{ str }}')"></p>
{% set str = "-- ---" %}
7) <!-- {{ str }} -->
{% set str = "javascript:evilCode()" %}
8) <a href="{{ str }}">...</a>
{% set str = "{{ foo }}" %}
9) <div id="app"> {{ str }} </div>
✅ <p><'"&</p>
✅ <input value="<'"&">
✅ <input value='<'"&'>
❌ <input value=foo onclick=evilCode()>
❌ <script> let foo = '"u{2028}; </script>
❌ <p onclick="foo('"u{2028})"></p>
❌ <!-- -- --- -->
❌ <a href="javascript:evilCode()">...</a>
❌ <div id="app"> {{ foo }} </div>
Twig dokuz testten altısında başarısız oldu!
Maalesef, Twig'in otomatik kaçış işlemi yalnızca HTML metninde ve niteliklerde çalışır ve o da yalnızca tırnak içine alınmışlarsa. Tırnak işaretleri eksik olduğunda, Twig herhangi bir hata bildirmez ve bir XSS güvenlik açığı oluşturur.
Bu özellikle can sıkıcıdır, çünkü React veya Svelte gibi popüler kütüphanelerde nitelik değerleri bu şekilde yazılır. Aynı anda Twig ve React kullanan bir programcı, bu nedenle tırnak işaretlerini tamamen doğal olarak unutabilir.
Twig'in otomatik kaçış işlemi diğer tüm örneklerde de başarısız
olur. (5) ve (6) bağlamlarında, {{ str|escape('js') }}
kullanarak
manuel olarak kaçış yapmak gerekir, diğer bağlamlar için Twig bir kaçış
fonksiyonu bile sunmaz. Hatalı bir bağlantının yazdırılmasına karşı
koruma (8) veya Vue için şablon desteği (9) de yoktur.
Blade ❌❌
İkinci katılımcı, Laravel ve ekosistemiyle sıkı bir şekilde entegre olan Blade (sürüm 10.9) şablon sistemidir. Yeteneklerini yine test sorularımızda kontrol edeceğiz. Cevaplarını oyun alanında keşfedin de inceleyebilirsiniz.
@php($str = "<'\"&")
1) <p>{{ $str }}</p>
2) <input value="{{ $str }}">
3) <input value='{{ $str }}'>
@php($str = "foo onclick=evilCode()")
4) <input value={{ $str }}>
@php($str = "'\"\n\u{2028}")
5) <script> let foo = {{ $str }}; </script>
6) <p onclick="foo({{ $str }})"></p>
@php($str = "-- ---")
7) <!-- {{ $str }} -->
@php($str = "javascript:evilCode()")
8) <a href="{{ $str }}">...</a>
@php($str = "{{ foo }}")
9) <div id="app"> {{ $str }} </div>
✅ <p><'"&</p>
✅ <input value="<'"&">
✅ <input value='<'"&'>
❌ <input value=foo onclick=evilCode()>
❌ <script> let foo = '" ; </script>
❌ <p onclick="foo('" )"></p>
❌ <!-- -- --- -->
❌ <a href="javascript:evilCode()">...</a>
❌❌ <div id="app"> <?php echo e(foo); ?> </div>
Blade dokuz testten altısında başarısız oldu!
Sonuç Twig'e benzer. Yine, otomatik kaçış yalnızca HTML metninde ve
niteliklerde ve yalnızca tırnak içine alınmışlarsa çalışır. Blade'in
otomatik kaçış işlemi diğer tüm örneklerde de başarısız olur. (5) ve
(6) bağlamlarında, {{ Js::from($str) }}
kullanarak manuel olarak
kaçış yapmak gerekir. Diğer bağlamlar için Blade bir kaçış fonksiyonu
bile sunmaz. Hatalı bir bağlantının yazdırılmasına karşı koruma (8)
veya Vue için şablon desteği (9) de yoktur.
Ancak şaşırtıcı olan, Blade'deki @php
direktifinin
başarısız olmasıdır, bu da kendi PHP kodunun doğrudan çıktıya
yazdırılmasına neden olur, bunu son satırda görüyorsunuz.
Smarty ❌❌❌
Şimdi PHP için en eski şablon sistemi olan Smarty (sürüm 4.3) test edeceğiz.
Büyük bir sürprizle, bu sistemin aktif otomatik kaçış özelliği yoktur.
Değişkenleri yazdırırken ya her seferinde {$var|escape}
filtresini belirtmeniz ya da otomatik HTML kaçışını etkinleştirmeniz
gerekir. Bununla ilgili bilgi belgelerde oldukça gizlidir.
{$str = "<'\"&"}
1) <p>{$str}</p>
2) <input value="{$str}">
3) <input value='{$str}'>
{$str = "foo onclick=evilCode()"}
4) <input value={$str}>
{$str = "'\"\n\u{2028}"}
5) <script> let foo = {$str}; </script>
6) <p onclick="foo({$str})"></p>
{$str = "-- ---"}
7) <!-- {$str} -->
{$str = "javascript:evilCode()"}
8) <a href="{$str}">...</a>
{$str = "{{ foo }}"}
9) <div id="app"> {$str} </div>
✅ <p><'"&</p>
✅ <input value="<'"&">
✅ <input value='<'"&'>
❌ <input value=foo onclick=evilCode()>
❌ <script> let foo = '"\u2028; </script>
❌ <p onclick="foo('"\u2028)"></p>
❌ <!-- -- --- -->
❌ <a href="javascript:evilCode()">...</a>
❌ <div id="app"> {{ foo }} </div>
Smarty dokuz testten altısında başarısız oldu!
Sonuç ilk bakışta önceki kütüphanelere benzer. Smarty yalnızca HTML
metninde ve niteliklerde, ve o da yalnızca değerler tırnak içine
alınmışsa otomatik olarak kaçış yapabilir. Diğer her yerde başarısız
olur. (5) ve (6) bağlamlarında, {$str|escape:javascript}
kullanarak manuel olarak kaçış yapmak gerekir. Ancak bu yalnızca otomatik
HTML kaçışı etkin değilse mümkündür, aksi takdirde bu kaçışlar
birbiriyle çakışır. Smarty bu nedenle güvenlik açısından bu testin
mutlak fiyaskosudur.
Latte ✅
Üçlüyü Latte (sürüm 3.0) şablon sistemi kapatıyor. Otomatik kaçışını deneyeceğiz. Cevaplarını ve davranışını oyun alanında keşfedin de inceleyebilirsiniz.
{var $str = "<'\"&"}
1) <p>{$str}</p>
2) <input value="{$str}">
3) <input value='{$str}'>
{var $str = "foo onclick=evilCode()"}
4) <input value={$str}>
{var $str = "'\"\n\u{2028}"}
5) <script> let foo = {$str}; </script>
6) <p onclick="foo({$str})"></p>
{var $str = "-- ---"}
7) <!-- {$str} -->
{var $str = "javascript:evilCode()"}
8) <a href="{$str}">...</a>
{var $str = "{{ foo }}"}
9) <div id="app"> {$str} </div>
✅ <p><'"&</p>
✅ <input value="<'"&">
✅ <input value='<'"&'>
✅ <input value="foo onclick=evilCode()">
✅ <script> let foo = "'\"\n\u2028"; </script>
✅ <p onclick="foo("'\"\n\u2028")"></p>
✅ <!-- - - - - - -->
✅ <a href="">...</a>
✅ <div id="app"> {<!-- -->{ foo }} </div>
Latte dokuz görevin hepsinde mükemmeldi!
HTML niteliklerindeki eksik tırnak işaretleriyle başa çıkmayı
başardı, hem <script>
öğesindeki hem de niteliklerdeki
JavaScript'i işlemeyi başardı ve HTML yorumlarındaki yasak diziyle bile
başa çıkabildi.
Dahası, bir saldırgan tarafından sahte bir bağlantıya tıklamanın kodunu çalıştırabileceği durumu önledi. Ve Vue için etiketlerin kaçışıyla başa çıkmayı başardı.
Bonus Test
Tüm şablon sistemlerinin temel yeteneklerinden biri bloklarla çalışmak
ve bununla ilişkili şablon kalıtımıdır. Bu nedenle test edilen tüm
şablon sistemlerine bir görev daha vereceğiz. Bir HTML niteliğinde
yazdıracağımız bir description
bloğu oluşturacağız. Gerçek
dünyada, elbette blok tanımı alt şablonda ve yazdırması üst şablonda,
yani örneğin layout'ta bulunurdu. Bu sadece basitleştirilmiş bir formdur,
ancak blokları yazdırırken otomatik kaçışı test etmek için yeterlidir.
Nasıl başardılar?
Twig: başarısız ❌ blokları yazdırırken karakterleri işlemez
{% block description %}
rock n' roll
{% endblock %}
<meta name='description'
content='{{ block('description') }}'>
<meta name='description'
content=' rock n' roll '> ❌
Blade: başarısız ❌ blokları yazdırırken karakterleri işlemez
@section('description')
rock n' roll
@endsection
<meta name='description'
content='@yield('description')'>
<meta name='description'
content=' rock n' roll '> ❌
Latte: başarılı ✅ blokları yazdırırken sorunlu karakterleri doğru bir şekilde işledi
{block description}
rock n' roll
{/block}
<meta name='description'
content='{include description}'>
<meta name='description'
content=' rock n' roll '> ✅
Neden Bu Kadar Çok Web Sitesi Savunmasız?
Twig, Blade veya Smarty gibi sistemlerdeki otomatik kaçış, basitçe beş
karakter <>"'&
'yi HTML varlıklarıyla değiştirerek
çalışır ve bağlamı hiçbir şekilde ayırt etmez. Bu nedenle yalnızca
bazı durumlarda çalışır ve diğer tüm durumlarda başarısız olur.
Naif otomatik kaçış tehlikeli bir özelliktir, çünkü yanlış bir
güvenlik hissi yaratır.
Bu nedenle, günümüzde web sitelerinin %27'sinden fazlasının kritik güvenlik açıklarına, özellikle de XSS'e sahip olması şaşırtıcı değildir (kaynak: Acunetix Web Vulnerability Report). Bundan nasıl çıkılır? Bağlamları ayırt eden bir şablon sistemi kullanın.
Latte, PHP'deki tek şablon sistemidir ki şablonu yalnızca bir karakter dizisi olarak görmez, HTML'yi anlar. Etiketlerin, niteliklerin vb. ne olduğunu anlar. Bağlamları ayırt eder. Ve bu nedenle HTML metninde doğru şekilde kaçış yapar, HTML etiketi içinde farklı, JavaScript içinde farklı vb.
Latte bu nedenle tek güvenli şablon sistemini temsil eder.
Ayrıca, HTML anlayışı sayesinde, kullanıcıların sevdiği harika n:attributes özelliğini sunar:
<ul n:if="$menu">
<li n:foreach="$menu->getItems() as $item">{$item->title}</li>
</ul>
Yorum göndermek için lütfen giriş yapın