Kviz: Ali se lahko zaščitite pred ranljivostjo XSS?
Preverite svoje znanje in varnostne veščine v tem kvizu! Ali lahko napadalcu preprečite, da bi prevzel nadzor nad stranjo HTML?
V vseh nalogah boste obravnavali isto vprašanje: kako pravilno prikazati
spremenljivko $str
na strani HTML, ne da bi ustvarili ranljivost XSS. Osnova
obrambe je escaping, kar pomeni zamenjavo znakov s posebnim pomenom
z ustreznimi zaporedji. Na primer, pri izpisu niza v besedilo HTML, v katerem
ima znak <
poseben pomen (označuje začetek oznake), ga
nadomestimo z entiteto HTML <
, brskalnik pa pravilno
prikaže znak <
.
Bodite pozorni, saj je ranljivost XSS zelo resna. Zaradi nje lahko napadalec prevzame nadzor nad stranjo ali celo uporabniškim računom. Veliko sreče in naj vam uspe ohraniti stran HTML varno!
Prva trojica vprašanj
Določite, katere znake je treba obdelati in kako v prvem, drugem in tretjem primeru:
1) <p><?= $str ?></p>
2) <input value="<?= $str ?>">
3) <input value='<?= $str ?>'>
Če izpis ne bi bil obdelan na noben način, bi postal del prikazane strani.
Če bi napadalcu uspelo vstaviti niz 'foo" onclick="evilCode()'
v spremenljivko in izpis ne bi bil obdelan, bi se ob kliku na element izvršila
njegova koda:
$str = 'foo" onclick="evilCode()'
❌ not treated: <input value="foo" onclick="evilCode()">
✅ treated: <input value="foo" onclick="evilCode()">
Rešitve za vsak primer:
- znaka
<
in&
predstavljata začetek oznake HTML in entitete; zamenjajte ju z<
in&
- znaka
"
in&
predstavljata konec vrednosti atributa in začetek entitete HTML; nadomestite ju z"
in&
- znaka
'
in&
predstavljata konec vrednosti atributa in začetek entitete HTML; nadomestite ju z'
in&
Za vsak pravilen odgovor prejmete točko. Seveda lahko v vseh treh primerih z entitetami zamenjate tudi druge znake; to ne škodi, vendar ni nujno.
Vprašanje št. 4
Če nadaljujemo, katere znake je treba nadomestiti pri prikazu spremenljivke v tem kontekstu?
<input value=<?= $str ?>>
Rešitev: Kot lahko vidite, tu manjkajo narekovaji. Najlažji način je, da
preprosto dodate narekovaje in nato pobegnete kot v prejšnjem vprašanju.
Obstaja tudi druga rešitev, in sicer tako, da presledke in vse znake, ki imajo
poseben pomen znotraj oznake, kot so >
, /
,
=
in nekateri
drugi, nadomestite z entitetami HTML.
Vprašanje št. 5
Zdaj pa je še bolj zanimivo. Katere like je treba obravnavati v tem kontekstu:
<script>
let foo = '<?= $str ?>';
</script>
Rešitev: Znotraj <script>
so pravila escapiranja
določena v JavaScriptu. Entitete HTML se tu ne uporabljajo, vendar obstaja eno
posebno pravilo. Katerim znakom se izognemo? Znotraj niza JavaScript seveda
pobegnemo znaku '
, ki ga razmejuje, z uporabo povratne poševnice,
ki jo nadomestimo z \'
. Ker JavaScript ne podpira večvrstičnih
nizov (razen kot literale
za predloge), moramo pobegniti tudi znakom novih vrstic. Vendar se
zavedajte, da JavaScript poleg običajnih znakov \n
in
\r
za znak nove vrstice šteje tudi znaka Unicode
\u2028
in \u2029
, ki ju moramo prav tako pobegniti. Na
koncu še omenjeno posebno pravilo: niz ne sme vsebovati znakov
</script
. To lahko preprečimo tako, da ga na primer nadomestimo
z znakom <\/script
.
Če ste to vedeli, vam čestitamo.
Vprašanje št. 6
Zdi se, da je naslednji kontekst le različica prejšnjega. Ali menite, da bo obravnava drugačna?
<p onclick="foo('<?= $str ?>')"></p>
Rešitev: Za razliko od prejšnjega konteksta, kjer entitete HTML niso bile
escapirane, so tukaj escapirane. Najprej se torej izognemo nizu JavaScript
s povratnimi lomkami, nato pa posebne znake ("
in
&
) nadomestimo z entitetami HTML. Pazite, pomemben je pravilen
vrstni red.
Kot lahko vidite, je lahko isti literal JavaScript različno kodiran v
<script>
elementu in različno v atributu!
Vprašanje št. 7
Vrnimo se od JavaScripta nazaj k HTML. Katere znake moramo zamenjati znotraj komentarja in kako?
<!-- <?= $str ?> -->
Rešitev: V komentarju HTML (in XML) se lahko pojavijo vsi tradicionalni
posebni znaki, kot so <
, &
, "
in
'
. Prepovedan pa je par znakov --
, kar vas bo morda
presenetilo. Izogibanje temu zaporedju ni določeno, zato je od vas odvisno,
kako ga boste nadomestili. Lahko jih prepletete s presledki. Lahko pa ju na
primer nadomestite s ==
.
Vprašanje št. 8
Bližamo se koncu, zato poskusite vprašanje spremeniti. Poskusite razmisliti o tem, na kaj morate biti pozorni pri tiskanju spremenljivke v tem kontekstu:
<a href="<?= $str ?>">...</a>
Rešitev: Poleg eskapiranja je pomembno tudi preveriti, ali naslov URL ne
vsebuje nevarne sheme, kot je javascript:
, saj bi tako sestavljen
naslov URL ob kliku izvršil napadalčevo kodo.
Vprašanje št. 9
Končno poslastica za prave poznavalce. To je primer aplikacije, ki uporablja
sodobno ogrodje JavaScript, zlasti Vue. Poglejmo, ali lahko ugotovite, na kaj je
treba biti pozoren pri izpisovanju 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 aplikacijo Vue, ki se bo izrisala v elementu
#app
. Vue vsebino tega elementa interpretira kot svojo predlogo.
V predlogi pa interpretira
dvojne oglate oklepaje, ki predstavljajo izpis spremenljivke ali klic kode
JavaScript (npr, {{ foo }}
).
Tako ima v elementu #app
poleg znakov <
in
&
poseben pomen tudi par {{
, ki ga moramo
nadomestiti z drugim ustreznim zaporedjem, da ga Vue ne bi razlagal kot svojo
oznako. Zamenjava z entitetami HTML v tem primeru ne pomaga. Kako se tega
lotiti? Obstaja trik: med oklepaje vstavite prazen komentar HTML
{<!-- -->{
in Vue ne bo upošteval tega zaporedja.
Rezultati kviza
Kako ste se odrezali v kvizu? Koliko pravilnih odgovorov imate? Če ste pravilno odgovorili na vsaj 4 vprašanja, ste med 8 % najboljših reševalcev – čestitamo!
Zagotavljanje varnosti vašega spletnega mesta pa zahteva pravilno ravnanje z izhodnimi podatki v vseh situacijah.
Če vas je presenetilo, koliko različnih kontekstov se lahko pojavi na tipični strani HTML, vedite, da še zdaleč nismo omenili vseh. S tem bi bil kviz veliko daljši. Kljub temu vam ni treba biti strokovnjak za pobege v vseh kontekstih, če to zmore vaš sistem za oblikovanje predlog.
Zato jih preizkusimo.
Kako se obnesejo šablonski sistemi?
Vsi sodobni sistemi za oblikovanje predlog imajo funkcijo autoescaping, ki samodejno izriše vse izpisane spremenljivke. Če to storijo pravilno, je vaše spletno mesto varno. Če to počnejo slabo, je spletno mesto izpostavljeno tveganju ranljivosti XSS z vsemi resnimi posledicami.
Preizkusili bomo priljubljene sisteme za šabloniranje iz vprašanj v tem kvizu, da bi ugotovili učinkovitost njihovega samodejnega izrivanja. Naj se pregled sistemov za oblikovanje predlog PHP začne.
Twig ❌
Prvi je sistem za oblikovanje predlog Twig (različica 3.5), ki se najpogosteje
uporablja v povezavi z ogrodjem Symfony. Zadolžili ga bomo, da odgovori na
vsa vprašanja kviza. Spremenljivka $str
bo vedno napolnjena
z zapletenim nizom, mi pa si bomo ogledali, kako ravna s svojim izhodom.
Rezultate si lahko ogledate na desni strani. Njegove odgovore in obnašanje
lahko raziščete tudi 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 bil neuspešen pri šestih od devetih testov!
Na žalost Twigovo samodejno izločanje deluje le pri besedilu HTML in atributih, pa še to le, če so zaprti v narekovaje. Takoj ko narekovajev ni, Twig ne sporoči nobene napake in ustvari varnostno luknjo XSS.
To je še posebej neprijetno, ker so tako zapisane vrednosti atributov v priljubljenih knjižnicah, kot sta React ali Svelte. Programer, ki uporablja tako Twig kot React, lahko povsem naravno pozabi na narekovaje.
Twigova funkcija samodejnega izpisa narekovajev je neuspešna tudi v vseh
drugih primerih. V primerih (5) in (6) je potrebno ročno izpisovanje
z uporabo {{ str|escape('js') }}
, medtem ko za druge kontekste
Twig sploh ne ponuja funkcije escapiranja. Prav tako nima zaščite pred izpisom
zlonamerne povezave (8) ali podpore za predloge Vue (9).
Blade ❌❌
Drugi udeleženec je sistem za oblikovanje predlog Blade (različica 10.9), ki je tesno povezan z Laravelom in njegovim ekosistemom. Tudi tokrat bomo njegove zmožnosti preizkusili na naših kviznih vprašanjih. Njegove odgovore lahko raziščete tudi 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>
Na šestih od devetih testov rezilo ni bilo uspešno!
Rezultat je podoben kot pri Twigu. Ponovno velja, da samodejno izločanje
deluje le v besedilu HTML in atributih ter le, če so ti zaprti v narekovaje.
Bladeovo samodejno izpisovanje je neuspešno tudi v vseh drugih primerih.
V primerih (5) in (6) je potrebno ročno eskapiranje z uporabo
{{ Js::from($str) }}
. Za druge kontekste Blade sploh ne ponuja
funkcije escapiranja. Prav tako nima zaščite pred izpisom zlonamerne povezave
(8) ali podpore za predloge Vue (9).
Preseneča pa neuspešnost direktive @php
v programu Blade, ki
povzroči izpis lastne kode PHP neposredno na izhod, kot je razvidno iz zadnje
vrstice.
Latte ✅
Trio zaključuje sistem za oblikovanje predlog Latte (različica 3.0). Preizkusili bomo njegovo samodejno izpisovanje. Njegove odgovore in obnašanje lahko raziščete tudi 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 bil odličen pri vseh devetih nalogah!
Obvladal je manjkajoče narekovaje v atributih HTML, obdelal JavaScript tako
v <script>
v elementu in v atributih ter obravnaval
prepovedano zaporedje v komentarjih HTML.
Poleg tega je preprečil situacijo, v kateri bi klik na zlonamerno povezavo, ki jo je zagotovil napadalec, lahko izvršil njegovo kodo. Poleg tega je za sistem Vue obvladal pobege oznak.
Dodatni test
Ena od bistvenih zmožnosti vseh sistemov za oblikovanje predlog je delo
z bloki in s tem povezano dedovanjem predlog. Zato bomo vsem preizkušenim
sistemom za oblikovanje predlog naložili še eno nalogo. Ustvarili bomo blok
description
, ki ga bomo izpisali v atribut HTML. V resničnem
svetu bi se definicija bloka seveda nahajala v podrejeni predlogi, njegov izpis
pa v nadrejeni predlogi, na primer v postavitvi. To je le poenostavljena
oblika, vendar je dovolj za preizkus samodejnega izpisa pri izpisovanju blokov.
Kako so se obnesle?
Twig: neuspešno ❌ pri izpisovanju blokov, znaki niso pravilno escapirani
{% block description %}
rock n' roll
{% endblock %}
<meta name='description'
content='{{ block('description') }}'>
<meta name='description'
content=' rock n' roll '> ❌
Blade: ni uspelo ❌ pri izpisovanju blokov, znaki niso pravilno escaped
@section('description')
rock n' roll
@endsection
<meta name='description'
content='@yield('description')'>
<meta name='description'
content=' rock n' roll '> ❌
Latte: posredovano ✅ pri izpisovanju blokov, pravilno je obravnaval 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 izpisovanje v sistemih, kot so Twig, Blade ali Smarty, deluje
tako, da preprosto nadomesti pet znakov <>"'&
z entitetami HTML in ne razlikuje konteksta. Zato deluje le v nekaterih
primerih, v vseh drugih pa je neuspešno. Naivno samodejno izpisovanje je
nevarna funkcija, saj 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 se rešiti iz te situacije? Uporabite sistem za oblikovanje predlog, ki razlikuje kontekste.
Latte je edini sistem za oblikovanje predlog PHP, ki predloge ne dojema le kot niz znakov, temveč razume HTML. Ve, kaj so oznake, atributi itd. Razlikuje kontekste. Zato pravilno eskapira v besedilu HTML, drugače znotraj oznak HTML, drugače znotraj JavaScript itd.
Latte tako predstavlja edini varni sistem za oblikovanje 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