Hoe we tijdens PHP2Shell onze website binnen een half uur veilig stelden, en er een snellere en veiligere website aan overhielden.

Door Bart de Boer – Principal Architect bij Hightech Innovators
Op dinsdagavond 20 oktober 2026 werd een kritiek lek in PHP ontdekt, al snel PHP2Shell genoemd. PHP is de programmeertaal waarop WordPress draait. Woensdagochtend rond een uur of tien was het wereldnieuws. Uit voorzorg gingen onder meer DigiD, MijnOverheid, de Tweede Kamer en zelfs het NCSC offline, net als systemen van ziekenhuizen en onderwijsinstellingen. Duizenden websites lagen urenlang plat, en niemand wist wanneer er een patch zou komen.
Waarom is zo’n lek zo gevaarlijk? Bij een WordPress-site, en bij veel andere CMS’en, rekent de server bij elk paginabezoek opnieuw uit hoe die pagina eruit moet zien. Dat kan heel krachtig zijn, bijvoorbeeld als elke bezoeker iets anders te zien krijgt. Maar op de meeste sites, en ook op de onze, ziet iedereen gewoon dezelfde pagina. Toch draait er bij elk bezoek code op de server. Dat kost tijd, en zit er een lek in die code, dan kan een aanvaller er via een gewoon paginabezoek bij.
We hadden al langer het idee dat onze site ook statisch kon. Dan zijn alle pagina’s vooraf uitgerekend en staan ze klaar als gewone bestanden, die de server alleen nog hoeft te serveren. Er draait dan geen code meer die je kunt aanvallen.
Zodra het nieuws woensdagochtend doorbrak en we zagen hoe ernstig het lek was, moest ik meteen aan dat idee denken, als pleister. Terwijl op directieniveau werd overlegd of de site offline moest, begon ik alvast aan zo’n statische kopie. Met hulp van een AI-codeerassistent stond die binnen een half uur klaar. Kort nadat het besluit viel om WordPress offline te halen, ging de kopie online, uren voordat de patch uitkwam. Bezoekers zagen gewoon onze site. Alleen formulieren, events boeken en zoeken werkten die dag even niet, en dat namen we voor lief.
Van die pleister hebben we inmiddels een blijvende oplossing gemaakt. We werken nog steeds met WordPress, dus voor het beheer van de site verandert er niets. Het verschil is dat WordPress niet meer vanaf internet te bereiken is, maar uitsluitend via VPN en onze SSO-login. Na elke wijziging maken we automatisch een statische versie van de site, die veilig en snel door Cloudflare wordt gehost. Daarmee bieden we aanvallers veel minder ingangen. Een lek in PHP, in WordPress of in een van de plugins kan onze publieke site niet meer raken. De site is bovendien merkbaar sneller, en omdat we elke versie bewaren, draaien we een fout zo terug. Dat werkt trouwens niet alleen voor WordPress. Een CMS dat niet publiek bereikbaar is, kan door een lek als dit niet geraakt worden, welk CMS het ook is.
Wat kun je hiermee?
Twee kwetsbaarheden in PHP zelf, allebei met een CVSS-score van 9.2 en allebei goed voor remote code execution zonder inloggen:
Kwetsbaar waren versies terug tot PHP 5.4. Zelf een patch maken was geen optie. We konden dus alleen wachten, en niemand kon zeggen hoe lang.
De gepatchte releases (8.5.12, 8.4.27, 8.3.36 en 8.2.35) kwamen woensdagmiddag, enkele uren na onze overstap. Voor PHP 8.1 en ouder komt er geen officiële fix. De officiële WordPress-Docker-images met gepatchte PHP volgden pas ’s avonds laat. Dat een patch “uit” is, betekent dus niet dat hij overal draait. Sommige Nederlandse sites waren een dag later nog offline. Het NCSC zag in Nederland geen actief misbruik, wel meer verkeer van scanners op zoek naar kwetsbare servers.
Cloudflare stond al als proxy tussen onze bezoekers en de origin, de webserver met WordPress. Tegen dit lek hielp dat niet. Een firewall voor je website houdt geen kwetsbaarheid in PHP zelf tegen als de aanval eruitziet als een gewoon verzoek.
Die positie bleek wel goud waard. Onze DNS-records stonden op proxied, dus bezoekers praatten altijd met Cloudflare en nooit rechtstreeks met de origin. Wie de origin wil vervangen, hoeft alleen te veranderen waar Cloudflare naartoe wijst. Je wacht niet op DNS-propagatie en hebt geen gedoe met certificaten, want het is één record.
Het crawlscript schreven we samen met Claude Code, een AI-codeerassistent in de terminal. We beschreven wat we wilden, lazen mee, stuurden bij en draaiden het. Binnen een paar minuten hadden we een Python-crawler van zo’n zestig regels, alleen met de standaardbibliotheek. De crawler
https://www.hightechinnovators.nl/...) naar root-relatieve paden (/...);Dat leverde ongeveer 107 pagina’s op, zo’n 400 bestanden en rond de 150 MB. De upload naar een nieuw Cloudflare Pages-project duurde ongeveer vijftien seconden:
wrangler pages deploy ./mirror --project-name <project>
Op de *.pages.dev-URL die Pages zelf uitdeelt, klikten we de pagina’s door en controleerden we dat niets nog naar de origin verwees.
Daarna lieten we het www-CNAME-record naar het Pages-project wijzen, nog steeds proxied, en koppelden we de hostname als eigen domein. Pages valideerde het domein in ongeveer een minuut.
Dat DNS-record omzetten mocht de AI-assistent overigens niet zelf. Zijn eigen veiligheidscontrole hield hem tegen bij het DNS-formulier, dus de laatste klik op “Opslaan” deed een van ons. Eerlijk gezegd vind ik dat ook de juiste verdeling.
Vanaf dat moment kwam geen enkel verzoek van een bezoeker nog bij PHP terecht. De origin ging in onderhoudsmodus, zodat we hem rustig konden patchen. Scanners die de rest van de dag nog wp-login.php of xmlrpc.php probeerden, kregen gewoon onze homepage te zien.
Van de eerste prompt tot een live statische site kostte het ons hooguit een half uur. Formulieren, boekingen en zoeken deden het die dag niet, en daarmee wisten we meteen wat de permanente opzet wél moest kunnen.
WordPress blijft het redactiegereedschap voor marketing, maar is niet bereikbaar vanaf het internet. De publieke site is statisch.
WordPress draait nu op ons private Docker-cluster, achter SSO en VPN. Redacteuren werken in hetzelfde dashboard als altijd, alleen kan de rest van de wereld er niet meer bij.
Een must-use plugin start bij elke publicatie een GitLab CI-pipeline. Daarnaast is er een knop “Publiceer site nu” in de adminbalk en draait er elk uur een geplande run als vangnet. Een korte debounce zorgt dat tien snelle bewerkingen samen één build opleveren.
guard → crawl → transform → validate → assets → commit → deploy → smoke
immutable caching. De pipeline uploadt alleen nieuwe bestanden.wrangler pages deploy als op woensdagochtend, nu vanuit de pipeline.Op die woensdag waren de formulieren het grootste gemis. Het endpoint is nu een Cloudflare Pages Function in dezelfde deployment als de site. Formulier en schema gaan daardoor altijd tegelijk live, en bij een rollback ook tegelijk terug.
We maten vanaf kantoor de time-to-first-byte, als mediaan van negen metingen.
De publieke site zelf kost bij Cloudflare vrijwel niets. WordPress moet nog wel ergens privé draaien, en wat dat kost hangt af van waar je dat doet. Wij hadden daar al een eigen plek voor, dus voor ons dalen de hostingkosten flink.
De belangrijkste les: een publiek bereikbaar CMS is een open deur. Of het nu WordPress, Drupal of iets zelfgebouwds is, als het systeem waarmee je je site beheert ook je site serveert, ben je bij elk lek in die software direct kwetsbaar en wacht je op de patch van een ander. Haal je het beheer van het internet af en serveer je alleen kant-en-klare pagina’s, dan heeft een aanvaller via je CMS geen ingang meer.
Verder nemen we mee:
Wil je sparren over zo’n opzet, of je eigen website en CMS laten doorlichten? Neem contact met ons op. We denken graag mee, ook als het geen noodgeval is.
We hebben je vraag in goede orde ontvangen. Een bevestiging van jouw vraag is verzonden naar je e-mailadres.
