Het voorkomen van JavaScript-injectie is niet één taak. Het is het sluiten van meerdere verschillende deuren die allemaal naar dezelfde ruimte leiden: door een aanvaller gecontroleerde code die in de browsers van je bezoekers draait met alle rechten van je eigen pagina. Sommige van die deuren zijn bugs in je code. De meeste zijn scripts die je bewust hebt geladen en die later vijandig werden. Deze gids doorloopt de maatregelen die het risico daadwerkelijk verkleinen, in de volgorde die de meeste bescherming voor de minste moeite biedt, en is eerlijk over waar elke maatregel ophoudt.
Wil je eerst de achtergrond, dan behandelt wat JavaScript-injectie is het mechanisme en elke route die vreemde code een pagina in neemt. Dit artikel is het praktische vervolg: wat je eraan doet.
JavaScript-injectie voorkomen, in het kort
- Sluit de XSS-klasse in je eigen code. Encodeer bij de uitvoer, gebruik veilige DOM-sinks, saneer niet-vertrouwde HTML en zet Trusted Types aan.
- Beperk waar script vandaan kan komen. Een strikte, op nonce gebaseerde Content Security Policy blokkeert niet-geautoriseerde scriptbronnen.
- Zet vast wat je kunt. Subresource Integrity bevriest statische afhankelijkheden van derden op een bekende hash.
- Verklein de afhankelijkheidsboom. Elk script van derden dat je verwijdert is één injectieroute minder.
- Monitor de runtime. Bewaak elk script dat in echte browsersessies draait, want preventie kan de compromittering van een vertrouwde leverancier niet bereiken.
De injectieroutes waartegen je je verdedigt
Preventie heeft pas zin als je weet wat je voorkomt. JavaScript wordt geïnjecteerd langs vier brede routes, en elk vraagt om een andere maatregel:
| Injectieroute | Hoe het gebeurt | De maatregel die helpt |
|---|---|---|
| Cross-site scripting (XSS) | Een fout in je code laat invoer van de aanvaller als script draaien | Uitvoerencoding, veilige sinks, sanering, Trusted Types |
| Gecompromitteerd script van derden | Een leverancier van wie je de tag laadt levert kwaadaardige code | Afhankelijkheidsreview, CSP, Subresource Integrity, runtime-monitoring |
| Magecart / skimmers | Aanvallers plaatsen kaartstelende code op betaalpagina's, vaak via een gecompromitteerde afhankelijkheid | Runtime-payloadmonitoring op de checkout, PCI DSS-maatregelen |
| Kwaadaardige extensies / clientmalware | Code wordt geïnjecteerd op de eigen machine van de bezoeker, op elke site die hij bezoekt | Runtime-detectie; je kunt de browser van de bezoeker niet patchen |
De eerste route is een codefout die van jou is en die je kunt herstellen. De rest zijn vertrouwensfouten: de code was uitgenodigd, of leeft op een machine die je niet beheert. Dat onderscheid telt, omdat het verklaart waarom het herstellen van je eigen code noodzakelijk maar nooit voldoende is.
Stap 1: Sluit de XSS-klasse in je eigen code
Cross-site scripting is de enige injectieroute die volledig onder jouw controle valt, dus begin hier. XSS gebeurt wanneer data die een gebruiker aanlevert in de pagina wordt geschreven op een plek waar de browser het als code in plaats van tekst behandelt. De oplossingen zijn goed begrepen:
- Encodeer bij de uitvoer, niet alleen bij de invoer. Dezelfde string is veilig in de ene context en gevaarlijk in de andere, dus encodeer data voor de exacte context waar ze terechtkomt: HTML-body, HTML-attribuut, JavaScript, URL of CSS. Contextbewuste uitvoerencoding is de meest effectieve XSS-maatregel.
- Gebruik veilige DOM-sinks. Wijs niet-vertrouwde data toe met
textContentin plaats vaninnerHTML, en vermijdeval,document.writeensetAttributeop event-handlers. Deze "sinks" zijn de plek waar geïnjecteerde strings uitvoerbaar worden. - Saneer HTML die je moet renderen. Wanneer je echt door de gebruiker aangeleverde HTML moet renderen (bijvoorbeeld een rich-text-reactie), haal die dan door een onderhouden sanitizer die script en event-handlers verwijdert, in plaats van je eigen filter te schrijven.
- Zet Trusted Types aan. Trusted Types zorgen dat gevaarlijke DOM-sinks gewone strings weigeren op browserniveau, zodat een geïnjecteerde payload ze niet kan bereiken, zelfs als een bug door de codereview glipt. Ze worden afgedwongen via een CSP-directive, wat direct naar de volgende stap leidt.
Het herstellen van de XSS-klasse verwijdert de deur die een aanvaller opent door jouw code te misbruiken. Het doet niets aan de deuren die je zelf hebt geopend door het script van iemand anders te laden, en daar komen de meeste moderne client-side-inbreuken vandaan.
Stap 2: Beperk bronnen met een Content Security Policy
Een Content Security Policy vertelt de browser welke bronnen script op je pagina mogen laden en uitvoeren. Een strikte, op nonce gebaseerde CSP is een van de sterkste losse maatregelen die je tegen injectie kunt inzetten: script dat niet van een toegestane bron komt, en dat niet de juiste nonce per verzoek draagt, draait simpelweg niet. Inline <script>-blokken die een aanvaller via een XSS-gat injecteert worden standaard geblokkeerd.
Twee praktische opmerkingen. Ten eerste: geef waar mogelijk de voorkeur aan nonces boven host-allowlists; een brede script-src-allowlist heropent stilletjes routes die je wilde sluiten. Ten tweede, en dat is de belangrijke beperking: CSP autoriseert bronnen, het beoordeelt geen gedrag. Zodra je het domein van een leverancier toestaat zodat hun legitieme script kan draaien, heeft CSP geen manier om te weten of dat script zich gedraagt of je formulier aftapt. Als de leverancier bij de bron wordt gecompromitteerd, staat hun domein nog steeds op je allowlist en laadt de kwaadaardige versie probleemloos. CSP is noodzakelijk. Het is niet het hele antwoord.
Stap 3: Zet statische afhankelijkheden vast met Subresource Integrity
Met Subresource Integrity (SRI) kun je een cryptografische hash koppelen aan een <script>- of <link>-tag. De browser berekent de hash van het gedownloade bestand en weigert het uit te voeren als de hash niet klopt, wat betekent dat een statisch bestand van derden niet kan worden verwisseld voor een gemanipuleerde versie zonder dat de wijziging wordt geblokkeerd.
SRI is uitstekend voor afhankelijkheden die niet veranderen: een specifieke versie van een bibliotheek vastgezet op de exacte hash. De beperking is het spiegelbeeld van die sterkte. Het doet niets voor scripts die bedoeld zijn om te updaten (de meeste analytics, tagmanagers en betaal-SDK's wijzigen hun geleverde bestand regelmatig), en het doet niets voor scripts die dynamisch door andere scripts worden geladen. Gebruik SRI overal waar je een versie kunt vastzetten. Verwacht niet dat het de delen van je stack dekt die zichzelf updaten.
Stap 4: Verklein en review de afhankelijkheidsboom
Elk script van derden op je pagina is een injectieroute met alle rechten van de pagina. De goedkoopste beschikbare preventie is er minder te hebben.
- Inventariseer wat je daadwerkelijk laadt. De meeste teams zijn verrast door hoeveel scripts er op een typische pagina draaien, en door hoeveel ervan niet direct maar door een ander script worden geladen (een tagmanager die een leverancier laadt die weer een leverancier laadt).
- Verwijder wat je niet nodig hebt. Een ongebruikte marketingpixel of een verlaten A/B-testtool is puur risico zonder voordeel. Het verwijderen elimineert een injectieroute volledig.
- Minimaliseer de impactradius van de tagmanager. Een gecompromitteerd tagmanager-account kan "gewoon nog een tag" injecteren die routineus lijkt. Beperk wie mag publiceren en review wat ze publiceren.
Deze stap heeft geen nadeel en geen enkele maatregel per script kan hem vervangen: een script dat niet op de pagina staat kan niet degene zijn die wordt gecompromitteerd.
Stap 5: Monitor de runtime met payloadinspectie in echte sessies
Stappen één tot en met vier verkleinen de kans. Geen van hen kan het geval bereiken dat de ergste client-side-inbreuken veroorzaakt: een script dat je legitiem vertrouwt, van een bron die je legitiem hebt toegestaan, dat kwaadaardig wordt nadat het al is geladen. CSP laat het door omdat de bron is toegestaan. SRI mist het omdat het script is ontworpen om te updaten. Je eigen code is schoon. De enige plek die overblijft om het te betrappen is waar de injectie daadwerkelijk landt, de browser, op het moment dat het script draait.
Dat is wat runtime-monitoring doet. Het bouwt een inventaris van elk script dat in echte gebruikerssessies draait, registreert wat elk doet en waar het data naartoe stuurt, en waarschuwt wanneer een nieuw script verschijnt, de code van een bekend script verandert, of data naar een onverwachte bestemming begint te stromen. Dit is de laag die Magecart-achtige skimmers en aanvallen van gecompromitteerde leveranciers betrapt die alle preventieve maatregelen hierboven doorlaten.
Tools aan de serverkant kunnen dit niet leveren. Een webapplicatiefirewall of een serverscan ziet de HTML die je hebt geleverd, niet de payload die een gecompromitteerde leverancier later verwisselt of de code die een extensie op de machine van de bezoeker injecteert. Detectie moet op de browserlaag plaatsvinden, in echte sessies.
Waar cside past: scriptmonitoring in echte sessies
cside is één first-party JavaScript-snippet die de runtime-laag van deze checklist levert. Het vervangt je CSP, je uitvoerencoding of je SRI-hashes niet; het is de maatregel die bewaakt wat je toegestane scripts daadwerkelijk doen zodra ze draaien, wat geen van de andere kan zien.
De scriptmonitoring van cside inventariseert elk script dat in echte browsersessies draait, analyseert de volledige scriptpayload en waarschuwt bij nieuwe domeinen, gewijzigde code en onverwachte datastromen. Het werkt in twee operationele modellen: de Script Method haalt scripts van derden op en analyseert ze aan de kant van cside voordat ze in de sessie draaien, en de Scan Method voor teams die een lichtere footprint verkiezen. Het wordt ingezet als één scripttag vanaf je eigen origin, zonder DNS-wijziging; cside haalt de scripts van derden die je pagina laadt op en analyseert ze, maar staat niet vóór het verkeer van je site en fungeert niet als proxy.
Op betaalpagina's is deze runtime-inventaris ook een nalevingsvereiste. De eisen 6.4.3 en 11.6.1 van PCI DSS 4.0.1 bestaan juist omdat geïnjecteerd script op de betaalpagina onzichtbaar is voor elke maatregel aan de serverkant: 6.4.3 vraagt je om elk script op je betaalpagina's te beheren en te autoriseren, en 11.6.1 vraagt je om ongeautoriseerde wijzigingen aan die scripts en de paginakoppen te detecteren. Scriptmonitoring in echte sessies is het directe antwoord op beide.
Preventiechecklist
Het voorkomen van JavaScript-injectie is gelaagd, en de lagen overlappen niet:
- Encodeer uitvoer en gebruik veilige DOM-sinks om de XSS-klasse in je eigen code te sluiten.
- Zet een strikte, op nonce gebaseerde CSP in om niet-geautoriseerde scriptbronnen te blokkeren.
- Voeg Subresource Integrity toe om statische afhankelijkheden van derden vast te zetten.
- Verklein en review de afhankelijkheidsboom zodat er minder te compromitteren is.
- Monitor echte browsersessies zodat een vertrouwd script dat vijandig wordt, wordt betrapt op het moment dat het handelt.
Krijg de eerste vier goed en je hebt de meeste deuren gesloten. Voeg de vijfde toe en je kunt de enige deur zien die preventie nooit volledig kan vergrendelen: de vertrouwde leverancier die wordt gecompromitteerd nadat je hem hebt binnengelaten.









