Stel je voor dat iemand een bezoeker verleidt tot een klik op een link terwijl die is ingelogd bij zijn bank. Die link leidt naar een verborgen formulier dat, zonder dat de bezoeker het weet, bij de bank een verzoek indient om geld over te maken. De bank ziet een legitiem verzoek van een ingelogde gebruiker en voert het uit. De aanvaller heeft nu geld verplaatst met het account van het slachtoffer, zonder ooit het wachtwoord te kennen.
Dit is CSRF (Cross-Site Request Forgery): een aanval waarbij een aanvaller een slachtoffer ongemerkt een formulier of verzoek laat versturen naar een andere website waar het slachtoffer al is ingelogd.
Dit leer je: wat CSRF-aanvallen zijn en hoe ze werken, waarom ze zo gevaarlijk zijn, ook als andere beveiliging aanwezig is, hoe CSRF-tokens ze voorkomen en hoe je bescherming op je formulieren inricht.
Wat is CSRF?
CSRF (uitgesproken als "sea-surf") staat voor Cross-Site Request Forgery. Het is een aanval die misbruik maakt van het feit dat je browser, als je bent ingelogd op een website, je sessie automatisch vertrouwt en meestuurt bij elk verzoek aan die site, ook als het verzoek van een andere website komt.
De aanval werkt door een ingelogde bezoeker ongemerkt een formulier te laten versturen naar een website waar hij een account heeft. De aanvaller ziet het wachtwoord van het slachtoffer niet. Dat hoeft ook niet. Het slachtoffer moet alleen al ingelogd zijn, en dan wordt hij verleid tot een verzoek (zoals het versturen van een verborgen formulier) dat doet wat de aanvaller wil.
CSRF verschilt van andere aanvallen, omdat er geen inloggegevens gestolen of versleuteling gekraakt hoeven te worden. Het misbruikt het vertrouwen van het slachtoffer in de website. Het slachtoffer is al geauthenticeerd, dus de website vertrouwt verzoeken die van hem lijken te komen.
Zo werken CSRF-aanvallen
Een CSRF-aanval verloopt in drie stappen.
Stap 1: Het slachtoffer is ingelogd op je site
Iemand bezoekt je website en logt in op zijn account. Zijn browser heeft nu een actieve sessiecookie die bewijst dat hij is geauthenticeerd. Die sessie blijft actief zolang hij de site blijft gebruiken.
Stap 2: De aanvaller lokt hem naar een schadelijke pagina
Terwijl het slachtoffer nog is ingelogd op je site, bezoekt hij een pagina die de aanvaller beheert (bijvoorbeeld via een link op social media, een misleidende advertentie of een website die betrouwbaar lijkt). Op die pagina staat verborgen code die een formulier terugstuurt naar jouw site.
Stap 3: Het formulier wordt automatisch verstuurd met de sessie van het slachtoffer
Het verborgen formulier wordt naar je website gestuurd. Omdat het slachtoffer al is ingelogd, stuurt zijn browser de sessiecookie automatisch mee. Vanuit het perspectief van je formulier is dit een legitiem verzoek van een geauthenticeerde gebruiker. Het formulier verwerkt het alsof het slachtoffer het bewust heeft verstuurd.
De aanvaller heeft nu iets gedaan op je site met het account van het slachtoffer, zonder dat die het weet. Hij kan het wachtwoord hebben gewijzigd, geld hebben overgemaakt, het profiel hebben aangepast of gegevens hebben verstuurd.
Waarom dit werkt, ook als andere beveiliging aanwezig is
CSRF-aanvallen omzeilen verschillende gangbare beveiligingsmaatregelen:
HTTPS stopt CSRF niet. De gegevens zijn tijdens het transport versleuteld, maar de aanvaller hoeft ze niet te onderscheppen. Hij stuurt het verzoek gewoon vanuit de browser van het slachtoffer, en die browser vertrouwt het al.
Wachtwoordbeveiliging stopt CSRF niet. De aanvaller heeft het wachtwoord van het slachtoffer nooit nodig. Hij maakt alleen gebruik van het feit dat het slachtoffer al is ingelogd.
Een sterk inlogsysteem stopt CSRF niet. Zelfs als de authenticatie perfect is, werkt de aanval zodra het slachtoffer is ingelogd.
Daarom is CSRF-bescherming een eigen laag. Andere beveiligingsmaatregelen voorkomen het niet. Je moet formulieren er specifiek op ontwerpen dat ze CSRF-aanvallen weerstaan.
CSRF-tokens: zo stoppen ze de aanval
De oplossing is een CSRF-token. Dat is een unieke, onvoorspelbare reeks tekens die je server voor elk formulier maakt. Zo werkt het:
Je server maakt voor elke bezoeker een uniek token
Als iemand een pagina met een formulier laadt, genereert je server een willekeurig token en koppelt dat aan de sessie van die gebruiker. Dit token is elke keer anders en uniek voor die gebruiker.
Het token zit verwerkt in het formulier
Het token staat als verborgen veld in het HTML-formulier. Als de bezoeker het formulier verstuurt, gaat het token mee.
Je server controleert het token voordat de inzending wordt verwerkt
Als het formulier wordt verstuurd, controleert je server of het token in de inzending overeenkomt met het token dat je voor de sessie van deze gebruiker hebt gemaakt. Zo ja, dan wordt het formulier verwerkt. Zo nee (of als er geen token is), dan wordt het formulier geweigerd.
Het verborgen formulier van de aanvaller kan geen geldig token bevatten, omdat tokens onvoorspelbaar zijn en uniek per sessie. Een aanvaller op een ander domein kan het token niet zien of raden. Zijn aanval mislukt dus. Het verborgen formulier wordt verstuurd, maar je server weigert het omdat het token niet klopt.
Hoe een CSRF-token er in de praktijk uitziet
In HTML ziet een formulier met CSRF-bescherming er zo uit:
<form method="POST" action="/submit-form"> <input type="hidden" name="csrf_token" value="a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"> <input type="email" name="email" placeholder="Your email"> <button type="submit">Submit</button> </form> Het veld `csrf_token` is verborgen. De bezoeker ziet het niet, maar het wordt samen met zijn formuliergegevens verstuurd. Je server controleert het voordat het e-mailadres of andere gegevens worden verwerkt.
CSRF-bescherming voor verschillende soorten formulieren
Gewone HTML-formulieren
Standaard HTML-formulieren (die gegevens via POST naar je server sturen) hebben CSRF-tokens nodig. Dat geldt voor contactformulieren, inlogformulieren, formulieren om een wachtwoord te herstellen en elk formulier dat gegevens op je server wijzigt.
AJAX- en JavaScript-formulieren
Formulieren die via JavaScript naar een API-endpoint worden verstuurd, hebben ook CSRF-bescherming nodig. Het token kan in het formulier zelf zitten, in een cookie staan of uit een metatag worden gehaald voordat het verzoek wordt verstuurd.
Stateless API-verzoeken
Gebruikt je API authenticatie met tokens (zoals JWT), dan werkt CSRF-bescherming anders. De browser kan een JWT-token niet automatisch meesturen zoals een sessiecookie, dus traditionele CSRF is een kleinere bedreiging. Toch is het verstandig om voor extra bescherming ook een CSRF-token toe te voegen.
Andere beveiligingen die samenwerken met CSRF-tokens
CSRF-tokens zijn de belangrijkste verdediging, maar ze werken het best naast andere beveiligingsmaatregelen.
SameSite-cookies
Als je een sessiecookie instelt, kun je een `SameSite`-attribuut toevoegen dat de browser vertelt het cookie niet mee te sturen bij verzoeken vanaf een ander domein. Zo kan het verborgen formulier van de aanvaller de sessie van het slachtoffer helemaal niet gebruiken.
Controle van de Referer-header
Je server kan de Referer-header controleren om te zien waar het verzoek vandaan komt. Zegt een inzending van je domein te komen, maar toont de Referer een ander domein, dan is het waarschijnlijk een aanval. (Referer-headers kunnen echter worden vervalst, dus dit is geen volledige verdediging.)
Double-submit-cookiepatroon
Bij deze aanpak staat het CSRF-token zowel in een cookie als in een formulierveld. Je server controleert of ze overeenkomen voordat hij verwerkt. Dit komt minder vaak voor dan gewone tokens, maar werkt goed voor stateless applicaties.
Zo voer je CSRF-bescherming in
De details hangen af van je technische opzet, maar het patroon is hetzelfde:
- Genereer een uniek token per sessie. Als een gebruiker een sessie start of een pagina met een formulier laadt, maakt je backend een nieuw token.
- Bewaar het token op de server. Sla het op, gekoppeld aan de sessie van die gebruiker.
- Neem het token op in het formulier. Voeg het toe als verborgen veld in de HTML of als metatag die JavaScript kan lezen.
- Controleer bij het versturen. Controleer voordat je formuliergegevens verwerkt of het verstuurde token overeenkomt met het bewaarde token van die sessie.
- Genereer opnieuw na gevoelige acties. Maak een nieuw token nadat een gebruiker zijn wachtwoord wijzigt of iets belangrijks doet.
De meeste moderne frameworks (Django, Rails, Laravel, ASP.NET) regelen het genereren en controleren van CSRF-tokens automatisch. Gebruik je een framework, dan staat CSRF-bescherming meestal standaard aan. Zorg er alleen voor dat je het niet per ongeluk uitschakelt.
Veelgemaakte fouten die formulieren kwetsbaar maken
CSRF-bescherming uitschakelen voor het gemak. Sommige ontwikkelaars zetten CSRF-controles uit om API-tests makkelijker te maken of hun code te vereenvoudigen. Doe dit niet. Het levert een echte kwetsbaarheid op.
Een vast token voor alle gebruikers gebruiken. Het token moet uniek zijn voor de sessie van elke gebruiker. Een token dat voor iedereen gelijk is (of voor alle gebruikers op alle dagen), beschermt niet tegen CSRF.
Tokens niet vernieuwen. Gebruik je wekenlang hetzelfde token, dan heeft een aanvaller meer tijd om het te misbruiken. Vernieuw tokens regelmatig en altijd na gevoelige handelingen.
Alleen op SameSite-cookies vertrouwen. SameSite helpt, maar is niet genoeg. Gebruik CSRF-tokens als belangrijkste verdediging en SameSite als back-up.
Tokens op sommige formulieren vergeten. Beveilig je sommige formulieren met tokens en andere niet, dan gebruiken aanvallers de onbeveiligde formulieren. Elk formulier dat gegevens wijzigt, heeft bescherming nodig.
Wat WEMASY doet voor CSRF-bescherming
Formulieren van WEMASY hebben automatisch CSRF-tokenbescherming op elk formulier dat je maakt. Je hoeft het niet in te stellen en er niet over na te denken. Verstuurt een bezoeker een formulier, dan controleert WEMASY het token voordat de inzending wordt opgeslagen. Deze bescherming is ingebouwd en staat standaard aan.
Lees meer over de opties voor formulierbeveiliging op de prijspagina.