A cross-site scripting akkor jön létre, amikor támadó által befolyásolt adat kódként kerül a böngésző értelmezésébe. A közismert tanács, hogy „escape-eljük a felhasználói inputot”, helyes irányt jelez, de túl pontatlan. A böngésző ugyanazon az oldalon HTML-t, attribútumokat, URL-eket, JavaScriptet és CSS-t parse-ol, mindegyik eltérő határolókkal és escape-szabályokkal. Egy HTML text node-hoz megfelelő kódolás scriptblokkban elégtelen lehet. A védelem kulcsa ezért a kimeneti kontextus pontos ismerete.
A bemeneti szűrés nem helyettesíti a kimeneti védelmet
Felhasználói névből kitilthatjuk a tageket, de ugyanaz az adat később attribútumba, JSON-ba vagy e-mailbe kerülhet. A veszélyes karakterek listája célonként változik, az obfuszkáció és Unicode pedig könnyen megkerüli az általános tiltást.
A bemeneti validáció a domén szabályait érvényesíti: hossz, engedélyezett formátum és jelentés. Az output encoding a konkrét renderer nyelvtanát védi. Mindkettő hasznos, de más kérdésre válaszol, és nem szabad az egyiket a másik bizonyítékának tekinteni.
A HTML text node a legegyszerűbb kontextus
Elemek közötti szövegben a kisebb jel, ampersand és a parser számára releváns karakterek entityvé alakítása megakadályozza, hogy a tartalom taget vagy karakterhivatkozást nyisson. A modern template engine {{ value }} jellegű alapértelmezett kimenete általában ezt végzi.
A „raw”, „safe” vagy hasonló bypass csak auditált, megbízható HTML-hez használható. Ha egy helper ilyen típust ad vissza egyszerű stringhez, a biztonsági határ láthatatlanná válik. A review-nak minden raw outputot külön indoklással kell kezelnie.
Az attribútumérték saját határolóval rendelkezik
Idézőjeles attribútumban a megfelelő idézőjelet is kódolni kell, különben a támadó lezárhatja az értéket és új attribútumot, például event handlert adhat hozzá. A dinamikus attribútum mindig legyen idézőjelben, és a sablonmotor attribútumkontextusát használja.
Nem minden attribútum biztonságos pusztán encoding után. Az onclick, style vagy bizonyos URL-t fogadó helyek új parserbe adják a dekódolt értéket. Ilyen sinkbe megbízhatatlan adatot lehetőleg egyáltalán ne illesszünk.
Az URL encoding és a URL policy két külön védelem
Egy query paramétert percent encodinggal kell a URL megfelelő komponensébe helyezni, majd az egész attribútumot HTML attribútumként renderelni. A két réteg sorrendje számít. Egy általános HTML escape nem készít helyes query stringet, a percent encoding pedig nem védi az idézőjeles markupot.
A szintaktikailag helyes URL továbbra is lehet javascript: séma vagy támadó domainje. Navigációs célhoz engedélyezett séma, host és relatív út policy szükséges. Az encoding megőrzi a struktúrát; nem dönti el, hogy a cél megbízható-e.
A JavaScript kontextust érdemes elkerülni
Dinamikus adatot közvetlenül script stringbe írni különösen törékeny. Idézőjel, backslash, sortörés, lezáró script tag és JavaScript escape egyaránt számít. A HTML entity önmagában itt nem megbízható, mert a böngésző tokenizálási fázisai más sorrendben működnek.
Biztonságosabb adatot JSON-ként egy megfelelően kezelt data blokkban vagy DOM data-* attribútumban átadni, majd kódból olvasni. A serializernek scriptbe ágyazásra alkalmas módot kell használnia, nem kézzel összefűzött JSON stringet.
A DOM API-k között vannak biztonságos és veszélyes sinkek
A textContent szöveges node-ot hoz létre, ezért a benne lévő tagek nem aktiválódnak. Az innerHTML új markupként parse-olja a stringet. Ugyanaz a támadó input az elsőben látható szöveg, a másodikban futó kód vagy veszélyes elem lehet.
A framework virtuális DOM-ja is rendszerint escape-el normál interpolációnál, de raw HTML directive esetén nem. A csapatnak össze kell gyűjtenie a használt framework veszélyes sinkjeit, lint szabállyal és code review ellenőrzéssel korlátozva őket.
A rich text sanitizationt, nem egyszerű escapinget igényel
Ha a termék valóban enged formázott felhasználói tartalmat, a teljes HTML escape megszüntetné a kívánt tageket. Ilyenkor HTML parser alapú sanitizer engedélyezett elemeket, attribútumokat és URL-sémákat tart meg, a többit eltávolítja vagy semlegesíti.
Regex nem képes biztonságosan kezelni a böngésző komplex hibajavítását, namespace-eit és kódolási trükkjeit. Érett, rendszeresen frissített sanitizer könyvtár kell, pontos konfigurációval. Az eredményt trusted HTML típusként lehet kezelni, de csak a sanitization után.
A sanitizált HTML-nek is van életciklusa
A böngészők és sanitizer szabályok fejlődnek. Egy éve megtisztított, adatbázisban tárolt HTML az új fenyegetési ismeretek szerint már problémás lehet. A rendszer tárolhatja a nyers forrást korlátozott hozzáféréssel, valamint a sanitizer verzióját és a publikált tiszta változatot.
Policy-frissítéskor háttérfolyamat újraszűrheti a tartalmat, vagy rendereléskor történhet sanitization cache-sel. A választás teljesítmény- és auditkompromisszum, de a verzió nyomon követése nélkül nem tudjuk, mely rekordok régi szabály szerint készültek.
A Content Security Policy második védelmi vonal
A szigorú CSP korlátozhatja, honnan tölthető script és milyen inline kód futhat. Nonce- vagy hash-alapú policy jelentősen csökkentheti egy megmaradt injection kihasználhatóságát. Report-only bevezetés feltárja a jelenlegi függőségeket.
A CSP nem helyettesíti az output encodingot. Hibás policy, engedékeny domain vagy már megbízható scriptben lévő DOM XSS megkerülheti. Defense in depth rétegként működik, miközben az elsődleges cél továbbra is az, hogy támadó adat ne kerüljön kódkontextusba.
A Trusted Types a DOM XSS sinkeket központosíthatja
Támogatott böngészőkben a Trusted Types policy megkövetelheti, hogy bizonyos veszélyes DOM sinkek ne fogadjanak tetszőleges stringet. Csak kijelölt, auditált függvény állíthat elő TrustedHTML vagy kapcsolódó értéket. Ez láthatóvá teszi a szétszórt innerHTML használatot.
Bevezetése migráció: először riportokból fel kell térképezni a sinkeket, majd megszüntetni vagy biztonságos policy mögé tenni őket. Egy mindent visszaadó default policy kiüresíti a védelmet, ezért minden kivételnek szűk céllal kell rendelkeznie.
A template kontextus elveszhet helper és komponens között
Egy helper visszaadhat előre escape-elt stringet, amelyet a sablon újra escape-el, vagy safe markupként jelölhet nyers adatot. A komponens API-jának külön típussal kell jeleznie a plain textet és a megbízható HTML-t, nem elnevezési konvencióval.
A „safe” érték ne legyen tartósan tárolható vagy tetszőlegesen összefűzhető. Ha új, megbízhatatlan rész kerül hozzá, a teljes eredmény elveszíti a garanciát. A strukturált komponensparaméterek kevesebb ilyen rejtett állapotot hoznak létre.
A teszt payloadok több parserréteget járjanak be
Egyetlen <script> teszt nem elég. Idézőjel-törés, event handler, veszélyes URL-séma, SVG/MathML, lezáró tag, entity és többszörös decode is szerepeljen. A cél nem támadási stringek végtelen listája, hanem minden tényleges sink és kontextus lefedése.
Automatikus browser teszt ellenőrizheti, hogy a payload szövegként jelenik meg és nem fut le. Statikus elemző keresheti a raw directive-okat, dinamikus scanner pedig kiegészítő bizonyítékot ad. Egyik eszköz sem helyettesíti a helyes adatfolyam-tervezést.
A hibás escaping gyakran kompatibilitási kerülőként tér vissza
Amikor a helyes autoescaping után egy régi rekord „<” szöveget mutat, csábító globálisan kikapcsolni a védelmet. Valójában valószínűleg korábban előre kódolt adat került a tárolóba. A megoldás célzott adatmigráció és az írási út javítása.
Shadow riport mérheti, mely rekordok tartalmaznak tipikus dupla-encoding mintát. A dekódolás csak bizonyított forrásra fusson, mert egy általános HTML decode korábban ártalmatlan szövegből aktív markupot készíthet. Rollback és mintavételes ellenőrzés szükséges.
A kontextusfüggő kódolás architekturális szabály
A legbiztonságosabb rendszer nyers, validált doménadatot tárol, és a renderelés utolsó pontján választ encodert a sink alapján. A template alapértelmezetten escape-el, a veszélyes kontextusokat elkerüli, a szükséges rich textet pedig dedikált sanitizer kezeli.
Így az XSS-védelem nem egyetlen helper vagy tiltólista, hanem következetes adatút. Minden dinamikus értéknél megválaszolható, ki irányítja, melyik parser olvassa következőként, és melyik szabály teszi abban a környezetben adattá a kód helyett.