Kvíz: XSS sebezhetőség ellen?
Tesztelje tudását és biztonsági ismereteit ebben a kvízben! Meg tudja akadályozni, hogy egy támadó átvegye az irányítást egy HTML oldal felett?
Minden feladatban ugyanazzal a kérdéssel fogsz foglalkozni: hogyan
jelenítheted meg megfelelően a $str
változót egy HTML-oldalon
anélkül, hogy XSS
sebezhetőséget hoznál létre. A védekezés alapja a escaping,
ami a speciális jelentésű karakterek megfelelő szekvenciákkal való
helyettesítését jelenti. Például, amikor egy olyan karakterláncot adunk ki
HTML-szövegként, amelyben a <
karakter különleges
jelentéssel bír (egy címke kezdetét jelzi), akkor azt a
<
HTML-egységgel helyettesítjük, és a böngésző
helyesen jeleníti meg a <
szimbólumot.
Legyünk éberek, mivel az XSS sebezhetőség nagyon komoly. A támadó átveheti az irányítást egy oldal vagy akár egy felhasználói fiók felett. Sok szerencsét, és sikerüljön biztonságban tartani a HTML-oldalt!
A kérdések első hármasa
Adja meg, hogy az első, második és harmadik példában mely karaktereket és hogyan kell kezelni:
1) <p><?= $str ?></p>
2) <input value="<?= $str ?>">
3) <input value='<?= $str ?>'>
Ha a kimenetet semmilyen módon nem kezelnénk, az a megjelenített oldal
részévé válna. Ha egy támadónak sikerült beillesztenie a
'foo" onclick="evilCode()'
karakterláncot egy változóba, és a
kimenet nem lett kezelve, akkor az elemre kattintva végrehajtódna
a kódja:
$str = 'foo" onclick="evilCode()'
❌ not treated: <input value="foo" onclick="evilCode()">
✅ treated: <input value="foo" onclick="evilCode()">
Megoldások az egyes példákhoz:
- a
<
és a&
karakterek egy HTML tag és egy entitás kezdetét jelentik; helyettesítsük őket a<
és a karakterekkel.&
- a
"
és a&
karakterek egy attribútumérték végét és egy HTML-entitás kezdetét jelölik; helyettesítsük őket a"
és a&
- a
'
és a&
karakterek egy attribútumérték végét és egy HTML-entitás kezdetét jelentik; helyettesítsék őket a'
és a karakterekkel.&
Minden helyes válaszért egy pontot kap. Természetesen mindhárom esetben más karaktereket is helyettesíthetsz entitásokkal; ez nem okoz kárt, de nem is szükséges.
4. kérdés
Továbblépve, mely karaktereket kell helyettesíteni egy változó megjelenítésekor ebben a kontextusban?
<input value=<?= $str ?>>
Megoldás: Amint láthatja, itt hiányoznak az idézőjelek.
A legegyszerűbb, ha egyszerűen hozzáadjuk az idézőjeleket, majd az előző
kérdéshez hasonlóan escape-eljük. Van egy második megoldás is, amely
szerint a szóközöket és minden olyan karaktert, amelynek különleges
jelentése van egy címkén belül, mint például a >
,
/
, =
, és néhány
más, HTML-egységekkel kell helyettesíteni.
5. kérdés
Most már egyre érdekesebb lesz. Mely karaktereket kell ebben a kontextusban kezelni:
<script>
let foo = '<?= $str ?>';
</script>
Megoldás: A <script>
tagben az escaping szabályokat a
JavaScript határozza meg. A HTML entitásokat itt nem használják, de van egy
speciális szabály. Tehát mely karaktereket menekítsük ki? A JavaScript
karakterlánc belsejében természetesen elkerüljük az azt határoló
'
karaktert, egy backslash segítségével, és a \'
karakterrel helyettesítjük. Mivel a JavaScript nem támogatja a többsoros
karakterláncokat (kivéve a sablon
literálokat), az újsor karaktereket is el kell kerülnünk. Figyeljünk
azonban arra, hogy a szokásos \n
és \r
karaktereken
kívül a JavaScript a \u2028
és \u2029
Unicode
karaktereket is újsorjelnek tekinti, amelyeket szintén ki kell kerülnünk.
Végül az említett speciális szabály: a karakterlánc nem tartalmazhat
</script
. Ez például úgy akadályozható meg, hogy
<\/script
-ra cseréljük.
Ha ezt tudta, akkor gratulálunk.
6. kérdés
A következő összefüggés úgy tűnik, hogy az előzőnek csak egy változata. Ön szerint más lesz a kezelés?
<p onclick="foo('<?= $str ?>')"></p>
Megoldás: Itt is a JavaScript karakterláncokra vonatkozó
escaping-szabályok érvényesek, de az előző kontextustól eltérően, ahol a
HTML-egységek nem lettek escapedálva, itt escaped-elve vannak. Tehát
először a JavaScript karakterláncot backslashes használatával kikerüljük,
majd a speciális karaktereket ("
és &
)
HTML-egységekkel helyettesítjük. Vigyázzunk, a helyes sorrend fontos.
Amint láthatja, ugyanazt a JavaScript szó szerinti karaktert
különbözőképpen lehet kódolni egy <script>
elemben és
másképp egy attribútumban!
7. kérdés
Térjünk vissza a JavaScriptről a HTML-re. Mely karaktereket kell kicserélnünk a megjegyzésen belül és hogyan?
<!-- <?= $str ?> -->
Megoldás: Egy HTML (és XML) megjegyzésen belül minden hagyományos
speciális karakter, például a <
, &
,
"
és '
, megjelenhet. Ami tilos, és ez talán
meglepő lehet, az a --
karakterpár. Ennek a sorozatnak a
kikerülése nincs előírva, így csak rajtad múlik, hogyan helyettesíted.
Szóközökkel közbeiktathatja őket. Vagy például helyettesítheti őket
==
.
8. kérdés
Közeledünk a végéhez, ezért próbáljuk meg variálni a kérdést. Próbáljuk meg átgondolni, hogy mire kell figyelnünk, amikor egy változót nyomtatunk ki ebben a kontextusban:
<a href="<?= $str ?>">...</a>
Megoldás: javascript:
Az ilyen módon összeállított URL
ugyanis a támadó kódját hajtaná végre, amikor rákattint.
9. kérdés
Végre egy csemege az igazi ínyenceknek. Ez egy példa egy modern JavaScript
keretrendszert, konkrétan a Vue-t használó alkalmazásra. Lássuk,
kitaláljuk, mire kell figyelni, amikor a #app
elemen belül
kiírunk egy változót:
<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 a #app
elemben
lesz megjelenítve. A Vue ennek az elemnek a tartalmát sablonként értelmezi.
A sablonon belül pedig értelmezi a
dupla szögletes zárójeleket, amelyek változó kimenetet vagy hívó
JavaScript kódot jelentenek (pl, {{ foo }}
).
Tehát a #app
elemen belül a <
és
&
karakterek mellett a {{
párnak is van egy
speciális jelentése, amit egy másik megfelelő szekvenciával kell
helyettesítenünk, hogy a Vue ne értelmezze saját tagként. A HTML
entitásokkal való helyettesítés ebben az esetben nem segít. Hogyan
kezeljük ezt? Van egy trükk: szúrjunk be egy üres HTML-kommentárt a
zárójelek közé {<!-- -->{
, és a Vue figyelmen kívül
hagyja ezt a szekvenciát.
Kvíz eredmények
Hogyan szerepeltél a kvízben? Hány helyes válaszod van? Ha legalább 4 kérdésre helyesen válaszoltál, akkor a megfejtők legjobb 8%-ához tartozol – gratulálunk!
A weboldal biztonságának garantálása azonban minden helyzetben megköveteli a kimenetek megfelelő kezelését.
Ha meglepődött azon, hogy mennyi különböző kontextus jelenhet meg egy tipikus HTML-oldalon, akkor tudja, hogy még messze nem említettük az összeset. Ez sokkal hosszabbá tenné a kvízt. Mindazonáltal nem kell minden kontextusban szakértőnek lennie a menekülésben, ha a templating rendszere képes kezelni azt.
Teszteljük hát őket.
Hogyan teljesítenek a templating rendszerek?
Minden modern templating rendszer büszkélkedhet egy autoescaping funkcióval, amely automatikusan megszünteti az összes kiadott változót. Ha ezt helyesen teszik, a webhelye biztonságban van. Ha rosszul csinálják, a webhely ki van téve az XSS sebezhetőség kockázatának, annak minden súlyos következményével együtt.
A kvíz kérdései közül a népszerű templating rendszereket fogjuk tesztelni, hogy megállapítsuk, mennyire hatékony az automatikus mentésük. Kezdődjön a PHP templating rendszerek áttekintése.
Twig ❌
Az első a Twig templating rendszer
(3.5 verzió), amelyet leggyakrabban a Symfony keretrendszerrel együtt
használnak. Ezt bízzuk meg a kvízkérdések megválaszolásával. A
$str
változót mindig egy trükkös sztringgel töltjük fel, és
megnézzük, hogyan kezeli a kimenetét. Az eredményeket a jobb oldalon
láthatjuk. A válaszait és viselkedését a játszótéren is
felfedezhetjük.
{% 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>
Kilencből hat tesztben megbukott!
Sajnos a Twig automatikus eszkábálás csak a HTML szövegben és az attribútumokban működik, és akkor is csak akkor, ha idézőjelek közé vannak zárva. Amint az idézőjelek hiányoznak, a Twig nem jelent hibát, és XSS biztonsági rést hoz létre.
Ez azért különösen kellemetlen, mert az attribútumértékek így íródnak az olyan népszerű könyvtárakban, mint a React vagy a Svelte. Egy programozó, aki Twiget és Reactot is használ, teljesen természetesen elfelejtheti az idézőjeleket.
A Twig autoescapingje az összes többi példában is kudarcot vall. Az (5)
és (6) kontextusban kézi szkriptelésre van szükség a twig használatával.
{{ str|escape('js') }}
, míg a többi kontextusban a Twig még csak
nem is kínál eszkábálási funkciót. Hiányzik továbbá a rosszindulatú
linkek nyomtatása elleni védelem (8) és a Vue sablonok támogatása (9).
Blade ❌❌
A második résztvevő a Blade templating rendszer (10.9-es verzió), amely szorosan integrálódik a Laravelbe és annak ökoszisztémájába. Ismét a képességeit fogjuk tesztelni kvízkérdéseinkben. Válaszait a játszótéren is felfedezhetitek.
@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>
Kilenc tesztből hatban megbukott a penge!
Az eredmény hasonló a Twig-hez. Ismét csak a HTML-szövegben és az
attribútumokban működik az automatikus eszkábálás, és csak akkor, ha
idézőjelek közé vannak zárva. A Blade automatikus szedése az összes
többi példában is kudarcot vallott. Az (5) és (6) kontextusban kézi
eszkábálásra van szükség a következő használatával
{{ Js::from($str) }}
. A többi kontextusban a Blade nem is kínál
eszkábálási funkciót. Hiányzik továbbá a rosszindulatú linkek
nyomtatása elleni védelem (8) és a Vue-sablonok támogatása (9).
Ami azonban meglepő, az a @php
direktíva hibája a Blade-ben,
ami a saját PHP kódjának közvetlen kimeneti kimenetre történő kiadását
okozza, ahogy az az utolsó sorban látható.
Latte ✅
A triót a Latte templating rendszer (3.0 verzió) zárja. Tesztelni fogjuk az autoescapinget. Válaszait és viselkedését a játszótéren is felfedezhetjük.
{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 mind a kilenc feladatban kiválóan teljesített!
Sikerült kezelnie a hiányzó idézőjeleket a HTML-attribútumokban,
feldolgozta a JavaScriptet mind a <script>
elemben és az
attribútumokban, és kezelte a tiltott szekvenciát a HTML-kommentárokban.
Mi több, megakadályozta, hogy egy támadó által megadott rosszindulatú linkre kattintva a támadó kódját futtassa. És sikerült kezelni a címkék escapingjét a Vue számára.
Bónusz teszt
Minden sablonkészítő rendszer egyik alapvető képessége a blokkokkal
való munka és a kapcsolódó sablonöröklés. Ezért minden tesztelt
templating rendszernek adunk még egy feladatot. Létrehozunk egy
description
blokkot, amelyet egy HTML-attribútumban fogunk
kiírni. A való világban a blokk definíciója természetesen a gyermek
sablonban, kimenete pedig a szülő sablonban, például az elrendezésben lenne
elhelyezve. Ez csak egy leegyszerűsített forma, de elég ahhoz, hogy
teszteljük az autoescapinget a blokkok kiadásakor. Hogyan teljesítettek?
Twig: sikertelen ❌ blokkok kiadásakor, a karakterek nem megfelelően vannak kikerülve.
{% block description %}
rock n' roll
{% endblock %}
<meta name='description'
content='{{ block('description') }}'>
<meta name='description'
content=' rock n' roll '> ❌
Blade: hiba ❌ blokkok kiadásakor, a karakterek nem megfelelően vannak kikerülve
@section('description')
rock n' roll
@endsection
<meta name='description'
content='@yield('description')'>
<meta name='description'
content=' rock n' roll '> ❌
Latte: átadott ✅ blokkok kiadá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 olyan sok weboldal sebezhető?
Az automatikus szedés az olyan rendszerekben, mint a Twig, a Blade vagy a
Smarty úgy működik, hogy egyszerűen öt karaktert
<>"'&
HTML-egységekkel helyettesít, és nem tesz
különbséget a kontextus között. Ezért csak bizonyos helyzetekben
működik, és minden másban nem. A naiv autoescaping veszélyes funkció,
mert a biztonság hamis érzetét kelti.
Nem meglepő tehát, hogy jelenleg a weboldalak több mint 27%-a rendelkezik kritikus sebezhetőségekkel, főként XSS-sel (forrás: Acunetix Web Vulnerability Report). Hogyan lehet ebből a helyzetből kilábalni? Használjon olyan templating rendszert, amely megkülönbözteti a kontextusokat.
A Latte az egyetlen olyan PHP templating rendszer, amely a sablont nem csak egy karaktersorozatnak érzékeli, hanem érti a HTML-t. Tudja, hogy mik azok a címkék, attribútumok stb. Megkülönbözteti a kontextusokat. És ezért helyesen escapeli a HTML-szövegben, másképp a HTML-tageken belül, másképp a JavaScriptben stb.
A Latte tehát az egyetlen biztonságos templating rendszert képviseli.
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