Cross-site scripting (XSS): hoe pentesters het vinden en hoe je het voorkomt

Security8 min leestijd
K

Kees van der Vlies

Partner | IT-auditor

Also available in:English

Cross-site scripting, kortweg XSS, is een van de kwetsbaarheden die we in webapplicatiepentests het vaakst rapporteren. Het principe is oud en goed gedocumenteerd, en toch duikt het steeds opnieuw op, ook in applicaties die met moderne frameworks zijn gebouwd. In dit artikel leggen we uit hoe XSS werkt, welke drie varianten een pentester onderscheidt, hoe wij ernaar zoeken en welke maatregelen het probleem bij de wortel aanpakken.

Wat cross-site scripting is

Bij XSS krijgt een aanvaller het voor elkaar dat zijn eigen JavaScript wordt uitgevoerd in de browser van een ander, binnen de context van jouw applicatie. Dat laatste maakt het gevaarlijk. Het script draait onder de sessie van het slachtoffer en kan alles wat die gebruiker kan: gegevens inzien, formulieren versturen, instellingen wijzigen. De oorzaak is vrijwel altijd dezelfde: de applicatie neemt invoer van een gebruiker op in een pagina zonder die invoer voor de juiste context te coderen. De browser kan dan niet meer onderscheiden wat data is en wat code.

De drie varianten

Pentesters onderscheiden drie soorten XSS, en het onderscheid is meer dan academisch: het bepaalt wie er geraakt wordt en hoe je de kwetsbaarheid vindt.

Reflected XSS is de eenvoudigste vorm. De payload zit in het verzoek, bijvoorbeeld in een zoekparameter, en de server kaatst die ongecodeerd terug in de reactie. Een aanvaller moet een slachtoffer zover krijgen op een geprepareerde link te klikken. Dat beperkt de impact enigszins, al is een overtuigende phishingmail met zo'n link snel gemaakt.

Stored XSS is ernstiger. De payload wordt opgeslagen in de applicatie, bijvoorbeeld via een profielveld, een reactie, een supportticket of de bestandsnaam van een upload, en uitgevoerd bij iedere gebruiker die de betreffende pagina bekijkt. Er is geen geprepareerde link nodig. Eén injectie kan honderden gebruikers raken, en als een beheerder de pagina opent, draait het script met beheerdersrechten.

DOM-based XSS speelt zich volledig in de browser af. JavaScript aan de clientkant leest invoer, bijvoorbeeld uit het URL-fragment, en plaatst die onveilig in de pagina. De server ziet de payload soms niet eens, waardoor deze variant onzichtbaar blijft voor detectie die alleen naar serververkeer kijkt.

Wat een aanvaller ermee kan

De klassieke demonstratie met een pop-upvenster doet de ernst van XSS tekort. Een realistische aanvaller gebruikt een geslaagde injectie om sessietokens of andere gevoelige gegevens uit de pagina te stelen, om acties uit te voeren namens het slachtoffer, of om een nagemaakt inlogscherm over de echte pagina heen te leggen en zo wachtwoorden te oogsten. Bij stored XSS in een beheeromgeving kan dat uitmonden in volledige overname van de applicatie. Toetsaanslagen meelezen binnen de getroffen pagina behoort ook tot de mogelijkheden. Kortom: wie in jouw applicatie scripts kan injecteren, hoeft je database niet eens te bereiken om grote schade aan te richten.

Hoe wij op XSS testen

Tijdens een pentest zoeken we eerst alle plekken waar gebruikersinvoer terugkomt in een pagina: parameters, formuliervelden, HTTP-headers, geüploade bestandsnamen en opgeslagen content. Vervolgens testen we per plek in welke context de invoer belandt, want daar hangt de payload van af. Invoer die in een HTML-attribuut terechtkomt, vraagt een andere aanpak dan invoer die in een stuk JavaScript of in een URL wordt geplaatst. Geautomatiseerde scanners vangen een deel hiervan af, maar juist de interessante gevallen vergen handwerk: payloads die pas na een tweede stap worden uitgevoerd, filters die net niet alles blokkeren, en encoding die in de ene context wel klopt en in de andere niet.

Voor stored XSS in beheeromgevingen gebruiken we blind-XSS-technieken: een payload die pas een signaal afgeeft wanneer die ergens anders wordt uitgevoerd, bijvoorbeeld in een intern dashboard waar supporttickets worden gelezen. Zo vinden we injecties waarvan het effect voor de tester zelf onzichtbaar blijft. We werken daarbij altijd met onschuldige proof-of-concept payloads: het rapport toont aan dat uitvoering mogelijk is, zonder dat er tijdens de test daadwerkelijk gegevens van gebruikers zijn benaderd.

