Cross-Site Scripting blijft een van die kwetsbaarheden die iedere pentester vroeg of laat tegenkomt. Het lijkt simpel. Een alert-boxje, een klein scriptje, maar achter dat ogenschijnlijk onschuldige gedrag schuilt een aanvalsvector waarmee je de browser van een ander overneemt. Drie varianten spelen hierbij een rol, en elk vraagt een andere manier van kijken en testen.
Reflected XSS
Het begint meestal met Reflected XSS. Dat is de variant die je het snelst leert herkennen tijdens een pentest: je voert iets in op de pagina, en de website spiegelt jouw input direct terug. Als je een zoekveld hebt waarin je letterlijk terugziet wat je hebt ingevoerd, of een foutmelding waarin jouw tekst ongewijzigd staat, dan weet je dat je te maken hebt met een plek waar de ontwikkelaar mogelijk geen validatie toepast. Om dat te testen hoef je niet meteen los te gaan met grote complexe payloads. Vaak begin ik zelf met een simpele harmless test, zoals het invoegen van een onschuldige HTML-tag om te zien of die wordt weergegeven. Verschijnt jouw tag in de interface of in de broncode? Dan probeer je voorzichtig een klein script of een afbeelding met een error-event. Niet om schade aan te richten, maar om te bevestigen dat de browser jouw code daadwerkelijk uitvoert. Reflected XSS werkt volledig op basis van directe interactie tussen jou en de webserver, waardoor het vooral opvalt bij zoekvelden, contactformulieren en URL-parameters.
Stored XSS
Stored XSS voelt anders. Daar test je niet door te kijken wat er direct gebeurt, maar door na te gaan wat er gebeurt als jouw input later door iemand anders wordt weergegeven. Deze variant kom je vaak tegen op plekken waar gebruikers content kunnen opslaan: profielnamen, reacties, reviews, chats. De truc om stored XSS te testen is geduld. Je voert iets in, wacht tot de applicatie het verwerkt, en bekijkt dan hoe diezelfde data er later uitziet. Wat ik vaak doe in labs en pentests is beginnen met een onschuldige marker, geen script, maar bijvoorbeeld een uniek ID of een opvallende HTML-tag, en vervolgens navigeer ik naar een andere plek in de applicatie om te zien of datzelfde stukje tekst daar terugkomt. Als dat gebeurt, weet je dat de applicatie je input bewaart én opnieuw uitvoert. Pas als ik bevestig dat de data “leeft” in de app, test ik of er daadwerkelijk JavaScript kan worden uitgevoerd. Dit is vaak de variant waarmee je de grootste impact behaalt, omdat een opgeslagen payload automatisch wordt uitgevoerd door iedereen die die pagina bezoekt en dat inclusief de beheerders.
DOM-Based XSS
De laatste variant, DOM-Based XSS, vraagt weer een totaal andere manier van denken. Hier test je niet door te kijken naar server-reacties, maar door te begrijpen wat de front-end code doet. DOM-XSS ontstaat wanneer JavaScript gebruikersinput rechtstreeks in de pagina injecteert. Je ontdekt het door developer tools open te zetten en te kijken of scripts data gebruiken die uit de URL komen, bijvoorbeeld uit location.hash, location.search of zelfs uit cookies. Als je ziet dat een stuk JavaScript zo’n waarde zonder enige vorm van controle in innerHTML plaatst, dan weet je dat dit een plek is waar een payload kans maakt. DOM-XSS test je vervolgens vaak door een payload achter een # in de URL te zetten, omdat die normaal niet naar de server wordt gestuurd. Je kijkt daarna of de browser die input direct verwerkt en uitvoert. Het mooie (en gevaarlijke) van DOM-XSS is dat de server nergens te zien is in het hele proces. De browser valt zichzelf aan, en logbestanden geven geen enkele aanwijzing.
Wat alle drie varianten met elkaar gemeen hebben, is dat je ze niet test door meteen met giftige payloads te strooien. Je begint klein en gecontroleerd, je kijkt hoe de applicatie jouw input verwerkt, en pas daarna bouw je op richting code-executie. In die zin lijkt XSS testen op het lezen van een verhaal: je kijkt hoe de applicatie reageert, je volgt de logica, en je ontdekt stap voor stap waar dingen misgaan.
De impact van XSS blijft daardoor groter dan veel mensen denken. Zodra jouw script draait in de context van een slachtoffer, kun je acties uitvoeren als die gebruiker, wachtwoorden onderscheppen, sessies kapen of zelfs onderdelen van de site manipuleren. En in het geval van stored XSS kan één enkele payload de deuren openen naar een account takeover of een complete compromittering van een admin-dashboard.
In mijn volgende posts duik ik in de praktische kant: welke payloads gebruik je wanneer, hoe bypass je filters, en welke technieken werk je het beste in PNPT labs.
Een goede pentester herkent XSS dan ook niet alleen aan het resultaat, maar ook aan het patroon: directe output wijst vaak op reflected, opgeslagen data op stored en dynamische DOM-manipulatie op DOM-based. Door te begrijpen hoe deze drie mechanismen werken én hoe je ze zorgvuldig test, kun je in elke pentest veel sneller zien waar de zwakke plekken zitten.