The PHP version number can't tell you whether a server is safe from PHP2Shell (CVE-2026-109618 and CVE-2026-109619), in either direction. The package changelog and the running process can.
I spent October 21 verifying every PHP version on infrastructure I manage, from 8.5 down to a 5.6 install kept alive for a legacy client. These are the gotchas that actually mattered.
1. Backported fixes keep the old version number
PHP only shipped official fixes for 8.2 through 8.5. But Debian, Ubuntu, Red Hat, and Ondrej Sury's repo (the one a lot of production servers use to pick their PHP version) all shipped the fix for older branches too. Sury's patched 8.1 still reports 8.1.34. Ubuntu 24.04's patched build still reports 8.3.6.
So php -v proves nothing. The changelog does:
zcat /usr/share/doc/php8.1-fpm/changelog.Debian.gz | grep -m2 -B2 '109618'
No output, no fix. Swap in your actual package name.
The flip side: not every vendor backports, and backports for end-of-life branches are a favor, not a plan. Keep 8.1 and older on a migration schedule.
2. Installed is not running
PHP2Shell lives in long-running workers. Upgrading the package does nothing for a PHP-FPM master or an Apache process that already loaded the old code. Apache with mod_php is the worst case, since the PHP library sits inside Apache and survives until Apache fully restarts. A graceful reload isn't enough.
Linux will tell you exactly which processes are still running replaced code:
grep -l 'libphp.*(deleted)' /proc/*/maps 2>/dev/null
Empty output is what you want. Apply the same idea to readlink /proc/<pid>/exe for each FPM master.
3. Header scans measure hygiene, not exposure
Some people scanned hundreds of sites this week and published lists of what was "confirmed vulnerable." The only external version signal is the X-Powered-By header, which PHP's production config turns off. I enabled expose_php for two minutes on one pool of my own site to show it:
$ curl -sI https://www.zadrosolutions.com | grep -i x-powered-by
x-powered-by: PHP/8.5.12
A header showing a fixed release is decent evidence. A header showing an older one proves nothing, because of gotcha #1. And hardened servers show nothing at all.
4. No changed files doesn't mean nothing leaked
One of the bugs is a memory disclosure. Reclaimed heap memory can come back URL-encoded in a PHPSESSID cookie, even on apps that never start a session. No files written, no webshell.
So a clean file scan says nothing about what an attacker may have read from worker memory. If an internet-facing app held payment keys, cloud credentials, or database passwords and ran unpatched PHP after October 17 (the first recorded exploit attempts), rotate them.
5. The follow-up fix needs the right config file
PHP disclosed a related bug, CVE-2026-115560, on October 23, with a fix coming in the next routine release. The interim mitigation is session.upload_progress.enabled = 0. It has to go in the FPM php.ini or in each pool file, because it applies before request parsing. Setting it through ini_set() or .user.ini does nothing.
Don't forget the PHP inside your appliances
Software firewalls like OPNsense and pfSense run their web GUIs on PHP, and those GUIs often listen on every internal VLAN. Patch them, then restrict the GUI to your management network.
The full walkthrough
The complete version, with distro package details, the log-hunting greps for the exploit's fingerprint, and a verification checklist, is here: How to Prove Your PHP Is Actually Patched.
Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.