Kvíz: XSS sebezhetőség ellen?

2 éve A címről David Grudl  

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

Megoldások az egyes példákhoz:

  1. a < és a & karakterek egy HTML tag és egy entitás kezdetét jelentik; helyettesítsük őket a &lt; és a karakterekkel. &amp;
  2. 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 &quot; és a &amp;
  3. 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 &apos; és a karakterekkel. &amp;

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>&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>

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>&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>

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>&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 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&apos; 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>