Terwijl een gebruiker is ingelogd op je site, kan een andere kwaadaardige pagina namens hem verzoeken naar je server sturen; dat is de kern van de vraag wat is CSRF. Cross-Site Request Forgery (vervalsing van verzoeken tussen sites) is een aanval die misbruik maakt van de gewoonte van de browser om cookies automatisch aan elk verzoek toe te voegen. Zolang de sessie van de gebruiker actief is, kan een aanvaller zijn autoriteit "lenen" om statuswijzigende acties te activeren, zoals een wachtwoord wijzigen, geld overmaken of instellingen aanpassen. In dit artikel leg ik uit hoe de aanval werkt, de logica achter tokens en hoe moderne frameworks deze verdediging automatiseren, allemaal met concrete voorbeelden.
Hoe de aanval stap voor stap werkt
Stel dat een bankapp een overschrijving uitvoert met een formulier zoals dit:
<form action="https://bank.com/overschrijving" method="POST">
<input name="ontvanger" value="...">
<input name="bedrag" value="...">
</form>
Als de gebruiker een totaal andere pagina bezoekt terwijl hij nog ingelogd is bij de bank, kan die pagina een verborgen formulier bevatten:
<form action="https://bank.com/overschrijving" method="POST" id="kwaadaardig">
<input type="hidden" name="ontvanger" value="aanvaller">
<input type="hidden" name="bedrag" value="10000">
</form>
<script>document.getElementById('kwaadaardig').submit();</script>
Wanneer de browser het verzoek verstuurt, voegt hij automatisch de sessiecookie toe die voor de bank is geregistreerd. Vanuit het oogpunt van de server lijkt het verzoek precies op dat van een legitieme gebruiker. De kern van het probleem is dit: de server kan niet bepalen of het verzoek echt van zijn eigen pagina komt of van een vreemde oorsprong.
De tokenlogica: het synchronisatietoken
De klassieke verdediging is het "synchronizer token pattern". De server genereert voor elke sessie een onvoorspelbare willekeurige waarde, slaat die op in de sessie van de gebruiker en sluit die als verborgen veld in het formulier van de pagina in:
<form method="POST" action="/overschrijving">
<input type="hidden" name="_token" value="a1b2c3...willekeurig">
...
</form>
Wanneer het formulier wordt verzonden, vergelijkt de server het binnenkomende _token met de waarde die in de sessie is opgeslagen. Als ze niet overeenkomen, wordt het verzoek geweigerd. De vreemde pagina van de aanvaller kan dit token niet kennen, omdat een pagina van een andere oorsprong de inhoud van de bankpagina niet kan lezen vanwege de Same-Origin Policy. Het token blijft geheim en het vervalste verzoek wordt ongeldig.
Om veilig te zijn moet een token deze eigenschappen hebben:
- Onvoorspelbaar: gemaakt met een cryptografisch sterke willekeurige generator.
- Gebonden aan de sessie of gebruiker: het token van de ene gebruiker mag niet geldig zijn voor een andere.
- Alleen vereist voor statuswijzigende verzoeken: alleen-lezen verzoeken zoals
GEThoren sowieso geen neveneffecten te hebben.
De double submit cookie-methode
Voor stateless API's die geen sessiestatus op de server willen bewaren, bestaat er een alternatief patroon: de double submit cookie. Hierbij wordt het token zowel naar een cookie als naar een verzoekheader (of de body) geschreven. De server controleert of de twee gelijk zijn. Een vreemde site kan de cookie van een andere oorsprong niet via JavaScript lezen om die in de header te kopiëren, dus de aanval mislukt. Het enige nadeel is dat het zorgvuldige configuratie vereist rond kwesties zoals vertrouwen tussen subdomeinen.
SameSite-cookies: verdediging op browserniveau
Het SameSite-attribuut dat aan cookies wordt toegevoegd, vertelt de browser of de cookie bij verzoeken tussen sites moet worden verzonden. Er zijn drie waarden:
SameSite=Strict: de cookie wordt alleen toegevoegd aan verzoeken van dezelfde site; hij wordt zelfs niet verzonden bij het volgen van een externe link.SameSite=Lax: hij wordt toegevoegd aanGET-verzoeken voor navigatie op het hoogste niveau, maar niet aanPOST's tussen sites. Dit is de standaard in de meeste moderne browsers.SameSite=None; Secure: hij wordt in alle gevallen verzonden; gebruik dit wanneer je echt een cookie tussen sites nodig hebt.
Lax of Strict biedt een sterke laag tegen CSRF, omdat de sessiecookie nooit wordt toegevoegd aan een door een aanvaller geactiveerde POST tussen sites. Toch wordt SameSite alleen niet als voldoende beschouwd; voor oudere browsers en bepaalde randgevallen is het gebruik van tokenbescherming ernaast de meest robuuste aanpak. Een gelaagde verdediging is essentieel.
Hoe frameworks dit automatiseren
Moderne frameworks regelen het genereren en valideren van tokens voor je. In Laravel voegt bijvoorbeeld de Blade-directive @csrf het verborgen tokenveld aan het formulier toe, en de VerifyCsrfToken-middleware valideert het automatisch bij elk POST-, PUT-, PATCH- en DELETE-verzoek:
<form method="POST" action="/profiel">
@csrf
<input name="naam">
<button>Opslaan</button>
</form>
Wanneer je een verzoek met JavaScript doet (bijvoorbeeld fetch), moet je het token uit een <meta>-tag lezen en aan de header toevoegen:
const token = document.querySelector('meta[name="csrf-token"]').content;
fetch('/profiel', {
method: 'POST',
headers: {
'X-CSRF-TOKEN': token,
'Content-Type': 'application/json'
},
body: JSON.stringify({ naam: 'Aslain' })
});
De logica is hetzelfde in andere ecosystemen: Django gebruikt {% csrf_token %} en CsrfViewMiddleware, Rails gebruikt protect_from_forgery en Express steunt op csurf-achtige pakketten voor hetzelfde werk. API's die geen op cookies gebaseerde sessies gebruiken (bijvoorbeeld die bij elk verzoek een Authorization: Bearer-header meedragen) zijn van nature beter bestand tegen CSRF, omdat de browser die header niet automatisch toevoegt.
Praktische checklist
- Bescherm elk statuswijzigend endpoint (
POST/PUT/PATCH/DELETE) met CSRF-validatie. - Voeg
SameSite=Lax(ofStrictwaar gepast) enSecuretoe aan sessiecookies. - Lek het token nooit in een URL of logbestand; gebruik een verborgen formulierveld of verzoekheader.
- Houd
GET-verzoeken vrij van neveneffecten; gebruik ze niet om gegevens te wijzigen. - Schakel de ingebouwde bescherming van het framework niet uit; als het echt moet, maak dan alleen de betreffende route een uitzondering.
Veelgestelde vragen
Zijn CSRF en XSS hetzelfde?
Nee. XSS is wanneer een aanvaller een kwaadaardig script in je pagina injecteert en kan zelfs tokenbescherming omzeilen. CSRF is het misbruiken van de bestaande sessie van de gebruiker vanaf buiten. Als er een XSS-kwetsbaarheid bestaat, kan je CSRF-verdediging ook instorten, en daarom moeten beide samen worden aangepakt.
Voorkomt alleen HTTPS gebruiken CSRF?
Nee. HTTPS versleutelt gegevens en maakt man-in-the-middle moeilijker, maar het kijkt niet naar waar het verzoek vandaan komt. Je hebt voor CSRF nog steeds tokens en/of SameSite-cookies nodig.
Moet het token bij elk verzoek veranderen?
Niet noodzakelijk. Een token dat vast is voor de sessie maar eraan gebonden is, volstaat in de meeste gevallen. Een token dat bij elk verzoek opnieuw wordt gegenereerd, biedt extra beveiliging maar kan problemen geven met de terugknop en gebruik in meerdere tabbladen; daarom gebruiken de meeste frameworks één token voor de hele duur van de sessie.
Wil je je formulieren beveiligen of een bestaande applicatie auditen? Ik bouw praktische oplossingen voor CSRF, XSS en sessiebeveiliging. Neem contact op en laten we je project samen versterken.