Hoe deze controle werkt
Deze controle draait alleen als je site WordPress is, wat hij herkent aan wp-content of wp-includes in de HTML van de hoofdpagina. Staan die er niet, dan doet hij niets. Staan ze er wel, dan test hij drie veelvoorkomende lekken. Ten eerste: de tag <meta name="generator" content="WordPress 6.x"> in de HTML, die de exacte versie onthult (lage ernst). Ten tweede: het bestand /readme.html, dat WordPress bij elke installatie meelevert en dat ook de versie noemt (laag). Ten derde: de route /wp-json/wp/v2/users van de REST API; antwoordt die 200 met JSON dat de velden slug en name bevat, dan levert hij de gebruikerslijst met hun inlognamen uit (gemiddelde ernst).
Waarom dit belangrijk is
WordPress draait meer dan 40% van het web, waardoor het het favoriete doelwit is van geautomatiseerde aanvallen. Die aanvallen werken in twee stappen: de versie en plugins identificeren om een exploit te kiezen, en daarna wachtwoorden proberen op de login. De blootgestelde versie lost stap één op. De gebruikerslijst lost de helft van stap twee op: met de slug van elke gebruiker (dat is de inlognaam, niet de weergavenaam) heeft de aanvaller alleen nog het wachtwoord nodig, en dat van admin, editor of de naam van de eigenaar wordt duizenden keren per minuut geprobeerd. Daarom heeft gebruikersenumeratie gemiddelde ernst en de andere twee lage.
Hoe je het oplost
Versie in de generator-meta
In de functions.php van je child-thema of in een kleine eigen plugin:
remove_action('wp_head', 'wp_generator');
Let op dat de versie ook zichtbaar is in de parameters ?ver=6.x van de CSS- en JS-bestanden van de core. Het helemaal verbergen vraagt een beveiligingsplugin; maar wat je echt beschermt, is WordPress up-to-date houden, niet het nummer verbergen.
readme.html
Verwijder het na elke installatie en update (WordPress maakt het opnieuw aan), of blokkeer het op de server. In nginx: location = /readme.html { return 404; }. In Apache, in de .htaccess: <Files readme.html> Require all denied </Files>. Doe hetzelfde met license.txt.
Gebruikersenumeratie
Beperk de gebruikersroute van de REST API tot ingelogde sessies. Zonder plugin, in functions.php:
add_filter('rest_endpoints', function ($endpoints) {
if (!is_user_logged_in()) {
unset($endpoints['/wp/v2/users']);
unset($endpoints['/wp/v2/users/(?P<id>[\d]+)']);
}
return $endpoints;
});
De gangbare beveiligingsplugins (Wordfence, Solid Security, All-In-One Security) hebben een vergelijkbare optie. Sluit ook de oude enumeratie via ?author=1, die doorverwijst naar /author/naam/, en zet een pogingenlimiet en tweestapsverificatie op de login: zelfs als de gebruikersnaam lekt, is het wachtwoord alleen niet meer genoeg.
Controleer of het opgelost is
curl -s https://jouwdomein.nl | grep -io '<meta name="generator"[^>]*>'
curl -s -o /dev/null -w "%{http_code}\n" https://jouwdomein.nl/readme.html
curl -s -o /dev/null -w "%{http_code}\n" https://jouwdomein.nl/wp-json/wp/v2/users
Verwacht: de eerste geeft niets terug; de tweede 404 of 403; de derde 401 of 403 (of 404 als je de route hebt verwijderd).
Veelgestelde vragen
Is het niet genoeg om de gebruiker "admin" te hernoemen?
Het helpt tegen de domste aanvallen, maar als de REST API gebruikers opsomt, is de nieuwe naam in één verzoek gevonden. Hernoem hem en sluit de enumeratie.
Mijn thema of een plugin gebruikt de gebruikers-REST API aan de voorkant.
Zeldzaam, maar het komt voor bij sommige auteursoverzichten. Beperk dan, in plaats van de route te verwijderen, de velden die hij teruggeeft (zonder slug) of beperk hem op gebruikersrechten. Test het eerst in een stagingomgeving.
Kijkt deze controle naar kwetsbare plugins?
Nee; hij kijkt naar lekken van de basisinstallatie. Verouderde plugins worden deels gedetecteerd door verouderde bibliotheken. En als WP_DEBUG_LOG aan staat, wordt het openbare bestand dat het aanmaakt gedekt door blootgestelde logs.