Test: XSS güvenlik açığına karşı savunma yapabilir misiniz?
Bu testte bilginizi ve güvenlik becerilerinizi sınayın! Bir saldırganın bir HTML sayfasının kontrolünü ele geçirmesini engelleyebilir misiniz?
Tüm görevlerde aynı soruyu ele alacaksınız: $str
değişkeninin bir HTML sayfasında XSS açığı oluşturmadan
nasıl düzgün bir şekilde görüntüleneceği. Savunmanın temeli
escaping, yani özel anlamları olan karakterlerin karşılık gelen
dizilerle değiştirilmesidir. Örneğin, <
karakterinin özel
bir anlama sahip olduğu (bir etiketin başlangıcını gösteren) bir dizeyi
HTML metnine çıktı olarak verirken, bunu <
HTML
varlığı ile değiştiririz ve tarayıcı <
sembolünü doğru
şekilde görüntüler.
XSS güvenlik açığı çok ciddi olduğu için dikkatli olun. Bir saldırganın bir sayfanın ve hatta bir kullanıcının hesabının kontrolünü ele geçirmesine neden olabilir. İyi şanslar ve HTML sayfasını güvende tutmayı başarabilirsiniz!
İlk üç soru
Birinci, ikinci ve üçüncü örneklerde hangi karakterlerin nasıl işlenmesi gerektiğini belirtin:
1) <p><?= $str ?></p>
2) <input value="<?= $str ?>">
3) <input value='<?= $str ?>'>
Çıktı herhangi bir şekilde işlenmezse, görüntülenen sayfanın bir
parçası haline gelirdi. Bir saldırgan 'foo" onclick="evilCode()'
dizesini bir değişkene eklemeyi başarırsa ve çıktı işlenmezse, öğeye
tıklandığında kodlarının yürütülmesine neden olur:
$str = 'foo" onclick="evilCode()'
❌ not treated: <input value="foo" onclick="evilCode()">
✅ treated: <input value="foo" onclick="evilCode()">
Her örnek için çözümler:
<
ve&
karakterleri bir HTML etiketinin ve varlığının başlangıcını temsil eder; bunları<
ve&
"
ve&
karakterleri bir öznitelik değerinin sonunu ve bir HTML varlığının başlangıcını temsil eder; bunları"
ve&
'
ve&
karakterleri bir öznitelik değerinin sonunu ve bir HTML varlığının başlangıcını temsil eder; bunları'
ve&
Her doğru cevap için bir puan alırsınız. Elbette, her üç durumda da, diğer karakterleri varlıklarla değiştirebilirsiniz; herhangi bir zararı yoktur, ancak gerekli değildir.
Soru No. 4
Devam edecek olursak, bu bağlamda bir değişken görüntülenirken hangi karakterlerin değiştirilmesi gerekir?
<input value=<?= $str ?>>
Çözüm: Gördüğünüz gibi, burada tırnak işaretleri eksik. En kolay
yol, tırnak işaretlerini eklemek ve ardından önceki soruda olduğu gibi
kaçmaktır. Ayrıca, boşlukları ve >
, /
,
=
ve some
others gibi bir etiket içinde özel anlamı olan tüm karakterleri HTML
varlıkları ile değiştirmek için ikinci bir çözüm de vardır.
Soru No. 5
Şimdi daha da ilginçleşiyor. Hangi karakterlerin bu bağlamda ele alınması gerekiyor:
<script>
let foo = '<?= $str ?>';
</script>
Çözüm: İçeride <script>
etiketinde, kaçış
kuralları JavaScript tarafından belirlenir. HTML varlıkları burada
kullanılmaz, ancak özel bir kural vardır. Peki hangi karakterlerden
kaçacağız? JavaScript dizesinin içinde, dizeyi sınırlayan '
karakterinden doğal olarak ters eğik çizgi kullanarak kaçarız ve bunu
\'
ile değiştiririz. JavaScript çok satırlı dizeleri
desteklemediğinden (şablon
değişmezleri hariç), satırsonu karakterlerinden de kaçmamız gerekir.
Ancak, JavaScript'in normal \n
ve \r
karakterlerine ek
olarak, \u2028
ve \u2029
Unicode karakterlerini de
satırsonu karakterleri olarak kabul ettiğini ve bunlardan da kaçmamız
gerektiğini unutmayın. Son olarak, bahsedilen özel kural: dize
</script
içermemelidir. Bu, örneğin <\/script
ile değiştirilerek önlenebilir.
Eğer bunu biliyorsanız, tebrikler.
Soru No. 6
Aşağıdaki bağlam bir öncekinin sadece bir varyasyonu gibi görünüyor. Tedavinin farklı olacağını düşünüyor musunuz?
<p onclick="foo('<?= $str ?>')"></p>
Çözüm: Yine, JavaScript dizeleri için kaçış kuralları burada da
geçerlidir, ancak HTML varlıklarının kaçmadığı önceki bağlamdan
farklı olarak, burada bunlar kaçmaktadır. Bu nedenle, önce ters eğik
çizgileri kullanarak JavaScript dizesini kaçarız ve ardından özel
karakterleri ("
ve &
) HTML varlıklarıyla
değiştiririz. Dikkatli olun, doğru sıra önemlidir.
Gördüğünüz gibi, aynı JavaScript değişmezi bir JavaScript
değişmezinde farklı şekilde kodlanabilir. <script>
öğesinde ve farklı olarak bir öznitelikte!
Soru No. 7
JavaScript'ten HTML'e geri dönelim. Yorumun içinde hangi karakterleri nasıl değiştirmemiz gerekiyor?
<!-- <?= $str ?> -->
Çözüm: Bir HTML (ve XML) yorumunun içinde <
,
&
, "
ve '
gibi tüm geleneksel özel
karakterler görünebilir. Yasak olan ve sizi şaşırtabilecek olan karakter
çifti --
. Bu diziden kaçış belirtilmemiştir, bu nedenle nasıl
değiştirileceği size bağlıdır. Bunları boşluklarla
serpiştirebilirsiniz. Veya, örneğin, bunları ==
ile
değiştirebilirsiniz.
Soru No. 8
Sona yaklaşıyoruz, bu yüzden soruyu çeşitlendirmeye çalışalım. Bu bağlamda bir değişken yazdırırken nelere dikkat etmeniz gerektiğini düşünmeye çalışın:
<a href="<?= $str ?>">...</a>
Çözüm: Kaçışa ek olarak, URL'nin javascript:
gibi
tehlikeli bir şema içermediğini doğrulamak da önemlidir, çünkü bu
şekilde oluşturulmuş bir URL tıklandığında saldırganın kodunu
çalıştıracaktır.
Soru No. 9
Son olarak, gerçek uzmanlar için bir ikram. Bu, modern bir JavaScript
çerçevesi, özellikle de Vue kullanan bir uygulama örneğidir. Bakalım
#app
öğesi içinde bir değişken yazdırırken nelere dikkat
etmeniz gerektiğini bulabilecek misiniz?
<div id="app">
<?= $str ?>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
...
})
</script>
Bu kod, #app
öğesine işlenecek bir Vue uygulaması
oluşturur. Vue bu öğenin içeriğini kendi şablonu olarak yorumlar. Ve
şablon içinde, değişken çıktısını temsil eden veya JavaScript kodunu
çağıran çift küme parantezlerini yorumlar
(örn, {{ foo }}
).
Dolayısıyla, #app
öğesi içinde, <
ve
&
karakterlerinin yanı sıra, {{
çiftinin de
özel bir anlamı vardır ve Vue'nun bunu kendi etiketi olarak yorumlamasını
önlemek için başka bir uygun diziyle değiştirmemiz gerekir. HTML
varlıkları ile değiştirmek bu durumda yardımcı olmaz. Bununla nasıl başa
çıkılır? Bir hile var: parantezlerin arasına boş bir HTML yorumu ekleyin
{<!-- -->{
ve Vue bu diziyi yok sayar.
Sınav Sonuçları
Testte nasıl yaptın? Kaç tane doğru cevabınız var? En az 4 soruya doğru yanıt verdiyseniz, çözenlerin ilk %8'i arasındasınız demektir – tebrikler!
Bununla birlikte, web sitenizin güvenliğini sağlamak, her durumda çıktıyı doğru şekilde ele almayı gerektirir.
Tipik bir HTML sayfasında kaç farklı bağlamın yer alabileceği sizi şaşırttıysa, şu ana kadar hepsinden bahsetmediğimizi bilin. Bu, testi çok daha uzun hale getirirdi. Bununla birlikte, şablonlama sisteminiz bunu halledebiliyorsa, her bağlamda kaçış konusunda uzman olmanıza gerek yoktur.
Öyleyse, onları test edelim.
Şablonlama sistemleri nasıl performans gösterir?
Tüm modern şablonlama sistemleri, çıktısı alınan tüm değişkenleri otomatik olarak önceleyen bir autoescaping özelliğine sahiptir. Eğer bunu doğru yaparlarsa, web siteniz güvende olur. Kötü yaparlarsa, site tüm ciddi sonuçlarıyla birlikte XSS güvenlik açığı riskine maruz kalır.
Bu testteki sorulardan popüler şablonlama sistemlerini test ederek, otomatik kaçışlarının etkinliğini belirleyeceğiz. PHP şablonlama sistemlerini incelemeye başlayalım.
Twig ❌
İlk olarak, en yaygın olarak Symfony çerçevesi ile birlikte kullanılan
Twig şablonlama sistemi (sürüm 3.5).
Onu tüm sınav sorularını yanıtlamakla görevlendireceğiz.
$str
değişkeni her zaman zor bir dize ile doldurulacak ve
çıktısını nasıl işlediğini göreceğiz. Sonuçları sağ tarafta
görebilirsiniz. Ayrıca cevaplarını ve davranışını oyun alanında
keşfedebilirsiniz.
{% 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!
Ne yazık ki, Twig'in otomatik kaçış özelliği yalnızca HTML metinlerinde ve özniteliklerinde ve o zaman bile yalnızca tırnak içine alındıklarında çalışır. Tırnak işaretleri eksik olduğunda, Twig herhangi bir hata bildirmez ve bir XSS güvenlik açığı oluşturur.
Bu özellikle rahatsız edici bir durumdur çünkü React veya Svelte gibi popüler kütüphanelerde öznitelik değerleri bu şekilde yazılır. Hem Twig hem de React kullanan bir programcı doğal olarak tırnak işaretlerini unutabilir.
Twig'in otomatik önceleme özelliği de diğer tüm örneklerde başarısız
olur. (5) ve (6) numaralı bağlamlarda, manuel kaçış için
{{ str|escape('js') }}
Diğer bağlamlar için ise Twig bir kaçış
işlevi bile sunmamaktadır. Ayrıca, kötü amaçlı bir bağlantının
yazdırılmasına karşı koruma (8) veya Vue şablonları için destek (9)
sunmamaktadır.
Blade ❌❌
İkinci katılımcı, Laravel ve ekosistemi ile sıkı bir şekilde entegre olan Blade şablonlama sistemidir (sürüm 10.9). Yine, yeteneklerini sınav sorularımızda test edeceğiz. Cevaplarını oyun alanında da keşfedebilirsiniz.
@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>
Bıçak dokuz testin altısında başarısız oldu!
Sonuç Twig'e benzer. Yine, otomatik kaçış yalnızca HTML metin ve
özniteliklerinde ve yalnızca tırnak içine alındıklarında çalışır.
Blade'in otomatik öncelemesi diğer tüm örneklerde de başarısız olur. (5)
ve (6) numaralı bağlamlarda, manuel kaçış için
{{ Js::from($str) }}
. Diğer bağlamlar için Blade bir kaçış
işlevi bile sunmaz. Ayrıca, kötü amaçlı bir bağlantının
yazdırılmasına karşı koruma (8) veya Vue şablonları için destek (9)
sunmaz.
Ancak şaşırtıcı olan, son satırda görüldüğü gibi, kendi PHP
kodunun doğrudan çıktıya verilmesine neden olan Blade'deki @php
yönergesinin başarısızlığıdır.
Latte ✅
Üçlü, Latte şablonlama sistemi (sürüm 3.0) tarafından tamamlanmıştır. Otomatik yazımını test edeceğiz. Ayrıca cevaplarını ve davranışlarını oyun alanında keşfedebilirsiniz.
{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 tamamında başarılı oldu!
HTML özniteliklerindeki eksik tırnak işaretlerini işlemeyi başardı,
JavaScript'i hem <script>
öğesinde ve özniteliklerde ve
HTML yorumlarındaki yasak dizilimle ilgilendi.
Dahası, bir saldırgan tarafından sağlanan kötü amaçlı bir bağlantıya tıklandığında kodlarının çalıştırılabileceği bir durumu önledi. Ve Vue için etiketlerin kaçışını ele almayı başardı.
Bonus test
Tüm şablonlama sistemlerinin temel yeteneklerinden biri bloklarla ve ilgili
şablon kalıtımıyla çalışmaktır. Bu nedenle, test edilen tüm şablonlama
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, blok tanımı elbette alt şablonda ve çıktısı da düzen gibi üst
şablonda yer alacaktır. Bu sadece basitleştirilmiş bir formdur, ancak
blokların çıktısını alırken otomatik kodlamayı test etmek için
yeterlidir. Nasıl performans gösterdiler?
Twig: failed ❌ blokların çıktısı alınırken karakterler düzgün şekilde escaped edilmiyor
{% block description %}
rock n' roll
{% endblock %}
<meta name='description'
content='{{ block('description') }}'>
<meta name='description'
content=' rock n' roll '> ❌
Blade: failed ❌ blokların çıktısı alınırken karakterler düzgün bir şekilde escaped edilmiyor
@section('description')
rock n' roll
@endsection
<meta name='description'
content='@yield('description')'>
<meta name='description'
content=' rock n' roll '> ❌
Latte: blokların çıktısını alırken ✅ geçti, sorunlu karakterleri doğru ş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 sistemlerde otomatik yazım,
<>"'&
adresindeki beş karakteri HTML varlıklarıyla
değiştirerek çalışır ve bağlamı ayırt etmez. Bu nedenle, yalnızca
bazı durumlarda çalışır ve diğerlerinde başarısız olur. Naive
autoescaping tehlikeli bir özelliktir çünkü yanlış bir güvenlik hissi
yaratır.
O halde, şu anda web sitelerinin %27'sinden fazlasının başta XSS olmak üzere kritik güvenlik açıklarına sahip olması şaşırtıcı değildir (kaynak: Acunetix Web Güvenlik Açığı Raporu). Bu durumdan nasıl kurtulabilirsiniz? Bağlamları ayırt eden bir şablonlama sistemi kullanın.
Latte, bir şablonu sadece bir karakter dizisi olarak algılamayan, ancak HTML'yi anlayan tek PHP şablonlama sistemidir. Etiketlerin, niteliklerin vb. ne olduğunu bilir. Bağlamları ayırt eder. Bu nedenle, HTML metni içinde, HTML etiketleri içinde farklı şekilde, JavaScript içinde farklı şekilde, vb. doğru şekilde kaçar.
Böylece Latte tek güvenli şablonlama 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