Klik hier voor informatie over de wijziging in de levering van diensten en ondersteuning.

WordPress afschermen met mu-plugins en Fail2Ban

glen-carrie-aUggJb4-P8c-unsplash-1080

You have been hacked!

Niets is zo vervelend als gehackt worden. Of het nu gaat om een computer, een e-mailaccount of een complete website: het gevoel dat iemand ongewenst toegang heeft gekregen tot jouw systemen blijft bijzonder frustrerend. Voor websites is dat niet anders — en helaas gebeurt het aan de lopende band. Geautomatiseerde bots en scanners zoeken continu naar kwetsbare systemen om te misbruiken voor spam, phishing, malware of andere vormen van misbruik.

Juist populaire platforms zijn daarbij een aantrekkelijk doelwit. Hoe groter het marktaandeel, hoe interessanter het wordt voor aanvallers om hun scans en aanvalstechnieken daarop te richten. Eén succesvolle kwetsbaarheid kan immers direct op enorme schaal worden misbruikt.

WordPress

Met circa 60 procent marktaandeel is WordPress één van de populairste Content Management Systeem (CMS) ter wereld. Het platform maakt het mogelijk om zonder programmeerkennis websites en webshops te bouwen en beheren.

Juist door dat enorme marktaandeel is WordPress ook een aantrekkelijk doelwit voor geautomatiseerde aanvallen, spambots en andere kwaadwillenden. Dagelijks worden wereldwijd miljoenen WordPress-installaties gescand op kwetsbaarheden.

Het is dan ook niet uitzonderlijk dat een WordPress-site vroeg of laat wordt misbruikt — iets wat ik enige tijd geleden zelf in de praktijk tegenkwam.

Praktijkvoorbeeld

Een klant gebruikte WordPress als backend voor een website, voornamelijk omdat het systeem zo gebruiksvriendelijk is. De omgeving was jaren geleden opgezet door een extern bedrijf dat inmiddels failliet was gegaan. Daarmee kwam ook het onderhoud van de WordPress-installatie volledig stil te liggen.

Het gevolg liet zich raden: de omgeving bleek te zijn misbruikt als SOCKS-proxy, waarmee vervolgens andere systemen werden bestookt met scan- en hackpogingen. Daarnaast draaiden er onder het gebruikersaccount van de website een groot aantal verdachte processen met willekeurige namen, terwijl de server tegelijkertijd honderden uitgaande verbindingen opzette naar externe systemen via poort 443 — en incidenteel ook via poort 80. Dat was een duidelijke indicatie dat de omgeving actief werd misbruikt voor kwaadaardige activiteiten.

Dat leidde uiteindelijk tot klachten vanuit de hostingpartij waar de server werd gehuurd. De boodschap was duidelijk: de activiteiten waren niet toegestaan en moesten worden opgelost. Doe je dat niet, dan loop je het risico dat een hostingprovider diensten beperkt of zelfs tijdelijk opschort. Bovendien kunnen dergelijke misbruikscenario’s leiden tot prestatieproblemen of reputatieschade.

Hoewel het probleem uiteindelijk is opgelost door een schone installatie uit te voeren, bleef één vraag hangen:

Waarom was de toegang tot de backend eigenlijk niet verder beperkt? Bijvoorbeeld met een IP-whitelist, zodat alleen bekende IP-adressen toegang krijgen?

Eerste uitdaging: frontend versus backend

Die vraag leek eenvoudig te beantwoorden, totdat een brede IP-beperking daadwerkelijk werd ingevoerd. Al snel bleek dat bepaalde content vanaf de frontend werd geladen vanuit de WordPress-omgeving zelf — met name media uit de uploads-map.

Daardoor ontstond een belangrijk uitgangspunt:

  • de uploads-map moet publiek toegankelijk blijven;
  • de rest van de backend hoeft dat niet te zijn.

Dat klinkt eenvoudiger dan het in de praktijk bleek te zijn.

De omgeving draaide namelijk op een combinatie van Plesk (een hosting control panel voor serverbeheer), NGINX (een snelle webserver en reverse proxy) en Apache (een traditionele webserver), waarbij NGINX als reverse proxy voor Apache functioneerde. In theorie flexibel, maar in de praktijk ontstond al snel een soort split-brain-situatie waarbij NGINX en Apache niet meer volledig overeenstemden over welke verzoeken wel of niet toegestaan moesten worden.

Kortom: de filtering moest op een lager niveau worden opgelost — binnen WordPress zelf.

Een mu-plugin als toegangsfilter

Omdat het om een WordPress Multisite-omgeving ging, ontstond het idee om een centrale plugin te ontwikkelen die voor alle sites actief zou zijn.

Daarvoor is gebruikgemaakt van de map mu-plugins. De naam wordt vaak verkeerd geïnterpreteerd als “multisite plugins”, maar staat in werkelijkheid voor “Must Use Plugins”: plugins die automatisch geladen worden en niet handmatig geactiveerd hoeven te worden.

