Kviz: se boste ubranili pred ranljivostjo XSS?
Preizkusite svoje znanje v varnostnem kvizu! Ali lahko preprečite napadalcu prevzem nadzora nad HTML stranjo?

V vseh nalogah boste reševali isto vprašanje: kako pravilno izpisati
spremenljivko $str
v HTML strani, da ne nastane ranljivost XSS. Osnova obrambe je ubežanje
znakov, kar pomeni zamenjavo znakov s posebnim pomenom z ustreznimi
zaporedji. Na primer pri izpisu niza v HTML besedilu, v katerem ima znak
<
poseben pomen (signalizira začetek značke), ga nadomestimo
s HTML entiteto <
in brskalnik pravilno prikaže simbol
<
.
Bodite na pozoru, saj je ranljivost XSS zelo resna. Lahko povzroči, da napadalec prevzame nadzor nad stranjo ali celo uporabniškim računom. Veliko uspeha in naj vam uspe ohraniti HTML stran varno!
Prva trojica vprašanj
Navedite, katere znake in na kakšen način je treba obdelati v prvem, drugem in tretjem primeru:
1) <p><?= $str ?></p>
2) <input value="<?= $str ?>">
3) <input value='<?= $str ?>'>
Če izpis ne bi bil nikakor obdelan, bi postal del prikazane strani. Če bi
napadalec v spremenljivko dobil niz 'foo" onclick="evilCode()'
in
izpis ne bi bil obdelan, bi povzročil, da se ob kliku na element sproži
njegova koda:
$str = 'foo" onclick="evilCode()';
❌ brez obdelave: <input value="foo" onclick="evilCode()">
✅ z obdelavo: <input value="foo" onclick="evilCode()">
Rešitve posameznih primerov:
- znaka
<
in&
predstavljata začetek HTML oznake in entitete, nadomestimo ju z<
in&
- znaka
"
in&
predstavljata konec vrednosti atributa in začetek HTML entitete, nadomestimo ju z"
in&
- znaka
'
in&
predstavljata konec vrednosti atributa in začetek HTML entitete, nadomestimo ju z'
in&
Za vsak pravilen odgovor dobite točko. Seveda lahko v vseh treh primerih nadomestimo z entitetami tudi druge znake, nič ne škodi, vendar ni nujno.
Vprašanje št. 4
Nadaljujemo naprej. Katere znake je treba nadomestiti pri izpisu spremenljivke v tem kontekstu?
<input value=<?= $str ?>>
Rešitev: Kot vidite, tukaj manjkajo narekovaji. Najlažje je preprosto
dodati narekovaje in nato ubežati enako kot v prejšnjem vprašanju. Obstaja
tudi druga rešitev, in sicer nadomestiti v nizu s HTML entitetami presledek
in vse znake, ki imajo poseben pomen znotraj oznake, tj. >
,
/
, =
in nekatere
druge.
Vprašanje št. 5
Zdaj pa postaja bolj zanimivo. Katere znake je treba obdelati v tem kontekstu:
<script>
let foo = '<?= $str ?>';
</script>
Rešitev: Znotraj značke <script>
pravila za ubežanje
znakov določa JavaScript. HTML entitete se tu ne uporabljajo, vendar
velja eno posebno pravilo. Torej katere znake ubežimo? Znotraj JavaScript niza
ubežimo seveda znak '
, ki ga omejuje, in sicer s pomočjo
poševnice, torej ga nadomestimo z \'
. Ker JavaScript ne podpira
večvrstičnih nizov (le kot template
literal), moramo ubežati tudi znake konca vrstic. Vendar pozor, poleg
običajnih znakov \n
in \r
šteje JavaScript za konce
vrstic tudi unicode znake \u2028
in \u2029
, ki jih
moramo prav tako ubežati. In končno omenjeno posebno pravilo: v nizu se ne
sme pojaviti </script
. To se da preprečiti npr. z zamenjavo z
<\/script
.
Če ste to vedeli, čestitamo.
Vprašanje št. 6
Naslednji kontekst izgleda le kot variacija prejšnjega. Kaj mislite, se bo obdelava razlikovala?
<p onclick="foo('<?= $str ?>')"></p>
Rešitev: Spet tu veljajo pravila za ubežanje znakov v JavaScript nizih,
ampak za razliko od prejšnjega konteksta, kjer se s pomočjo HTML entitet ni
ubežalo, se tukaj nasprotno ubeži. Torej najprej ubežimo JavaScript niz
s pomočjo poševnic in nato nadomestimo znake s posebnim pomenom
("
in &
) s HTML entitetami. Pozor, pravilno
zaporedje je pomembno.
Kot vidite, se lahko isti JavaScript literal kodira drugače v elementu
<script>
in drugače v atributu!
Vprašanje št. 7
Vrnimo se iz JavaScripta nazaj v HTML. Katere znake moramo nadomestiti znotraj komentarja in na kakšen način?
<!-- <?= $str ?> -->
Rešitev: znotraj HTML (in XML) komentarja se lahko pojavljajo vsi
tradicionalni posebni znaki, kot so <
, &
,
"
in '
. Prepovedan je, in to vas bo verjetno
presenetilo, par znakov --
. Ubežanje tega zaporedja ni
specificirano, zato je na vas, na kakšen način ga boste nadomestili. Lahko jih
preložite s presledki. Ali pa na primer nadomestite z ==
.
Vprašanje št. 8
Že se bližamo koncu, zato poskusimo spremeniti vprašanje. Poskusite razmisliti, na kaj je treba paziti pri izpisu spremenljivke v tem kontekstu:
<a href="<?= $str ?>">...</a>
Rešitev: poleg ubežanja znakov je pomembno še preveriti, da URL ne vsebuje
nevarne sheme kot javascript:
, ker bi tako sestavljen URL po kliku
poklical napadalčevo kodo.
Vprašanje št. 9
Za konec biser za prave sladokusce. Gre za primer aplikacije, ki uporablja
sodobno JavaScript ogrodje, konkretno Vue. Prav zanima me, ali vam pade na
pamet, na kaj paziti pri izpisu spremenljivke znotraj elementa
#app
:
<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>
Ta koda ustvari Vue aplikacijo, ki se bo izrisovala v element
#app
. Vue vsebino tega elementa razume kot svojo predlogo. In
znotraj predloge interpretira
dvojne zavite oklepaje, ki pomenijo izpis spremenljivke ali klic javascript kode
(npr. {{ foo }}
).
Torej znotraj elementa #app
imata poleg znakov <
in &
še poseben pomen par {{
, ki ga moramo
nadomestiti z drugim ustreznim zaporedjem, da ga Vue ne bi interpretiral kot
svojo značko. Zamenjava s HTML entitetami pa v tem primeru ne pomaga. Kako si
pomagati? Deluje trik: med oklepaja vstavimo prazen HTML komentar
{<!-- -->{
in Vue takšno zaporedje ignorira.
Rezultati kviza
Kako ste se odrezali v kvizu? Koliko pravilnih odgovorov imate? Če ste na vsaj 4 vprašanja odgovorili pravilno, spadate med 8 % najboljših reševalcev – čestitamo!
Vendar je za zagotovitev varnosti vašega spletnega mesta nujno pravilno obdelati izpis v vseh situacijah.
Če vas je presenetilo, koliko različnih kontekstov se lahko pojavi na običajni HTML strani, vedite, da nismo omenili niti približno vseh. To bi bil kviz veliko daljši. Kljub temu vam ni treba biti strokovnjak za ubežanje znakov v vsakem kontekstu, če to zmore vaš sistem predlog.
Pa jih torej preizkusimo.
Kako se obnesejo sistemi predlog?
Vsi sodobni sistemi predlog se ponašajo s funkcijo samodejnega ubežanja znakov, ki samodejno ubeži vse izpisane spremenljivke. Če to počnejo pravilno, je vaše spletno mesto varno. Če to počnejo napačno, je spletno mesto izpostavljeno tveganju ranljivosti XSS z vsemi resnimi posledicami.
Preizkusili bomo priljubljene sisteme predlog iz vprašanj tega kviza, da ugotovimo, kako učinkovito je njihovo samodejno ubežanje znakov. Začenja se dTest sistemov predlog za PHP.
Twig ❌
Prvi na vrsti je sistem predlog Twig
(različica 3.5), ki se najpogosteje uporablja v povezavi z ogrodjem Symfony.
Dali mu bomo nalogo odgovoriti na vsa kvizna vprašanja. Spremenljivka
$str
bo vedno napolnjena z zvitim nizom in pogledali bomo, kako si
bo poradil z njenim izpisom. Rezultate vidite desno. Njegove odgovore in
obnašanje lahko tudi raziskate na igrišču.
{% 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 je padel na šestih od devetih testov!
Na žalost samodejno ubežanje znakov Twiga deluje le v HTML besedilu in atributih, in to še samo takrat, ko so zaprti v narekovaje. Takoj ko narekovaji manjkajo, Twig ne javi nobene napake in ustvari varnostno luknjo XSS.
To je še posebej neprijetno, ker se tako vrednosti atributov zapisujejo v priljubljenih knjižnicah, kot sta React ali Svelte. Programer, ki hkrati uporablja Twig in React, tako povsem naravno lahko pozabi na narekovaje.
Samodejno ubežanje znakov Twiga odpove tudi v vseh ostalih primerih.
V kontekstih (5) in (6) je treba ubežati ročno s pomočjo
{{ str|escape('js') }}
, za druge kontekste Twig funkcije za
ubežanje niti ne ponuja. Nima niti zaščite pred izpisom škodljive povezave
(8) ali podpore za predloge za Vue (9).
Blade ❌❌
Drugi udeleženec je sistem predlog Blade (različica 10.9), ki je tesno integriran z Laravelem in njegovim ekosistemom. Spet bomo preverili njegove sposobnosti na naših kviznih vprašanjih. Njegove odgovore si lahko tudi raziskate na igrišču.
@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>
Blade je padel na šestih od devetih testov!
Rezultat je podoben kot pri Twigu. Spet velja, da samodejno ubežanje znakov
deluje le v HTML besedilu in atributih in le če so zaprti v narekovaje.
Samodejno ubežanje znakov Blade odpove tudi v vseh ostalih primerih.
V kontekstih (5) in (6) je treba ubežati ročno s pomočjo
{{ Js::from($str) }}
. Za druge kontekste Blade funkcije za
ubežanje niti ne ponuja. Nima zaščite pred izpisom škodljive povezave (8)
niti podpore za predloge za Vue (9).
Kar pa je presenetljivo, je odpoved direktive @php
v Blade, kar
povzroča izpis lastne PHP kode neposredno na izhod, kar vidite v zadnji
vrstici.
Smarty ❌❌❌
Zdaj bomo preizkusili najstarejši sistem predlog za PHP, ki je Smarty (različica 4.3). Na veliko
presenečenje ta sistem nima aktivnega samodejnega ubežanja znakov. Morate
torej pri izpisovanju spremenljivk bodisi vsakič navesti filter
{$var|escape}
, bodisi aktivirati samodejno ubežanje znakov HTML.
Informacija o tem je v dokumentaciji precej zapadla.
{$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>
Smarty je padel na šestih od devetih testov!
Rezultat je na prvi pogled podoben kot pri prejšnjih knjižnicah. Smarty
zmore samodejno ubežati znake le v HTML besedilu in atributih, in to le, ko so
vrednosti zaprte v narekovaje. Povsod drugje odpove. V kontekstih (5) in (6)
je treba ubežati ročno s pomočjo {$str|escape:javascript}
.
Vendar je to mogoče le takrat, ko ni aktivno samodejno ubežanje znakov HTML,
sicer se namreč ta ubežanja med seboj tepejo. Smarty so tako z vidika
varnosti popoln polom tega testa.
Latte ✅
Trojico zaključuje sistem predlog Latte (različica 3.0). Preizkusili bomo njegovo samodejno ubežanje znakov. Njegove odgovore in obnašanje si lahko prav tako raziskate na igrišču.
{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 je v vseh devetih nalogah blestel!
Uspelo se je spopasti z manjkajočimi narekovaji pri HTML atributih, uspelo
je obdelati JavaScript tako v elementu <script>
, kot
v atributih in uspelo se je spopasti tudi s prepovedanim zaporedjem v HTML
komentarjih.
Še več, preprečil je situacijo, ko bi klik na podtaknjeno povezavo s strani napadalca lahko sprožil njegovo kodo. In uspelo si je poraditi z ubežanjem značk za Vue.
Bonus test
Ena izmed bistvenih sposobnosti vseh sistemov predlog je delo z bloki in
s tem povezana dednost predlog. Poskusili bomo torej dati vsem testiranim
sistemom predlog še eno nalogo. Ustvarili bomo blok description
,
ki ga bomo izpisali v HTML atributu. V realnem svetu bi se seveda definicija
bloka nahajala v otroški predlogi in njegov izpis v starševski predlogi,
torej na primer layoutu. To je le poenostavljena oblika, ampak zadostuje, da
preizkusimo samodejno ubežanje znakov pri izpisovanju blokov. Kako so se
odrezali?
Twig: padel ❌ pri izpisovanju blokov znakov ne obdeluje
{% block description %}
rock n' roll
{% endblock %}
<meta name='description'
content='{{ block('description') }}'>
<meta name='description'
content=' rock n' roll '> ❌
Blade: padel ❌ pri izpisovanju blokov znakov ne obdeluje
@section('description')
rock n' roll
@endsection
<meta name='description'
content='@yield('description')'>
<meta name='description'
content=' rock n' roll '> ❌
Latte: uspel ✅ pri izpisovanju blokov je korektno obdelal problematične znake
{block description}
rock n' roll
{/block}
<meta name='description'
content='{include description}'>
<meta name='description'
content=' rock n' roll '> ✅
Zakaj je toliko spletnih mest ranljivih?
Samodejno ubežanje znakov v sistemih kot so Twig, Blade ali Smarty deluje
tako, da preprosto nadomešča pet znakov <>"'&
s HTML
entitetami in nikakor ne razlikuje konteksta. Zato deluje le v nekaterih
situacijah in v vseh ostalih odpove. Naivno samodejno ubežanje znakov je
nevarna funkcija, ker ustvarja lažen občutek varnosti.
Zato ni presenetljivo, da ima trenutno več kot 27 % spletnih mest kritične ranljivosti, predvsem XSS (vir: Acunetix Web Vulnerability Report). Kako iz tega ven? Uporabiti sistem predlog, ki razlikuje kontekste.
Latte je edini sistem predlog v PHP, ki ne dojema predloge le kot niz znakov, ampak razume HTML. Razume, kaj so značke, atributi itd. Razlikuje kontekste. In zato pravilno ubeži v HTML besedilu, drugače znotraj HTML značke, drugače znotraj JavaScripta itd.
Latte tako predstavlja edini varen sistem predlog.
Poleg tega zaradi svojega razumevanja HTML ponuja čudovite n:atribute, ki jih uporabniki obožujejo:
<ul n:if="$menu">
<li n:foreach="$menu->getItems() as $item">{$item->title}</li>
</ul>
Če želite oddati komentar, se prijavite