This week the security feeds lit up with a name I had not seen before: PHP2Shell. Two critical bugs in PHP itself, CVE-2026-109618 and CVE-2026-109619, rated 9.2 out of 10. No login needed, no user interaction, and the payoff for an attacker is remote code execution on the server.
This site runs on WordPress, and WordPress runs on PHP. So being affected was a given. The job was proving, fast, that I no longer was.
What PHP2Shell actually is
The bugs live in the PHP runtime, below WordPress, below every plugin, below anything I wrote. When a PHP request runs out of memory or dies abnormally, the engine stops, and it does not always tidy up after itself. Leftover pointers and cache entries can be picked up by the next request, and a carefully shaped request turns that mess into code execution. It touches every way PHP serves the web: PHP-FPM, mod_php, LiteSpeed, FrankenPHP. Versions 5.4 through 8.5, so roughly two decades of PHP.
The timeline is the uncomfortable part. Exploit attempts were spotted in honeypots on 17 October, four days before the fix existed. Details leaked ahead of the embargo, proof-of-concept code was on GitHub within hours, and the PHP team pushed the patch out a day early on 21 October: 8.5.12, 8.4.27, 8.3.36 and 8.2.35. Their own advice was blunt. There is no complete configuration workaround. Update.
Step one: measure first
I asked Claude to check the site over the WPVibe connection it already uses to manage WordPress here. First reading: PHP 8.5.8. Vulnerable, with attacks already circulating.
Before touching anything we ran a quick integrity sweep through the same connection: WordPress core against official checksums, plugins against WordPress.org checksums, and a look at who holds administrator rights. Core verified clean. One stray backup copy of the config file was sitting in the webroot where it had no business being, and it is gone now.
Step two: patch the container
The site lives in Docker on a Synology NAS, which means PHP lives inside the WordPress image, sealed off from whatever PHP the NAS runs for itself. That is worth saying out loud, because Synology had a PHP update waiting in Package Center the same morning, and installing it does nothing for a container. The container brings its own runtime.
My first instinct was the shell, and the shell sent me looking in the wrong folder. Asking Docker where the container was built from gave a path that only Portainer uses, which settled it: this stack is managed in Portainer, so Portainer is where it gets updated. Stacks, open the stack, Update, switch on “re-pull image and redeploy”. A minute later:
PHP 8.5.12 (cli) (built: Oct 21 2026)
Built on release day. Patched. I had a stopgap ready (turning off PHP’s session upload progress tracking, which blunts the known entry point) in case the image had lagged behind. It did not, so the stopgap stayed in my pocket.
Step three: assume something leaked
PHP2Shell can also leak server memory: passwords, session tokens, API keys. Attacks were in the wild for six days before this site was patched, so the clean-up treated every secret in that memory as burned.
- Rotated WordPress salts. Mine were hardcoded fallbacks in the config file. They now live as environment variables in the Portainer stack, generated with
openssl rand, and the config file never had to be opened. Rotating them logs out every session everywhere, stolen ones included. - A filesystem sweep for PHP files hiding in uploads and anything modified since the exploit window opened, plus a grep across the theme for the usual webshell fingerprints. Clean.
The intruder who wasn’t
Halfway through, the sweep turned up a second administrator account. No email address, created back in July. For about ten minutes this was a three-month-old break-in and the morning got a lot longer.
Then I remembered. It was me, testing whether I could assign articles to a reader account, and I had forgotten all about it. The call was coming from inside the house. The account is deleted, and test accounts from now on get names like test-delete-me.
It still earned its place in this write-up. A forgotten admin with no email is exactly what a real backdoor looks like, and the only reason I could tell the difference was that I remembered making it.
Where it landed
- PHP 8.5.12 inside the container, confirmed from the shell.
- Cloudflare in front, proxied, so its managed firewall rules for PHP2Shell were already filtering traffic before the patch went in.
- One administrator, new salts, every old session dead.
- Core verified, webroot clean.
- Still on the list: the theme’s bundled page-builder plugins are years behind, and that gets fixed next.
Total time from “what is PHP2Shell?” to patched and swept: about an hour, most of it spent finding which folder the stack lived in. If you run anything on PHP, check your version now. If it is below 8.5.12, 8.4.27, 8.3.36 or 8.2.35, you are on the wrong side of the sign.