De plugin kreeg uiteindelijk een relatief eenvoudige maar effectieve opzet:

  1. Allow list
    Staat een IPv4-adres in de whitelist, dan wordt volledige toegang toegestaan.

  2. Root redirect
    Staat een IP-adres niet in de whitelist, maar wordt uitsluitend de rootpagina bezocht — zoals een normale bezoeker zou doen — dan wordt het verzoek doorgestuurd naar de frontend.

  3. Publieke uploads-map
    Verzoeken naar /wp-content/uploads/ blijven publiek toegankelijk.

  4. Detectie van verdachte verzoeken
    Verzoeken naar bekende aanvalsvectoren zoals xmlrpc.php, wp-login.php, .git, .env, .zip, cmd en willekeurige .php-bestanden worden aangemerkt als verdacht wanneer deze afkomstig zijn van niet-gewhiteliste IP-adressen.

  5. HTTP 410-response
    Zodra een verzoek als verdacht wordt aangemerkt, retourneert de plugin een HTTP 410-statuscode (“Gone”).

Waarom HTTP 410?

Het doel van die HTTP 410-response was niet alleen om het verzoek af te wijzen, maar vooral om herkenbare logging mogelijk te maken. Daarnaast heeft een HTTP 410-statuscode als voordeel dat deze normaal gesproken niet automatisch door applicaties of webservers wordt gegenereerd. In de praktijk betekent dit vrijwel altijd dat een beheerder bewust heeft gekozen om deze response terug te geven. Dat maakt een 410 betrouwbaarder als signaal dan generieke foutcodes zoals een 404. Hetzelfde principe zie je overigens ook terug bij minder gangbare HTTP-codes zoals 451 (Unavailable For Legal Reasons) of zelfs 418 (I'm a teapot), die eveneens handmatig worden ingesteld.

Foutmeldingen vanuit de mu-plugin komen terecht in de standaard PHP-FPM errorlog van een Plesk-omgeving: /var/log/plesk-phpxx-fpm/error.log. Daarbij staat xx voor de gebruikte PHP-versie, bijvoorbeeld 80 voor PHP 8.0.

Door daarin een herkenbare marker op te nemen — bijvoorbeeld WP_BLOCK_410 — werd het mogelijk om deze gebeurtenissen automatisch verder te verwerken met Fail2Ban.

Integratie met Fail2Ban

Vanaf dat moment werd het interessant.

Voor Fail2Ban is een custom filter geschreven dat alle PHP-FPM errorlogs scant op meldingen met WP_BLOCK_410.

Vervolgens is een jail-configuratie ingericht waarbij:

  • een overtredend IP-adres na detectie één uur wordt geblokkeerd;
  • de detectievenster (findtime) op één minuut staat;
  • herhaalde overtredingen leiden tot exponentieel langere blokkades:

    • 1 uur
    • 2 uur
    • 4 uur
    • enzovoort.

Het resultaat is dat scan- en hackpogingen niet alleen zichtbaar worden, maar ook actief worden afgeremd.

Daarnaast ontstaat direct inzicht in:

  • de omvang van de aanvallen;
  • de herkomst van verdachte verzoeken;
  • de effectiviteit van de plugin- en Fail2Ban-configuratie.

Recidive en tarpitting

Tot slot wordt binnen Fail2Ban gebruikgemaakt van de recidive-jail. Daarmee kunnen IP-adressen die herhaaldelijk terugkeren voor langere tijd worden geblokkeerd.

Dit principe staat ook wel bekend als tarpitting: aanvallers worden niet alleen geweerd, maar actief vertraagd.

Conclusie

De backend van de klant is hiermee niet alleen afgeschermd met een IP-whitelist, maar wordt daarnaast ook actief beschermd tegen geautomatiseerde scans en aanvalspogingen.

Het grote voordeel daarvan is rust.

De WordPress-omgeving kan nu gecontroleerd verder worden verbeterd en bijgewerkt, zonder voortdurende druk van misbruik of acute beveiligingsproblemen. Denk aan structureel onderhoud, geplande updates, het verwijderen van verouderde plugins en het verder hardenen van de omgeving.

En misschien nog belangrijker: de infrastructuur geeft nu eindelijk weer inzicht in wat er daadwerkelijk gebeurt. Vooral in de eerste periode na het activeren van de logging en Fail2Ban werd duidelijk hoeveel geautomatiseerde verzoeken er dagelijks op een WordPress-backend afkomen. Het ging daarbij niet om incidentele scans, maar om grote aantallen pogingen vanuit uiteenlopende IP-adressen en netwerken wereldwijd.

Juist dat inzicht bleek waardevol voor de klant. Waar beveiliging vaak abstract blijft totdat er iets misgaat, werd nu zichtbaar wat er normaal gesproken “onder water” gebeurt. Tegelijkertijd werd ook het effect van de maatregelen duidelijk merkbaar: naarmate Fail2Ban overtredende IP-adressen begon te blokkeren, nam de hoeveelheid terugkerende verzoeken zichtbaar af. Niet omdat aanvallen volledig verdwijnen, maar omdat Fail2Ban geautomatiseerde scanners en aanvalspogingen daadwerkelijk afremt door IP-adressen tijdelijk of langdurig buitenspel te zetten. Het is lastig om exact te meten in hoeverre bots daardoor definitief afhaken, maar in de praktijk lijkt een deel van de opportunistische scanners uiteindelijk simpelweg door te gaan naar eenvoudiger doelwitten. Daardoor worden resources minder belast en haken veel opportunistische scanners uiteindelijk vanzelf af.

Dat heeft niet alleen technische voordelen in de vorm van minder onnodige belasting en logvervuiling, maar geeft ook rust in de beleving. De omgeving voelt weer beheersbaar. In plaats van continu reactief bezig te zijn met verdachte activiteiten, ontstaat er ruimte om structureel aan onderhoud, updates en verdere beveiliging te werken.

Previous Post