Kvíz: meg tudja védeni magát az XSS sebezhetőségtől?
Tesztelje tudását a biztonsági kvízben! Meg tudja akadályozni, hogy egy támadó átvegye az irányítást egy HTML oldal felett?

Minden feladatban ugyanazt a kérdést fogja megoldani: hogyan kell helyesen
kiírni a $str
változót egy HTML oldalon, hogy ne keletkezzen XSS sebezhetőség?
A védekezés alapja az escapelés, ami azt jelenti, hogy a speciális
jelentéssel bíró karaktereket a megfelelő szekvenciákkal helyettesítjük.
Például, amikor egy stringet írunk ki HTML szövegbe, amelyben a
<
karakternek speciális jelentése van (egy tag kezdetét
jelzi), helyettesítjük a <
HTML entitással, és a
böngésző helyesen jeleníti meg a <
szimbólumot.
Legyen óvatos, mert az XSS sebezhetőség nagyon súlyos. Okozhatja, hogy egy támadó átveszi az irányítást az oldal vagy akár a felhasználói fiók felett. Sok sikert, és sikerüljön biztonságban tartani a HTML oldalt!
Az első három kérdés
Adja meg, milyen karaktereket és milyen módon kell kezelni az első, második és harmadik példában:
1) <p><?= $str ?></p>
2) <input value="<?= $str ?>">
3) <input value='<?= $str ?>'>
Ha a kimenet semmilyen módon nem lenne kezelve, a megjelenített oldal
részévé válna. Ha egy támadó a 'foo" onclick="evilCode()'
stringet juttatná a változóba, és a kimenet nem lenne kezelve, azt okozná,
hogy az elemre kattintva lefut a kódja:
$str = 'foo" onclick="evilCode()'
❌ kezelés nélkül: <input value="foo" onclick="evilCode()">
✅ kezeléssel: <input value="foo" onclick="evilCode()">
Az egyes példák megoldása:
- a
<
és&
karakterek a HTML tag és entitás kezdetét jelentik, helyettesítjük őket<
-vel és&
-mal - a
"
és&
karakterek az attribútum értékének végét és a HTML entitás kezdetét jelentik, helyettesítjük őket"
-tal és&
-mal - az
'
és&
karakterek az attribútum értékének végét és a HTML entitás kezdetét jelentik, helyettesítjük őket'
-sal és&
-mal
Minden helyes válaszért egy pontot kap. Természetesen mindhárom esetben lehet más karaktereket is entitásokkal helyettesíteni, ez nem árt, de nem szükséges.
4. kérdés
Folytatjuk tovább. Milyen karaktereket kell helyettesíteni a változó kiírásakor ebben a kontextusban?
<input value=<?= $str ?>>
Megoldás: Ahogy látja, itt hiányoznak az idézőjelek. A legegyszerűbb
egyszerűen kiegészíteni az idézőjeleket, majd ugyanúgy escapelni, mint az
előző kérdésben. Létezik egy második megoldás is, mégpedig a stringben a
szóközt és minden olyan karaktert, amelynek speciális jelentése van a tagen
belül, azaz >
, /
, =
és néhány
másikat, HTML entitásokkal helyettesíteni.
5. kérdés
Most már kezd érdekesebbé válni. Milyen karaktereket kell kezelni ebben a kontextusban:
<script>
let foo = '<?= $str ?>';
</script>
Megoldás: A <script>
tagen belül az escapelési
szabályokat a JavaScript határozza meg. HTML entitások itt nem
használatosak, azonban érvényes egy speciális szabály. Tehát milyen
karaktereket escapelünk? Egy JavaScript stringen belül természetesen
escapeljük az '
karaktert, amely azt határolja, mégpedig perjel
segítségével, tehát \'
-re cseréljük. Mivel a JavaScript nem
támogatja a többsoros stringeket (csak template
literal), escapelnünk kell a sorvégi karaktereket is. Azonban vigyázat, a
szokásos \n
és \r
karaktereken kívül a JavaScript
a \u2028
és \u2029
unicode karaktereket is sorvégnek
tekinti, amelyeket szintén escapelnünk kell. És végül az említett
speciális szabály: a stringben nem fordulhat elő </script
.
Ezt meg lehet akadályozni pl. <\/script
-re való cserével.
Ha ezt tudta, gratulálunk.
6. kérdés
A következő kontextus csak az előző variációjának tűnik. Mit gondol, eltérő lesz a kezelés?
<p onclick="foo('<?= $str ?>')"></p>
Megoldás: Ismét a JavaScript stringekben való escapelés szabályai
érvényesek itt, de ellentétben az előző kontextussal, ahol HTML
entitásokkal nem escapeltünk, itt éppen ellenkezőleg, escapelünk. Tehát
először escapeljük a JavaScript stringet perjelekkel, majd a speciális
jelentéssel bíró karaktereket ("
és &
) HTML
entitásokkal helyettesítjük. Vigyázat, a helyes sorrend fontos.
Ahogy látja, ugyanaz a JavaScript literál másképp lehet kódolva a
<script>
elemben és másképp egy attribútumban!
7. kérdés
Visszatérünk a JavaScriptből a HTML-be. Milyen karaktereket kell helyettesítenünk a kommenten belül és milyen módon?
<!-- <?= $str ?> -->
Megoldás: a HTML (és XML) kommenten belül az összes hagyományos
speciális karakter, mint a <
, &
,
"
és '
, előfordulhat. Tiltott, és ez
valószínűleg meglepi, a --
karakterpár. Ennek a szekvenciának
az escapelése nincs specifikálva, így önön múlik, milyen módon
helyettesíti. Közéjük tehet szóközöket. Vagy például helyettesítheti
==
-vel.
8. kérdés
Már közeledünk a végéhez, próbáljuk megváltoztatni a kérdést. Próbáljon meg elgondolkodni azon, mire kell figyelni a változó kiírásakor ebben a kontextusban:
<a href="<?= $str ?>">...</a>
Megoldás: az escapelésen kívül fontos még ellenőrizni, hogy az URL nem
tartalmaz-e veszélyes sémát, mint a javascript:
, mert egy így
összeállított URL kattintás után a támadó kódját hívná meg.
9. kérdés
Végül egy csemege az igazi ínyenceknek. Egy modern JavaScript
keretrendszert, konkrétan a Vue-t használó alkalmazás példája. Kíváncsi
vagyok, eszébe jut-e, mire kell figyelni a változó kiírásakor az
#app
elemen belül:
<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>
Ez a kód egy Vue alkalmazást hoz létre, amely az #app
elembe
fog renderelődni. A Vue ennek az elemnek a tartalmát a saját sablonjaként
értelmezi. És a sablonon belül értelmezi a
dupla kapcsos zárójeleket, amelyek egy változó kiírását vagy egy
JavaScript kód meghívását jelentik (pl. {{ foo }}
).
Tehát az #app
elemen belül a <
és
&
karaktereken kívül még speciális jelentése van a
{{
párnak, amelyet egy másik megfelelő szekvenciával kell
helyettesítenünk, hogy a Vue ne értelmezze a saját tagjeként. A HTML
entitásokkal való helyettesítés azonban ebben az esetben nem segít. Hogyan
birkózzunk meg vele? Működik egy trükk: a zárójelek közé egy üres HTML
kommentet illesztünk {<!-- -->{
, és a Vue egy ilyen
szekvenciát figyelmen kívül hagy.
Kvíz eredményei
Hogy teljesített a kvízben? Hány helyes válasza van? Ha legalább 4 kérdésre helyesen válaszolt, a legjobb 8% közé tartozik – gratulálunk!
Azonban webhelye biztonságának biztosításához elengedhetetlen a kimenet helyes kezelése minden helyzetben.
Ha meglepte, mennyi különböző kontextus fordulhat elő egy átlagos HTML oldalon, akkor tudja, hogy messze nem említettük mindet. Az egy sokkal hosszabb kvíz lenne. Ennek ellenére nem kell szakértőnek lennie az escapelésben minden kontextusban, ha a sablonrendszere képes rá.
Tehát próbáljuk ki őket.
Hogyan teljesítenek a sablonrendszerek?
Minden modern sablonrendszer büszkélkedik az automatikus escapelés funkcióval, amely automatikusan escapeli az összes kiírt változót. Ha ezt helyesen teszik, a webhelye biztonságban van. Ha rosszul teszik, a webhely ki van téve az XSS sebezhetőség kockázatának, annak minden súlyos következményével.
Teszteljük a népszerű sablonrendszereket ennek a kvíznek a kérdéseivel, hogy megtudjuk, mennyire hatékony az automatikus escapelésük. Kezdődik a PHP sablonrendszerek dTestje.
Twig ❌
Az első a sorban a Twig (3.5-ös
verzió) sablonrendszer, amelyet leggyakrabban a Symfony keretrendszerrel
együtt használnak. Feladjuk neki a feladatot, hogy válaszoljon az összes
kvízkérdésre. A $str
változó mindig egy trükkös stringgel
lesz feltöltve, és megnézzük, hogyan birkózik meg a kiírásával. Az
eredményeket jobbra látja. Válaszait és viselkedését a játszótéren is
megvizsgálhatja.
{% 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>
A Twig kilenc tesztből hatban megbukott!
Sajnos a Twig automatikus escapelése csak HTML szövegben és attribútumokban működik, és ráadásul csak akkor, ha idézőjelek közé vannak zárva. Amint hiányoznak az idézőjelek, a Twig nem jelez hibát, és létrehoz egy XSS biztonsági rést.
Ez különösen kellemetlen, mert így írják az attribútumértékeket olyan népszerű könyvtárakban, mint a React vagy a Svelte. Egy programozó, aki egyszerre használja a Twiget és a Reactot, így teljesen természetesen elfelejtheti az idézőjeleket.
A Twig automatikus escapelése minden más példában is megbukik. Az (5)
és (6) kontextusokban manuálisan kell escapelni a
{{ str|escape('js') }}
segítségével, további kontextusokhoz a
Twig escapelési funkciót sem kínál. Nem rendelkezik védelemmel a hibás
link kiírása ellen (8), sem támogatással a Vue sablonokhoz (9).
Blade ❌❌
A második résztvevő a Blade (10.9-es verzió) sablonrendszer, amely szorosan integrálva van a Laravellel és annak ökoszisztémájával. Ismét ellenőrizzük képességeit a kvízkérdéseinken. Válaszait a játszótéren is megvizsgálhatja.
@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>
A Blade kilenc tesztből hatban megbukott!
Az eredmény hasonló a Twigéhez. Ismét érvényes, hogy az automatikus
escapelés csak HTML szövegben és attribútumokban működik, és csak akkor,
ha idézőjelek közé vannak zárva. A Blade automatikus escapelése minden
más példában is megbukik. Az (5) és (6) kontextusokban manuálisan kell
escapelni a {{ Js::from($str) }}
segítségével. További
kontextusokhoz a Blade escapelési funkciót sem kínál. Nem rendelkezik
védelemmel a hibás link kiírása ellen (8), sem támogatással a Vue
sablonokhoz (9).
Ami azonban meglepő, az a @php
direktíva hibája a Blade-ben,
ami azt okozza, hogy a saját PHP kódja közvetlenül a kimenetre kerül, amit
az utolsó sorban láthat.
Smarty ❌❌❌
Most a legrégebbi PHP sablonrendszert teszteljük, ami a Smarty (4.3-as verzió). Nagy meglepetésre
ennek a rendszernek nincs aktív automatikus escapelése. Így a változók
kiírásakor vagy minden alkalommal meg kell adni a {$var|escape}
szűrőt, vagy aktiválni kell az automatikus HTML escapelést. Erről az
információ a dokumentációban elég eldugott helyen van.
{$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>
A Smarty kilenc tesztből hatban megbukott!
Az eredmény első pillantásra hasonló az előző könyvtárakéhoz.
A Smarty csak HTML szövegben és attribútumokban tud automatikusan escapelni,
és csak akkor, ha az értékek idézőjelek közé vannak zárva. Mindenhol
máshol megbukik. Az (5) és (6) kontextusokban manuálisan kell escapelni a
{$str|escape:javascript}
segítségével. De ez csak akkor
lehetséges, ha nincs aktiválva az automatikus HTML escapelés, különben ezek
az escapelések ütköznek egymással. A Smarty így biztonsági szempontból
ennek a tesztnek a teljes bukása.
Latte ✅
A hármast a Latte (3.0-s verzió) sablonrendszer zárja. Kipróbáljuk az autoescapelését. Válaszait és viselkedését a játszótéren is megvizsgálhatja.
{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>
A Latte mind a kilenc feladatban kiválóan teljesített!
Megbirkózott a hiányzó idézőjelekkel a HTML attribútumoknál, kezelte a
JavaScriptet mind a <script>
elemben, mind az
attribútumokban, és megbirkózott a tiltott szekvenciával a HTML
kommentekben is.
Sőt, megelőzte azt a helyzetet, hogy egy támadó által becsempészett linkre kattintva lefusson a kódja. És megbirkózott a Vue tagjeinek escapelésével is.
Bónusz teszt
Minden sablonrendszer egyik lényeges képessége a blokkokkal való munka
és az ehhez kapcsolódó sablonöröklődés. Ezért megpróbálunk még egy
feladatot adni az összes tesztelt sablonrendszernek. Létrehozunk egy
description
blokkot, amelyet egy HTML attribútumban írunk ki.
Valós világban természetesen a blokk definíciója a gyermek-sablonban,
kiírása pedig a szülő-sablonban, tehát például a layoutban található
lenne. Ez csak egy egyszerűsített forma, de elegendő ahhoz, hogy teszteljük
az automatikus escapelést a blokkok kiírásakor. Hogyan teljesítettek?
Twig: megbukott ❌ a blokkok kiírásakor nem kezeli a karaktereket
{% block description %}
rock n' roll
{% endblock %}
<meta name='description'
content='{{ block('description') }}'>
<meta name='description'
content=' rock n' roll '> ❌
Blade: megbukott ❌ a blokkok kiírásakor nem kezeli a karaktereket
@section('description')
rock n' roll
@endsection
<meta name='description'
content='@yield('description')'>
<meta name='description'
content=' rock n' roll '> ❌
Latte: megfelelt ✅ a blokkok kiírásakor helyesen kezelte a problémás karaktereket
{block description}
rock n' roll
{/block}
<meta name='description'
content='{include description}'>
<meta name='description'
content=' rock n' roll '> ✅
Miért sebezhető ennyi webhely?
Az automatikus escapelés olyan rendszerekben, mint a Twig, Blade vagy
Smarty, úgy működik, hogy egyszerűen helyettesíti az öt
<>"'&
karaktert HTML entitásokkal, és nem tesz
különbséget a kontextusok között. Ezért csak bizonyos helyzetekben
működik, és minden másban megbukik. A naiv automatikus escapelés
veszélyes funkció, mert hamis biztonságérzetet kelt.
Nem meglepő tehát, hogy jelenleg a webhelyek több mint 27%-a rendelkezik kritikus sebezhetőségekkel, elsősorban XSS-sel (forrás: Acunetix Web Vulnerability Report). Hogyan lehet ebből kilábalni? Használjon olyan sablonrendszert, amely megkülönbözteti a kontextusokat.
A Latte az egyetlen PHP sablonrendszer, amely nem csak karakterláncként tekint a sablonra, hanem érti a HTML-t. Érti, mik a tagek, attribútumok stb. Megkülönbözteti a kontextusokat. És ezért helyesen escapel HTML szövegben, másképp egy HTML tagen belül, másképp JavaScripten belül stb.
A Latte így az egyetlen biztonságos sablonrendszer.
Sőt, a HTML megértésének köszönhetően a csodálatos n:attribútumokat is kínálja, amit a felhasználók imádnak:
<ul n:if="$menu">
<li n:foreach="$menu->getItems() as $item">{$item->title}</li>
</ul>
A hozzászólás elküldéséhez kérjük, jelentkezzen be