XSS: amikor az adat kóddá válik
Célok
A lecke végére el tudod magyarázni az XSS lényegét, meg tudod különböztetni a tárolt, a visszatükrözött és a DOM-alapú változatot, és ismered a védelem rétegeit: kimeneti kódolás, szöveg- és HTML-API-k, tisztítás, Content Security Policy.
Miért veszélyes egy hozzászólás?
A Kurzuspontban a hallgatók hozzászólást fűzhetnek egy kurzushoz. Ha az alkalmazás a szöveget ellenőrizetlenül HTML-ként építi be az oldalba, a böngésző a támadó által megadott részt kódként értelmezi. Az MDN szerint az XSS lehetővé teszi a támadónak, hogy kódot futtasson a sérülékeny oldal saját eredetében, ahol a böngésző úgy kezeli, mintha az oldal szerzője írta volna: olvashatja az oldalt és a helyi tárhelyet, és a felhasználó hitelesítő adataival kérhet a szervertől. A sérülés oka mindig az, hogy a szöveg és a futtatható tartalom határa elveszett.
Három változat
| Változat |
Hol kerül a rosszindulatú tartalom az oldalba? |
Példa |
| Tárolt (stored) |
A szerver eltárolja, és később másoknak is megjeleníti. |
Hozzászólás, amit minden látogató lát. |
| Visszatükrözött (reflected) |
A kérésből (például URL-paraméterből) vett adat kerül a válaszba. |
Keresőoldal, amely kiírja a keresőszót. |
| DOM-alapú (DOM-based) |
A kliensoldali kód ír veszélyes módon adatot a DOM-ba. |
innerHTML fed a location.search-ből. |
flowchart LR
A[Támadó által adott szöveg] --> S[Alkalmazás]
S -->|veszélyes HTML-beillesztés| B[Áldozat böngészője]
B -->|kód fut a sérülékeny eredetben| R[Bejelentkezett művelet]
Kipróbálva: kimeneti kódolás nélkül és azzal
A hozzászólás szövege egy támadó üzenete. Egy egyszerű szerveroldali sablon (JavaScript sablonszöveg) két módon építi a HTML-t:
const komment = '<img src=x onerror="alert(document.cookie)">';
const veszelyes = c => `<p class="komment">${c}</p>`;
const esc = s => s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')
.replace(/"/g,'"').replace(/'/g,''');
const biztonsagos = c => `<p class="komment">${esc(c)}</p>`;
veszélyes : <p class="komment"><img src=x onerror="alert(document.cookie)"></p>
biztonságos : <p class="komment"><img src=x onerror="alert(document.cookie)"></p>
Az első kimenetben egy img elem van, amelynek hibakezelője (onerror) kódot futtat: a böngésző ezt az oldal részeként értelmezi. A második kimenetben a <, > és " karakterek HTML-entitássá alakultak, ezért a böngésző szövegként jeleníti meg, nem elemként.
A kimeneti kódolás (output encoding) kontextusfüggő: más szabályok kellenek a HTML-szövegben, egy attribútumban vagy egy JavaScript-ben. Attribútumban egy idézőjel is kiléphet az értékből:
escape nélkül: <input value="" onmouseover="alert(1)">
escape-pel : <input value="" onmouseover="alert(1)">
Az MDN ezért azt tanácsolja, hogy kimeneti kódolást végző sablonmotort használjunk, tartsuk szem előtt a kontextust (attribútumoknál az értéket mindig idézőjelek közé tegyük), és a szkript- és stíluskörnyezeteket tekintsük szinte mindig veszélyesnek. A fenti esc függvény oktatási példa: valódi alkalmazásban bevált sablonmotort vagy könyvtárat kell használni, nem saját megoldást.
A védelem rétegei
-
Kimeneti kódolás: a sablonmotor alapértelmezésben kódoljon, és a kontextust ismerjük.
-
Szöveg-API-k a HTML-API-k helyett: ha csak szövegre van szükség, ne HTML-ként értelmezzük. Böngészőben az innerHTML HTML-ként értelmezi a szöveget, a textContent szövegként illeszti be:
welcome.innerHTML = `Üdv, ${nev}!`;
welcome.textContent = `Üdv, ${nev}!`;
-
Tisztítás (sanitization): ha valóban HTML-t kell fogadni (formázott hozzászólás), ismert, jó hírű tisztító könyvtárat kell használni (az MDN a DOMPurify-t említi), saját szűrőt nem.
-
Content Security Policy (CSP): a böngészőnek küldött szabályrendszer, amely korlátozza, honnan tölthet be szkriptet az oldal. Az MDN szerint elsődleges célja az XSS elleni védelem: akkor is megakadályozhatja az idegen szkript futtatását, ha a tisztítás elbukott.
Content-Security-Policy: script-src 'nonce-{VÉLETLEN}'; object-src 'none'; base-uri 'none'
A nonce egy válaszonként új véletlen érték, amely a fejlécben és az engedélyezett <script nonce="..."> elemekben egyezik; a beszúrt idegen szkript nem ismeri. A CSP letiltja a beágyazott eseménykezelőket (onclick=...), a javascript: címeket és az eval() jellegű futtatást is. A CSP nem pótolja a bemenet helyes kezelését, hanem mögöttes védelmi réteg.
-
Trusted Types: az MDN szerint a CSP require-trusted-types-for 'script' direktívájával kikényszeríthető, hogy veszélyes helyekre (például innerHTML) csak egy szabályzaton átment érték kerülhessen. Ez a megoldás nem tisztító, hanem a tisztítás kikényszerítésére való keretrendszer, és a böngészőtámogatása korlátozott.
Mit tud a támadó, ha mégis bejut?
A támadó kódja a sérülékeny oldal eredetében fut, ezért a bejelentkezett felhasználó nevében kérhet a szervertől. A HttpOnly cookie-t (7.2) a szkript közvetlenül nem olvashatja, de ettől még a bejelentkezett böngésző nevében érzékeny műveleteket kezdeményezhet, és a localStorage tartalmához is hozzáférhet (7.3). Az XSS ráadásul a legtöbb CSRF-védelmet (8.4) is meg tudja kerülni, mert a kérés a saját eredetből indul. A HttpOnly tehát enyhít, de nem pótolja az XSS megelőzését.
Végigvezetett példa: hozzászólás a Kurzuspontban
| Lépés |
Döntés |
| A hozzászólás szövegét a szerver eltárolja |
Nyers szövegként, változtatás nélkül (nem „tisztítva” tároljuk, hanem megjelenítéskor kódolunk). |
| Megjelenítéskor |
A sablonmotor kódol (HTML-kontextus); a kliens textContent-et használ. |
| Formázott hozzászólás kell |
Tisztító könyvtár a megengedett elemek listájával. |
| Tartalék |
CSP nonce-szal, idegen szkript tiltásával. |
| Süti |
HttpOnly; Secure; SameSite. |
Egy támadó hozzászólása így szövegként jelenik meg, nem elemként, és ha mégis hiba csúszik be, a CSP megakadályozza a szkript futtatását.
Gyakori tévhitek
| Állítás |
Pontosítás |
„A HttpOnly minden XSS-kárt megakadályoz.” |
A cookie olvasását korlátozza; a sérült oldalon futó kód más műveletekre képes. |
„Elég a <script> szöveget kiszűrni.” |
Sok más út van (eseménykezelő attribútumok, javascript: címek); a kontextusfüggő kódolás kell. |
| „A HTTPS véd az XSS ellen.” |
A szállítást védi, nem az alkalmazás hibáját (4.3). |
| „A CSP elég, ha van.” |
Mögöttes védelem; a helyes kimenetkezelést nem pótolja. |
| „A bemenetet kell tisztítani tároláskor.” |
A kódolás a kimenet kontextusától függ; megjelenítéskor kell alkalmazni. |
Röviden
- Az XSS-nél adatból kód lesz: a támadó szkriptje a sérülékeny oldal eredetében fut.
- Változatok: tárolt, visszatükrözött, DOM-alapú.
- Védelem: kontextusfüggő kimeneti kódolás,
textContent, tisztító könyvtár HTML-hez, CSP tartalékként.
- A
HttpOnly enyhít, de nem véd meg az XSS kárától.
Ellenőrző kérdések
- Mi a különbség a tárolt és a visszatükrözött XSS között?
- Mi a különbség az
innerHTML és a textContent között?
- Miért nem pótolja a CSP a kimeneti kódolást?
- Miért veszélyes a
localStorage-ban tárolt token XSS esetén?
Tanulási források
XSS: amikor az adat kóddá válik
Célok
A lecke végére el tudod magyarázni az XSS lényegét, meg tudod különböztetni a tárolt, a visszatükrözött és a DOM-alapú változatot, és ismered a védelem rétegeit: kimeneti kódolás, szöveg- és HTML-API-k, tisztítás, Content Security Policy.
Miért veszélyes egy hozzászólás?
A Kurzuspontban a hallgatók hozzászólást fűzhetnek egy kurzushoz. Ha az alkalmazás a szöveget ellenőrizetlenül HTML-ként építi be az oldalba, a böngésző a támadó által megadott részt kódként értelmezi. Az MDN szerint az XSS lehetővé teszi a támadónak, hogy kódot futtasson a sérülékeny oldal saját eredetében, ahol a böngésző úgy kezeli, mintha az oldal szerzője írta volna: olvashatja az oldalt és a helyi tárhelyet, és a felhasználó hitelesítő adataival kérhet a szervertől. A sérülés oka mindig az, hogy a szöveg és a futtatható tartalom határa elveszett.
Három változat
innerHTMLfed alocation.search-ből.flowchart LR A[Támadó által adott szöveg] --> S[Alkalmazás] S -->|veszélyes HTML-beillesztés| B[Áldozat böngészője] B -->|kód fut a sérülékeny eredetben| R[Bejelentkezett művelet]Kipróbálva: kimeneti kódolás nélkül és azzal
A hozzászólás szövege egy támadó üzenete. Egy egyszerű szerveroldali sablon (JavaScript sablonszöveg) két módon építi a HTML-t:
const komment = '<img src=x onerror="alert(document.cookie)">'; const veszelyes = c => `<p class="komment">${c}</p>`; const esc = s => s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>') .replace(/"/g,'"').replace(/'/g,'''); const biztonsagos = c => `<p class="komment">${esc(c)}</p>`;Az első kimenetben egy
imgelem van, amelynek hibakezelője (onerror) kódot futtat: a böngésző ezt az oldal részeként értelmezi. A második kimenetben a<,>és"karakterek HTML-entitássá alakultak, ezért a böngésző szövegként jeleníti meg, nem elemként.A kimeneti kódolás (output encoding) kontextusfüggő: más szabályok kellenek a HTML-szövegben, egy attribútumban vagy egy JavaScript-ben. Attribútumban egy idézőjel is kiléphet az értékből:
Az MDN ezért azt tanácsolja, hogy kimeneti kódolást végző sablonmotort használjunk, tartsuk szem előtt a kontextust (attribútumoknál az értéket mindig idézőjelek közé tegyük), és a szkript- és stíluskörnyezeteket tekintsük szinte mindig veszélyesnek. A fenti
escfüggvény oktatási példa: valódi alkalmazásban bevált sablonmotort vagy könyvtárat kell használni, nem saját megoldást.A védelem rétegei
Kimeneti kódolás: a sablonmotor alapértelmezésben kódoljon, és a kontextust ismerjük.
Szöveg-API-k a HTML-API-k helyett: ha csak szövegre van szükség, ne HTML-ként értelmezzük. Böngészőben az
innerHTMLHTML-ként értelmezi a szöveget, atextContentszövegként illeszti be:welcome.innerHTML = `Üdv, ${nev}!`; // HTML-ként értelmezve: veszélyes welcome.textContent = `Üdv, ${nev}!`; // szövegként: biztonságosTisztítás (sanitization): ha valóban HTML-t kell fogadni (formázott hozzászólás), ismert, jó hírű tisztító könyvtárat kell használni (az MDN a DOMPurify-t említi), saját szűrőt nem.
Content Security Policy (CSP): a böngészőnek küldött szabályrendszer, amely korlátozza, honnan tölthet be szkriptet az oldal. Az MDN szerint elsődleges célja az XSS elleni védelem: akkor is megakadályozhatja az idegen szkript futtatását, ha a tisztítás elbukott.
A
nonceegy válaszonként új véletlen érték, amely a fejlécben és az engedélyezett<script nonce="...">elemekben egyezik; a beszúrt idegen szkript nem ismeri. A CSP letiltja a beágyazott eseménykezelőket (onclick=...), ajavascript:címeket és azeval()jellegű futtatást is. A CSP nem pótolja a bemenet helyes kezelését, hanem mögöttes védelmi réteg.Trusted Types: az MDN szerint a CSP
require-trusted-types-for 'script'direktívájával kikényszeríthető, hogy veszélyes helyekre (példáulinnerHTML) csak egy szabályzaton átment érték kerülhessen. Ez a megoldás nem tisztító, hanem a tisztítás kikényszerítésére való keretrendszer, és a böngészőtámogatása korlátozott.Mit tud a támadó, ha mégis bejut?
A támadó kódja a sérülékeny oldal eredetében fut, ezért a bejelentkezett felhasználó nevében kérhet a szervertől. A
HttpOnlycookie-t (7.2) a szkript közvetlenül nem olvashatja, de ettől még a bejelentkezett böngésző nevében érzékeny műveleteket kezdeményezhet, és alocalStoragetartalmához is hozzáférhet (7.3). Az XSS ráadásul a legtöbb CSRF-védelmet (8.4) is meg tudja kerülni, mert a kérés a saját eredetből indul. AHttpOnlytehát enyhít, de nem pótolja az XSS megelőzését.Végigvezetett példa: hozzászólás a Kurzuspontban
textContent-et használ.HttpOnly; Secure; SameSite.Egy támadó hozzászólása így szövegként jelenik meg, nem elemként, és ha mégis hiba csúszik be, a CSP megakadályozza a szkript futtatását.
Gyakori tévhitek
HttpOnlyminden XSS-kárt megakadályoz.”<script>szöveget kiszűrni.”javascript:címek); a kontextusfüggő kódolás kell.Röviden
textContent, tisztító könyvtár HTML-hez, CSP tartalékként.HttpOnlyenyhít, de nem véd meg az XSS kárától.Ellenőrző kérdések
innerHTMLés atextContentközött?localStorage-ban tárolt token XSS esetén?Tanulási források