Een nonce in WordPress is een beveiligingstoken dat wordt meegegeven aan formulieren, links en AJAX-verzoeken, zodat WordPress kan controleren of het verzoek bewust door een specifieke ingelogde gebruiker is gestart. De naam komt uit de cryptografie en staat voor number used once, maar dat is hier een beetje misleidend: een WordPress-nonce is geen getal, wordt niet één keer gebruikt en is technisch gezien een hash die gekoppeld is aan de gebruiker, de sessie en één specifieke actie. Hij blijft tot 24 uur geldig. De dreiging waar nonces tegen beschermen is cross-site request forgery (CSRF), een aanval waarbij een kwaadaardige pagina stilletjes een verzoek verstuurt met jouw ingelogde sessie, bijvoorbeeld om een bericht te verwijderen of een instelling te wijzigen. Nonces werken, en precies daarom zit de meest gemaakte beveiligingsfout aan de andere kant: een nonce bewijst intentie, geen rechten. Plugincode die een nonce controleert maar de rechtencontrole met current_user_can() vergeet, is een van de vaakst voorkomende bronnen van gepubliceerde WordPress-kwetsbaarheden.
Wat is een nonce? De naam klopt niet helemaal
In de cryptografie is een nonce een waarde die precies één keer wordt gebruikt en daarna nooit meer, en juist dat maakt hem nuttig tegen replay-aanvallen. WordPress leende de naam, maar niet de strikte definitie. Een WordPress-nonce is een korte hash die uit vier ingrediënten wordt opgebouwd: de actie waar hij bij hoort, het ID en het sessietoken van de huidige gebruiker, het actuele tijdvenster en de geheime sleutels uit je wp-config.php. Die constructie heeft drie praktische gevolgen:
- Een nonce is persoonlijk. Een token dat voor de ene gebruiker is aangemaakt, faalt bij de validatie voor elke andere gebruiker, dus hij is niet te delen en niet van een openbare pagina te stelen.
- Een nonce hoort bij één actie. Een token voor de actie delete-post_42 is waardeloos voor delete-post_43 of voor welke andere bewerking dan ook. Daarom verzwakt een hardgecodeerde generieke actienaam het hele mechanisme.
- Een nonce is herbruikbaar binnen zijn levensduur. Hetzelfde token valideert zo vaak als je wilt, tot 24 uur lang. WordPress documenteert dat expliciet, dus een nonce alleen beschermt niet tegen het opnieuw afspelen van een verzoek dat een aanvaller al heeft onderschept.
De officiële WordPress-ontwikkelaarsdocumentatie is verfrissend eerlijk over dit alles en beschrijft nonces als één verdedigingslaag, niet als een compleet beveiligingssysteem.
Welke aanvallen voorkomen WordPress-nonces? CSRF uitgelegd
De aanval waar nonces voor bestaan is cross-site request forgery. Het scenario: je bent in het ene browsertabblad ingelogd op je WordPress-beheerscherm. In een ander tabblad open je een volstrekt losstaande pagina die een aanvaller beheert. Die pagina bevat een verborgen formulier of een img-tag die naar jouw site wijst, bijvoorbeeld een verzoek om een plugin te deactiveren of een gebruiker aan te maken. Omdat je browser je WordPress-cookies automatisch meestuurt bij elk verzoek aan jouw domein, komt het verzoek volledig geauthenticeerd binnen en zou WordPress het gewoon uitvoeren. Je klikte nergens op, je zag niets, en je site heeft er zojuist een beheerder bij gekregen.
Nonces breken deze aanval bij de laatste stap. De kwaadaardige pagina kan het verzoek vervalsen, maar kan de actuele nonce voor jouw gebruiker en die actie niet kennen, omdat het token alleen verschijnt in pagina's die WordPress voor jou rendert. Het vervalste verzoek komt zonder geldige nonce aan, de verificatie mislukt en WordPress stopt met dat beroemde witte scherm dat vraagt: weet je zeker dat je dit wilt doen?
Hoe lang is een WordPress-nonce geldig? Het ticksysteem
WordPress verdeelt de tijd in ticks van twaalf uur. Bij het controleren van een nonce accepteert WordPress tokens uit de huidige tick en uit de vorige, en daar komt de maximale levensduur van 24 uur vandaan. De verificatiefuncties vertellen zelfs in welke helft je zit: ze geven 1 terug als de nonce binnen de laatste twaalf uur is aangemaakt, 2 als hij tussen de twaalf en 24 uur oud is en false als hij ongeldig of verlopen is.
De levensduur is aan te passen met de filter nonce_life, wat sommige beveiligingsgerichte opstellingen gebruiken om het venster te verkorten:
// Levensduur van nonces verkorten naar 4 uur
add_filter( 'nonce_life', function () {
return 4 * HOUR_IN_SECONDS;
} );Een kortere levensduur verkleint het replayvenster, maar zorgt er ook voor dat gecachte pagina's met ingebakken nonces sneller verlopen, en dat is een echt probleem op sites met agressieve paginacaching. Serveert een gecachte pagina een nonce die ouder is dan 24 uur, dan krijgt elke bezoeker een token dat nooit meer kan valideren. Dit is de klassieke reden waarom AJAX-functies op zwaar gecachte WordPress-sites voor sommige bezoekers op onverklaarbare wijze niet werken.
Hoe maak je een nonce aan in WordPress?
WordPress levert drie hulpfuncties voor het genereren van nonces, één voor elke plek waar een token doorgaans belandt:
// 1. In een formulier: voegt een verborgen invoerveld plus een refererveld toe
wp_nonce_field( 'save_settings_action', 'my_plugin_nonce' );
// 2. In een URL: plakt ?_wpnonce=... achter een link
$url = wp_nonce_url( admin_url( 'admin.php?page=my-plugin&delete=42' ), 'delete-item_42' );
// 3. Kaal token, bijvoorbeeld om door te geven aan JavaScript
$nonce = wp_create_nonce( 'my_ajax_action' );Het eerste argument is altijd de actie, en juist daar loont zorgvuldigheid. Een actiestring als delete-item_42, die het object-ID bevat, levert een token op dat alleen voor precies dat item werkt. Een luie generieke actie als my_nonce die door een hele plugin heen wordt gedeeld, betekent dat één gelekt of voorspelbaar token elke bewerking van die plugin openzet.
Hoe controleer je een nonce: wp_verify_nonce, check_admin_referer en check_ajax_referer
Voor de controle bestaat er één lowlevelfunctie en twee gemakswrappers die je in echte code veel vaker tegenkomt:
// Lowlevel: geeft 1, 2 of false terug en stopt nooit zelf
if ( ! wp_verify_nonce( $_POST['my_plugin_nonce'], 'save_settings_action' ) ) {
return; // stoppen, ongeldig verzoek
}
// Beheerschermen: controleert en stopt bij falen met een foutpagina
check_admin_referer( 'save_settings_action', 'my_plugin_nonce' );
// AJAX-handlers: hetzelfde idee voor verzoeken via admin-ajax.php
check_ajax_referer( 'my_ajax_action', 'security' );De wrappers stoppen de uitvoering zodra de controle mislukt, en dat is meestal precies wat je wilt bovenaan een handler. De lowlevelfunctie wp_verify_nonce() geeft je in plaats daarvan de retourwaarde, zodat je in een API-context met een nette JSON-fout kunt antwoorden. Welke variant je ook kiest, de controle moet vóór de actie staan en niet erna, wat vanzelfsprekend klinkt en toch een terugkerende bug in changelogs van plugins blijft.
Nonces in de REST API en AJAX-verzoeken
De WordPress REST API gebruikt hetzelfde mechanisme met een vaste actienaam. Voor elk verzoek dat op cookie-authenticatie leunt, verwacht WordPress een nonce die is aangemaakt voor de actie wp_rest en die wordt meegestuurd in de requestheader X-WP-Nonce. Scripts die op de standaardmanier worden geregistreerd krijgen dit automatisch: wp_localize_script() of de nieuwere bundel wp.apiFetch geven het token door, en elk REST-verzoek van een ingelogde gebruiker draagt het mee zonder dat de ontwikkelaar erover nadenkt.
// Een REST-nonce doorgeven aan je JavaScript
wp_localize_script( 'my-app', 'myAppData', array(
'restUrl' => esc_url_raw( rest_url( 'my-plugin/v1/' ) ),
'nonce' => wp_create_nonce( 'wp_rest' ),
) );
// In JavaScript: meesturen als header
fetch( myAppData.restUrl + 'items', {
method: 'POST',
headers: { 'X-WP-Nonce': myAppData.nonce, 'Content-Type': 'application/json' },
body: JSON.stringify( { title: 'Nieuw item' } ),
} );Zonder die header behandelt de REST API het verzoek als niet-geauthenticeerd, ook al stuurde de browser geldige inlogcookies mee. Dat is bewust: het is exact de CSRF-bescherming die hierboven is beschreven, toegepast op de API.
Waarom een nonce geen rechtencontrole is
Dit is verreweg het belangrijkste om over nonces te begrijpen, en het is ook het deel dat in het plugin-ecosysteem geregeld misgaat. Een geslaagde noncecontrole bewijst twee dingen: het verzoek komt van een pagina die WordPress heeft gegenereerd, en het hoort bij de gebruiker aan wie het token is uitgegeven. Hij bewijst niet dat die gebruiker mag doen wat het verzoek vraagt. Een abonnee kan een volstrekt geldige nonce hebben, want WordPress geeft nonces gewoon uit aan elke ingelogde gebruiker, en sommige tokens werken zelfs voor bezoekers die helemaal niet zijn ingelogd.
Correcte handlercode stelt daarom altijd twee losse vragen:
// Intentie: is dit een echt verzoek van deze gebruiker?
check_admin_referer( 'save_settings_action', 'my_plugin_nonce' );
// Rechten: mag deze gebruiker dit doen?
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( 'Onvoldoende rechten.' );
}Kwetsbaarheidsdatabases staan vol met wat er gebeurt als die tweede vraag wordt overgeslagen. Gebrekkige toegangscontrole en CSRF staan structureel bij de meest voorkomende categorieën in de CVE's van WordPress-plugins, en een flink deel van die meldingen komt neer op een handler die een nonce controleerde, of helemaal niets, in plaats van een rechtencontrole. Lees je een pluginadvies waarin staat dat elke geauthenticeerde gebruiker instellingen kon wijzigen of dat autorisatie ontbrak, dan zit dit patroon er meestal achter.
Veelgemaakte noncefouten in themes en plugins
- Noncecontrole in plaats van rechtencontrole. De klassieker, hierboven beschreven. Beide controles zijn nodig, ze beantwoorden verschillende vragen.
- Eén generieke nonce voor alles. Eén actiestring voor een hele plugin verandert een smal token in een loper.
- Nonces in gecachte pagina's. Full-page caches serveren tokens voorbij hun levensduur van 24 uur en breken zo stilletjes formulieren en AJAX voor een deel van de bezoekers. Fragmentcaching of de nonce apart ophalen lost dat op.
- Controleren na het handelen. De controle hoort het eerste te zijn wat de handler doet.
- Een nonce vertrouwen als identiteitsbewijs voor uitgelogde gebruikers. Nonces voor bezoekers zonder sessie zijn per constructie zwakker, omdat het gebruikersdeel van de hash leeg is.
- Nonces lekken via URL's. Tokens die aan links worden geplakt belanden in serverlogs, browsergeschiedenis en de Referer-header. Voor destructieve acties zijn POST-verzoeken met wp_nonce_field() de betere plek.
Hoe helpt InspectWP je de gevolgen te zien?
Van buitenaf kun je niet zien of een plugin zijn nonces correct controleert, maar je kunt de gevolgen wél zien zodra die openbaar worden. InspectWP herkent de plugins en themes van een geanalyseerde WordPress-site inclusief hun versies en vergelijkt die met bekende kwetsbaarheidsgegevens, en CSRF-fouten en ontbrekende autorisatie van precies het soort dat in dit artikel wordt beschreven, vormen een groot deel van die meldingen. Markeert een rapport een geïnstalleerde plugin met een gepubliceerde kwetsbaarheid, dan is de oplossing bijna altijd dezelfde en verfrissend simpel: bijwerken. Met geplande automatische rapporten loopt die cyclus van signaleren, leren en bijwerken continu door, in plaats van wanneer iemand er toevallig aan denkt.