Word on the street this morning was that something nasty was going around in PHP land, and that some site owners were taking their sites offline as a precaution. That’s never a good sign. So I put my rendering work aside, read the advisory, and spent the next hour patching my web server that I have with IONOS. This is the log of that hour: what the vulnerability is, what I did, what went wrong along the way, and what I learned.
If you only read one thing: check which PHP version your sites are running today. If it’s anything below 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12, you’re exposed. And if it’s PHP 7.x or 5.x, no patch is coming at all (in which case SWITCH NOW to a patched version of PHP 8 before you get invaded).
What is PHP2Shell?
PHP2Shell is the nickname for two critical bugs in PHP itself, CVE-2026-109618 and CVE-2026-109619. Both are rated 9.2 on CVSS v4, and both can let an attacker run code on your server without logging in. One is memory corruption in how PHP recovers from running out of memory; the other is internal state that survives a fatal interruption when it shouldn’t.
The scary part is where it happens. The known triggers are multipart/form-data requests, the format browsers use for file uploads, and PHP parses those before any of your own code runs. That means WordPress, your plugins, your security plugin and your carefully hardened wp-config are all irrelevant. If PHP is reachable, it’s reachable.
The flaws reportedly go all the way back to PHP 5.4. Fixes shipped on 21 October 2026 in the following versions:
- PHP 8.2.35
- PHP 8.3.36
- PHP 8.4.27
- and PHP 8.5.12.
Versions 8.1 and older are end-of-life and don’t get official releases, although the PHP team published minimal source backports for 7.4, 8.0 and 8.1 for people who compile their own.
Step 1: What’s actually running?
My server runs AlmaLinux 9 with Plesk. Plesk ships its own PHP builds under /opt/plesk/php/, so the first question was which handlers exist, and whether patched packages were already available:
plesk bin php_handler --list
dnf check-update | grep -i php
Good news first: Plesk had already published 8.2.35 and 8.3.36. You can even see the build times baked into the package versions (8.2.35-0redhat.9.261021.0737), so the vendor chain turned this around within hours of the upstream release. Very impressive!
The less good news: the handler list still showed PHP 5.6.40 and 7.4.33 enabled. Some sites I host for friends run old themes that never made the jump to PHP 8, so those versions had quietly lingered. And they will never receive an update. That was the real emergency.
Step 2: A stopgap for the unpatchable versions
Before touching anything else, I closed the most widely discussed attack path on the old versions by turning off session upload progress. It’s a partial mitigation, not a fix, but it takes thirty seconds:
for v in 5.6 7.4; do
printf 'session.upload_progress.enabled = 0\nsession.use_strict_mode = 1\n' \
> /opt/plesk/php/$v/etc/php.d/zz-php2shell.ini
done
systemctl restart plesk-php56-fpm plesk-php74-fpm
If you can’t patch at all, the stronger option is rejecting multipart requests at the web server. That breaks media uploads and some contact forms, so it’s a judgement call per site.
Step 3: Update, and a GPG surprise
Next up, a plain dnf update, which also pulled in a new kernel. It downloaded everything and then stopped dead:
Public key for plesk-php82-8.2.35-0redhat.9.261021.0737.x86_64.rpm is not installed
...
Error: GPG check FAILED
Plesk’s signing key wasn’t in the RPM keyring, probably because Plesk usually handles its own updates and plain dnf had never needed it. The tempting shortcut is --nogpgcheck. Don’t do that! An emergency security build is exactly the package whose signature you want verified. Import the key from Plesk over HTTPS instead:
rpm --import https://autoinstall.plesk.com/plesk.gpg
dnf update -y && reboot
After the reboot, uname -r showed the new kernel and the handler list confirmed 8.2.35 and 8.3.36 everywhere. My own sites loaded fine. The PHP 8 side of the server was patched.
Step 4: Moving the stragglers to PHP 8.3
This query lists every domain still on the old handlers:
plesk db "SELECT d.name, h.php_handler_id FROM hosting h JOIN domains d ON d.id = h.dom_id WHERE h.php_handler_id LIKE '%php56%' OR h.php_handler_id LIKE '%php74%'"
Eight sites were on 7.4, and to my surprise none were on 5.6. Those handlers were enabled but completely unused, which is pure attack surface waiting for someone to select it from a dropdown. I disabled them on the spot:
plesk bin php_handler --disable plesk-php56-fastcgi
plesk bin php_handler --disable plesk-php56-fpm
Then I bit the bullet and moved all eight to PHP 8.3 in one go, whether their themes were ready or not, and ran a quick status sweep:
for d in $DOMS; do plesk bin domain -u $d -php_handler_id plesk-php83-fpm; done
for d in $DOMS; do printf '%-20s' $d; curl -sk -o /dev/null -w '%{http_code}\n' -L https://$d; done
The sweep came back with one 200, four 500s, two 503s and one 403. That looked worse than it was: checking in a browser, three sites actually worked fine, and some of those codes came from access rules and holding pages rather than PHP fatals. Lesson learned: a status code is a first filter, not a verdict.
The five that genuinely broke got suspended, and one retired project of mine went dark for good. Here’s the trick I liked most: a Plesk maintenance or suspension page is served without running PHP at all. So the broken sites can stay on the patched 8.3 runtime behind the curtain, rather than being rolled back to an unpatchable 7.4. I’ll fix their themes later, at leisure, and they’re safe in the meantime. With nothing left on 7.4, that whole handler family got disabled too.
Step 5: Were we hit?
Patching closes the door, but it doesn’t tell you whether someone walked through it earlier. These are the checks I ran:
# Session files that touched the upload-progress code path
find /var/lib/php /var/lib/plesk /var/www/vhosts -name 'sess_*' -mtime -4 2>/dev/null \
| xargs -r grep -l 'upload_progress_'
# Crashed PHP workers, a strong sign of exploit attempts
journalctl --since "4 days ago" | grep -iE 'segfault|sigsegv'
# Out-of-memory fatals in per-domain error logs
grep -rliE 'allowed memory size' /var/www/vhosts/system/*/logs/error_log
# POSTs answered with 5xx in the access logs
awk '$6 ~ /POST/ && $9 ~ /^5/ {print $1, $4, $7, $9}' /var/www/vhosts/system/DOMAIN/logs/access_ssl_log
No upload-progress session files, no crashed workers. The access logs showed the usual internet background radiation: bots POSTing to /graphql, xmlrpc.php and a web-IDE path that doesn’t exist on WordPress, mostly answered by holding pages. One honest limitation: access logs don’t record whether a request body was multipart, so this is “no evidence of exploitation” rather than proof.
The out-of-memory check did turn up hits on one of my own sites, which gave me a moment’s pause, since running out of memory is how one of these bugs gets triggered. But every entry came from my own IP address while I was in wp-admin, the errors happened inside WordPress code (so the script was already running), and they were spread over the past month. Harmless, but a nudge to raise that site’s 128 MB memory limit.
What I learned
- The emergency wasn’t the versions I was watching. PHP 8 got patched by the vendor within hours. The risk was the old handlers I’d forgotten were there.
- Enabled-but-unused is still attack surface. Disable PHP versions nobody uses.
- Never skip signature checks in a hurry. Import the key instead.
- Maintenance mode is a security tool. Park broken sites on the patched runtime rather than reverting to the vulnerable one.
- Status codes lie a little. Always look at the actual page.
Start to finish, from reading the advisory to the last log check, took about 40 minutes. I worked through it together with Claude, which researched the advisories, suggested the commands and read the output alongside me (and caught its own misreading of a log filter afterwards, which went straight into the forensics section above). Having a second chair made this a lot calmer than it could have been.
Your turn
If you run anything on PHP, please check it today. On shared hosting, your control panel usually shows the PHP version per site; anything below the fixed releases needs updating, and if your host only offers 7.x for your site, ask them what they’re doing about PHP2Shell. If you manage your own server, update your PHP packages, then go looking for the old versions you’ve forgotten about.