Quiz: Vă puteți apăra împotriva vulnerabilității XSS?
Testați-vă cunoștințele și abilitățile de securitate în acest test! Puteți împiedica un atacator să preia controlul unei pagini HTML?
În toate sarcinile, veți aborda aceeași întrebare: cum să afișați în
mod corespunzător variabila $str
într-o pagină HTML fără a
crea o vulnerabilitate
XSS. Baza apărării este escaping, ceea ce înseamnă înlocuirea
caracterelor cu semnificații speciale cu secvențe corespunzătoare. De
exemplu, la ieșirea unui șir de caractere în text HTML, în care caracterul
<
are o semnificație specială (indică începutul unei
etichete), îl înlocuim cu entitatea HTML <
, iar browserul
afișează corect simbolul <
.
Fiți vigilenți, deoarece vulnerabilitatea XSS este foarte gravă. Ea poate determina un atacator să preia controlul unei pagini sau chiar al contului unui utilizator. Mult succes și să reușiți să păstrați pagina HTML în siguranță!
Primul trio de întrebări
Precizați ce caractere trebuie să fie tratate și cum în primul, al doilea și al treilea exemplu:
1) <p><?= $str ?></p>
2) <input value="<?= $str ?>">
3) <input value='<?= $str ?>'>
Dacă ieșirea nu ar fi tratată în niciun fel, ar deveni parte a paginii
afișate. Dacă un atacator reușește să introducă șirul de caractere
'foo" onclick="evilCode()'
într-o variabilă, iar ieșirea nu
este tratată, codul său va fi executat atunci când se face clic pe
element:
$str = 'foo" onclick="evilCode()'
❌ not treated: <input value="foo" onclick="evilCode()">
✅ treated: <input value="foo" onclick="evilCode()">
Soluții pentru fiecare exemplu:
- caracterele
<
și&
reprezintă începutul unei etichete și entități HTML; înlocuiți-le cu<
și&
- caracterele
"
și&
reprezintă sfârșitul unei valori de atribut și începutul unei entități HTML; înlocuiți-le cu"
și&
- caracterele
'
și&
reprezintă sfârșitul unei valori de atribut și începutul unei entități HTML; înlocuiți-le cu'
și&
Primiți un punct pentru fiecare răspuns corect. Bineînțeles, în toate cele trei cazuri, puteți înlocui și alte caractere cu entități; acest lucru nu provoacă niciun rău, dar nu este necesar.
Întrebarea nr. 4
Mergând mai departe, ce caractere trebuie înlocuite la afișarea unei variabile în acest context?
<input value=<?= $str ?>>
Soluție: După cum puteți vedea, aici lipsesc ghilimelele. Cel mai simplu
este să adăugați pur și simplu ghilimelele și apoi să scăpați ca în
întrebarea anterioară. Există, de asemenea, o a doua soluție, care constă
în înlocuirea spațiilor și a tuturor caracterelor care au o semnificație
specială în interiorul unei etichete, cum ar fi >
,
/
, =
, și unele
altele cu entități HTML.
Întrebarea nr. 5
Acum devine mai interesant. Ce personaje trebuie tratate în acest context:
<script>
let foo = '<?= $str ?>';
</script>
Soluția: În interiorul <script>
regulile de scăpare
sunt stabilite de JavaScript. Entitățile HTML nu sunt utilizate aici, dar
există o regulă specială. Deci, ce caractere scăpăm? În interiorul
șirului JavaScript, scăpăm în mod natural caracterul '
care îl
delimitează, folosind o backslash, înlocuindu-l cu \'
. Deoarece
JavaScript nu acceptă șiruri de caractere cu mai multe rânduri (cu excepția
șabloanelor
literale), trebuie, de asemenea, să scăpăm de caracterele newline. Cu
toate acestea, rețineți că, pe lângă caracterele obișnuite \n
și \r
, JavaScript consideră, de asemenea, caracterele Unicode
\u2028
și \u2029
ca fiind caractere newline, pe care
trebuie să le scăpăm și noi. În cele din urmă, regula specială
menționată: șirul nu trebuie să conțină </script
. Acest
lucru poate fi evitat, de exemplu, prin înlocuirea acestuia cu
<\/script
.
Dacă știați acest lucru, vă felicit.
Întrebarea nr. 6
Următorul context pare a fi doar o variantă a celui precedent. Credeți că tratamentul va fi diferit?
<p onclick="foo('<?= $str ?>')"></p>
Soluție: Din nou, aici se aplică regulile de scăpare pentru șirurile
JavaScript, dar, spre deosebire de contextul anterior, în care entitățile
HTML nu au fost scăpate, aici sunt scăpate. Așadar, mai întâi, scăpăm
șirul JavaScript folosind backslash-uri și apoi înlocuim caracterele speciale
("
și &
) cu entități HTML. Fiți atenți,
ordinea corectă este importantă.
După cum puteți vedea, același literal JavaScript poate fi codificat
diferit într-un fișier <script>
element și în mod diferit
într-un atribut!
Întrebarea nr. 7
Să ne întoarcem de la JavaScript înapoi la HTML. Ce caractere trebuie să înlocuim în interiorul comentariului și cum?
<!-- <?= $str ?> -->
Soluția: "Cum să înlocuim caracterele din comentarii? În interiorul unui
comentariu HTML (și XML), pot apărea toate caracterele speciale tradiționale,
cum ar fi <
, &
, "
și
'
. Ceea ce este interzis, și acest lucru vă poate surprinde, este
perechea de caractere --
. Nu este specificată evitarea acestei
secvențe, așa că depinde de dumneavoastră cum să o înlocuiți. Puteți
să le intercalați cu spații. Sau, de exemplu, să le înlocuiți cu
==
.
Întrebarea nr. 8
Ne apropiem de final, așa că să încercăm să variem întrebarea. Încercați să vă gândiți la ceea ce trebuie să aveți grijă atunci când imprimați o variabilă în acest context:
<a href="<?= $str ?>">...</a>
Soluție: Pe lângă scăparea, este important să verificați, de asemenea,
dacă URL-ul nu conține o schemă periculoasă, cum ar fi
javascript:
, deoarece un URL compus astfel ar executa codul
atacatorului atunci când se face clic pe el.
Întrebarea nr. 9
În sfârșit, un răsfăț pentru adevărații cunoscători. Acesta este un
exemplu de aplicație care utilizează un cadru JavaScript modern, mai exact
Vue. Să vedem dacă vă puteți da seama la ce trebuie să fiți atenți atunci
când imprimați o variabilă în interiorul elementului #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>
Acest cod creează o aplicație Vue care va fi redată în elementul
#app
. Vue interpretează conținutul acestui element ca fiind
șablonul său. Iar în cadrul șablonului, acesta interpretează
acoladele duble, care reprezintă ieșirea variabilelor sau apelarea codului
JavaScript (de ex, {{ foo }}
).
Așadar, în cadrul elementului #app
, pe lângă caracterele
<
și &
, perechea {{
are, de
asemenea, o semnificație specială, pe care trebuie să o înlocuim cu
o altă secvență adecvată pentru a împiedica Vue să o interpreteze ca
fiind propria etichetă. Înlocuirea cu entități HTML nu ajută în acest caz.
Cum să ne ocupăm de aceasta? Există un truc: inserați un comentariu HTML gol
între paranteze {<!-- -->{
, iar Vue ignoră această
secvență.
Rezultatele testului
Cum v-ați descurcat la acest test? Câte răspunsuri corecte aveți? Dacă ați răspuns corect la cel puțin 4 întrebări, vă aflați printre primii 8% de rezolvatori – felicitări!
Cu toate acestea, pentru a asigura securitatea site-ului dvs. web este necesar să gestionați în mod corespunzător ieșirea în toate situațiile.
Dacă ați fost surprins de cât de multe contexte diferite pot apărea pe o pagină HTML tipică, să știți că nu le-am menționat nici pe departe pe toate. Acest lucru ar face ca testul să fie mult mai lung. Cu toate acestea, nu trebuie să fiți un expert în a evada în fiecare context dacă sistemul dvs. de modelare se poate descurca.
Așadar, haideți să le testăm.
Cum se comportă sistemele de modelare?
Toate sistemele moderne de creare de șabloane dispun de o funcție de autoescaping care scapă automat toate variabilele afișate. Dacă o fac corect, site-ul dumneavoastră este în siguranță. Dacă nu o fac bine, site-ul este expus riscului de vulnerabilitate XSS cu toate consecințele sale grave.
Vom testa sistemele de modelare populare din întrebările din acest test pentru a determina eficiența auto-escapării lor. Să înceapă examinarea sistemelor de modelare PHP.
Twig ❌
Primul este sistemul de modelare Twig
(versiunea 3.5), cel mai des utilizat împreună cu cadrul Symfony. Îl vom
însărcina să răspundă la toate întrebările testului. Variabila
$str
va fi întotdeauna completată cu un șir de caractere
înșelător și vom vedea cum își gestionează rezultatul. Puteți vedea
rezultatele în dreapta. De asemenea, puteți explora răspunsurile și
comportamentul său pe
terenul de joacă.
{% 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 a picat la șase din nouă teste!
Din nefericire, scăparea automată a Twig funcționează numai în textul HTML și în atributele HTML, și chiar și atunci numai atunci când acestea sunt incluse între ghilimele. În momentul în care ghilimelele lipsesc, Twig nu raportează nicio eroare și creează o gaură de securitate XSS.
Acest lucru este deosebit de neplăcut, deoarece acesta este modul în care sunt scrise valorile atributelor în biblioteci populare precum React sau Svelte. Un programator care utilizează atât Twig, cât și React poate uita în mod natural de ghilimele.
Autodeschiderea automată a Twig eșuează, de asemenea, în toate celelalte
exemple. În contextele (5) și (6), este necesară o scăpare manuală
folosind {{ str|escape('js') }}
, în timp ce pentru alte contexte,
Twig nici măcar nu oferă o funcție de scăpare. De asemenea, îi lipsește
protecția împotriva imprimării unui link malițios (8) sau suportul pentru
șabloanele Vue (9).
Blade ❌❌
Cel de-al doilea participant este sistemul de modelare Blade (versiunea 10.9), care este strâns integrat cu Laravel și cu ecosistemul său. Din nou, îi vom testa abilitățile la întrebările testului nostru. De asemenea, puteți explora răspunsurile sale pe terenul de joacă.
@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>
Lama a eșuat în șase din nouă teste!
Rezultatul este similar cu Twig. Din nou, scăparea automată funcționează
numai în text și atribute HTML și numai dacă acestea sunt incluse între
ghilimele. De asemenea, funcția de autoescaping a lui Blade eșuează în toate
celelalte exemple. În contextele (5) și (6), este nevoie de escaping manual
folosind {{ Js::from($str) }}
. Pentru alte contexte, Blade nici
măcar nu oferă o funcție de scăpare. De asemenea, îi lipsește protecția
împotriva imprimării unui link malițios (8) sau suportul pentru șabloanele
Vue (9).
Cu toate acestea, ceea ce este surprinzător este eșecul directivei
@php
din Blade, care determină ieșirea propriului cod PHP direct
la ieșire, așa cum se vede în ultima linie.
Latte ✅
Trio-ul este încheiat de sistemul de modelare Latte (versiunea 3.0). Vom testa autodeschiderea sa. De asemenea, puteți explora răspunsurile și comportamentul său pe terenul de joacă.
{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 a excelat în toate cele nouă sarcini!
A reușit să gestioneze ghilimelele lipsă din atributele HTML, a procesat
JavaScript atât în <script>
cât și în atribute, și a
tratat secvența interzisă din comentariile HTML.
Mai mult, a prevenit o situație în care, dacă se dădea click pe un link malițios furnizat de un atacator, acesta putea executa codul său. Și a reușit să gestioneze scăparea etichetelor pentru Vue.
Test bonus
Una dintre capacitățile esențiale ale tuturor sistemelor de modelare este
lucrul cu blocurile și moștenirea șablonului aferent. Prin urmare, vom da
tuturor sistemelor de creare de șabloane testate încă o sarcină. Vom crea
un bloc description
, pe care îl vom imprima într-un atribut HTML.
În lumea reală, definiția blocului ar fi, bineînțeles, localizată în
șablonul copil, iar ieșirea sa în șablonul părinte, cum ar fi layout-ul.
Aceasta este doar o formă simplificată, dar este suficientă pentru a testa
autodeschiderea la ieșirea blocurilor. Cum au funcționat?
Twig: a eșuat ❌ la ieșirea blocurilor, caracterele nu sunt evadate corespunzător
{% block description %}
rock n' roll
{% endblock %}
<meta name='description'
content='{{ block('description') }}'>
<meta name='description'
content=' rock n' roll '> ❌
Blade: a eșuat ❌ la ieșirea blocurilor, caracterele nu sunt scăpate corect
@section('description')
rock n' roll
@endsection
<meta name='description'
content='@yield('description')'>
<meta name='description'
content=' rock n' roll '> ❌
Latte: a trecut ✅ la ieșirea blocurilor, a gestionat corect caracterele problematice
{block description}
rock n' roll
{/block}
<meta name='description'
content='{include description}'>
<meta name='description'
content=' rock n' roll '> ✅
De ce sunt atât de multe site-uri web vulnerabile?
Autodeschiderea automată în sisteme precum Twig, Blade sau Smarty
funcționează prin simpla înlocuire a cinci caractere
<>"'&
cu entități HTML și nu face distincție de
context. Prin urmare, funcționează doar în anumite situații și eșuează
în toate celelalte. Autoescaparea naivă este o caracteristică
periculoasă, deoarece creează un fals sentiment de securitate.
Prin urmare, nu este surprinzător faptul că, în prezent, peste 27 % dintre site-urile web prezintă vulnerabilități critice, în principal XSS (sursa: Acunetix Web Vulnerability Report). Cum să ieșiți din această situație? Folosiți un sistem de șabloane care să distingă contextele.
Latte este singurul sistem de șabloane PHP care nu percepe un șablon ca pe un simplu șir de caractere, ci înțelege HTML. Știe ce sunt etichetele, atributele etc. Distinge contextele. Și, prin urmare, evadează corect în textul HTML, în mod diferit în interiorul tag-urilor HTML, în mod diferit în JavaScript etc.
Latte reprezintă astfel singurul sistem de creare de template-uri sigur.
În plus, datorită înțelegerii HTML, oferă minunatele n:attributes, pe care utilizatorii le adoră:
<ul n:if="$menu">
<li n:foreach="$menu->getItems() as $item">{$item->title}</li>
</ul>
Pentru a trimite un comentariu, vă rugăm să vă conectați