Test: XSS güvenlik açığına karşı savunma yapabilir misiniz?

2 yıl önce Kimden David Grudl  

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 &lt; 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&quot; onclick=&quot;evilCode()">

Her örnek için çözümler:

  1. < ve & karakterleri bir HTML etiketinin ve varlığının başlangıcını temsil eder; bunları &lt; ve &amp;
  2. " ve & karakterleri bir öznitelik değerinin sonunu ve bir HTML varlığının başlangıcını temsil eder; bunları &quot; ve &amp;
  3. ' ve & karakterleri bir öznitelik değerinin sonunu ve bir HTML varlığının başlangıcını temsil eder; bunları &apos; ve &amp;

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>&lt;&#039;&quot;&amp;</p>
✅ <input value="&lt;&#039;&quot;&amp;">
✅ <input value='&lt;&#039;&quot;&amp;'>


❌ <input value=foo onclick=evilCode()>


❌ <script> let foo = &#039;&quot;u{2028}; </script>
❌ <p onclick="foo(&#039;&quot;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>&lt;&#039;&quot;&amp;</p>
✅ <input value="&lt;&#039;&quot;&amp;">
✅ <input value='&lt;&#039;&quot;&amp;'>


❌ <input value=foo onclick=evilCode()>


❌ <script>	let foo = &#039;&quot; ; </script>
❌ <p onclick="foo(&#039;&quot; )"></p>


❌ <!-- -- --- -->


❌ <a href="javascript:evilCode()">...</a>


❌❌ <div id="app"> &lt;?php echo e(foo); ?&gt; </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>&lt;'"&amp;</p>
✅ <input value="&lt;&apos;&quot;&amp;">
✅ <input value='&lt;&apos;&quot;&amp;'>


✅ <input value="foo onclick=evilCode()">


✅ <script> let foo = "'\"\n\u2028"; </script>
✅ <p onclick="foo(&quot;&apos;\&quot;\n\u2028&quot;)"></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&apos; 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>