Voorkomen: encoding eerst, dan de vangnetten

De structurele oplossing voor XSS is output-encoding die past bij de context waarin de invoer terechtkomt. HTML-context vraagt HTML-encoding, attribuutcontext attribuutencoding, JavaScript-context weer een andere behandeling. Moderne frameworks doen dit standaard goed: wie in React of Angular netjes binnen de template-syntax blijft, krijgt encoding cadeau. De kwetsbaarheden ontstaan bij de uitzonderingen, zoals dangerouslySetInnerHTML in React of het omzeilen van de sanitizer in Angular, en bij alles wat buiten het framework om HTML genereert: e-mailtemplates, PDF-export, oudere applicatiedelen.

Moet je gebruikers opgemaakte tekst laten invoeren, bijvoorbeeld in een editor met vet en cursief, dan is encoding alleen niet genoeg. Dan is sanitization nodig op basis van een strikte allowlist van toegestane elementen en attributen, met een beproefde bibliotheek zoals DOMPurify. Zelfgebouwde filters die verdachte tekens wegstrepen, sneuvelen in vrijwel elke pentest; er is altijd een encoding of een variant die het filter niet voorzag.

Daarnaast zijn er vangnetten die de impact beperken als er toch iets doorheen glipt. Een strikte Content Security Policy kan voorkomen dat geïnjecteerde scripts uitvoeren of data naar buiten sturen. Het HttpOnly-attribuut op sessiecookies houdt die cookies buiten bereik van JavaScript. Deze maatregelen verhelpen de kwetsbaarheid niet, en dat hoeft ook niet: ze zijn de tweede linie, geen vervanging van encoding en sanitization.

Herstel en retest

XSS-bevindingen herstellen is meestal geen groot project: de juiste encoding of sanitization op de gevonden plek, plus een controle of hetzelfde patroon elders in de code voorkomt. Dat laatste is de stap die nogal eens wordt overgeslagen. Een pentester rapporteert de plekken die binnen de testperiode gevonden zijn; dezelfde onveilige constructie zit vaak op meer plaatsen. Na herstel volgt een retest, waarin we controleren of de oorspronkelijke payloads niet meer werken en of de fix geen omwegen openlaat. Een filter dat de gerapporteerde payload blokkeert maar een variant doorlaat, komt bij een retest alsnog boven water.

Veelgestelde vragen

Is XSS nog relevant nu moderne frameworks automatisch escapen?+

Ja. Frameworks zoals React en Angular escapen standaard, maar bieden ontsnappingsluiken zoals dangerouslySetInnerHTML waarmee ontwikkelaars die bescherming bewust uitzetten, bijvoorbeeld om opgemaakte tekst te tonen. Daarnaast blijven oudere applicatiedelen, e-mailtemplates en PDF-generatoren buiten de framework-bescherming vallen. In pentests vinden we XSS dan ook nog steeds regelmatig.

Wat is het verschil tussen reflected, stored en DOM-based XSS?+

Bij reflected XSS komt het script mee in het verzoek en kaatst de server het direct terug in de reactie. Bij stored XSS wordt het script opgeslagen, bijvoorbeeld in een profielveld of supportticket, en uitgevoerd bij iedereen die de pagina later bekijkt. Bij DOM-based XSS ontstaat het probleem volledig in de browser, doordat JavaScript aan de clientkant onveilig met invoer omgaat.

Lost een Content Security Policy XSS op?+

Nee, een CSP is een tweede verdedigingslinie die de impact van een geslaagde injectie beperkt. De kwetsbaarheid zelf verhelp je met correcte output-encoding en sanitization. Een strikte CSP is wel waardevol: als encoding ergens vergeten wordt, kan de policy voorkomen dat het geïnjecteerde script daadwerkelijk uitvoert.

Hoe toont een pentester XSS aan zonder schade aan te richten?+

Met een onschuldige proof-of-concept payload die alleen aantoont dat scriptuitvoering mogelijk is, bijvoorbeeld een melding of een gecontroleerd callback-verzoek naar een systeem van de tester. In het rapport staat vervolgens beschreven wat een echte aanvaller met dezelfde injectie had gekund, zonder dat dit tijdens de test is uitgevoerd.

Hulp nodig bij security?

Hoe veilig is uw IT-omgeving werkelijk? Wij testen het met vulnerability scans en pentests, en begeleiden de implementatie van ISO 27001 en IEC 62443.

Bekijk Security

Over de auteur

K
Kees van der Vlies

Partner | IT-auditor

Terug naar kennisbank

Heb je een vraag?

Neem contact met ons op voor advies over IT-audit, compliance en informatiebeveiliging.

Contact opnemen