On 21 October the PHP project released versions 8.5.12, 8.4.27, 8.3.36 and 8.2.35 to fix two vulnerabilities that, within days, got the name PHP2Shell. They are flaws in the interpreter itself, not in a framework or a CMS, so they affect any PHP installation that serves web requests.
Reading the advisories, I realised that for defenders the technical part is the simple one. There’s a patch, and the temporary mitigation is one line of configuration. The real problem comes earlier: knowing where PHP is running. Most organisations don’t have that answer ready, and I suspect the same goes for many of those that think they do.
What the two CVEs are#
Here’s what I understood from the PHP advisories, the Dutch NCSC repository and Qualys’ analysis. I haven’t reproduced the exploit, and I don’t intend to on systems that aren’t mine.
- CVE-2026-109618: memory corruption in out-of-memory handling. When a request runs out of memory, PHP can abandon it with structures only partly initialised, and request shutdown then treats them as valid.
- CVE-2026-109619: global state that belongs to one request and survives into the next request on the same worker (a use-after-free). One of the paths goes through the session upload progress feature. Besides code execution, it can leak memory, meaning passwords, tokens, API keys.
Both are rated 9.2 under CVSS v4.0 and allow unauthenticated remote code
execution with the privileges of the web server user. The vector says
AC:H/AT:P: exploitation depends on the build, the allocator and worker
scheduling, so it isn’t reliable on the first try. On Apache with mod_php,
which the PHP project considers the highest-risk setup, RCE has been
demonstrated with default limits and one request per attempt. PHP-FPM,
php-cgi, FrankenPHP and LiteSpeed are affected too.
On real-world exploitation: Qualys reports that Cloudflare has seen attack traffic, including attempts to get around its rules, and that proofs of concept are circulating on GitHub. I also read about a campaign with dozens of compromised servers, but it comes from a single vendor and I couldn’t verify it, so I’m leaving it out.
The easy part: patch and mitigation#
The fixed versions are 8.5.12, 8.4.27, 8.3.36 and 8.2.35. For 7.4, 8.0 and 8.1, which are out of support, PHP only published minimal backports; for distribution packages, what counts is the advisory from Debian, Ubuntu, Red Hat and the rest.
If you can’t update right away, on 23 October PHP recommended disabling session upload progress until the next bugfix release:
; php.ini, until the next maintenance release
session.upload_progress.enabled = 0After the change, or after updating, restart every worker. Qualys adds more
settings (limits on post_max_size, max_input_nesting_level,
fatal_error_backtraces=0 on 8.5, pm.max_requests=1 on FPM if you can afford
the cost), but the PHP project is clear that none of these is a complete fix.
So far, nothing different from a hundred other bulletins. The next step is the one I care about.
The number I didn’t expect#
The Dutch NCSC repository keeps a list of products that ship PHP and their status with respect to PHP2Shell. This morning, 30 October, it read:
| Status | Products |
|---|---|
| Vulnerable | 217 |
| Fixed | 165 |
| Under investigation | 345 |
| Not affected | 201 |
| Unknown | 1 |
| Total tracked | 929 |
It includes the web interfaces of Check Point firewalls, Cisco Expressway, the Broadcom Carbon Black App Control server, VitalPBX phone systems. Sophos released a hotfix for its firewalls with additional WAF rules. These are all products where PHP is invisible: nobody files them under “PHP server”, and the administrator often can’t update the interpreter by hand and has to wait for the vendor.
More than a third of the list is still “under investigation” nine days after the patch. For hundreds of products, not even the vendor can say yet whether they’re exposed.
Then there’s the Qualys figure, which should be taken for what it is (one vendor’s telemetry on its own customers), but gives an order of magnitude: 167 million PHP installations observed, about 110 million of them on unsupported versions and 44 million on supported branches that weren’t up to date. In the first twelve hours, about 199,000 showed up as patched.
What inventory means for PHP2Shell#
I used to think of asset inventory as a list of machines. This vulnerability made me see it as a list of questions, and a list of hosts only answers the first one.
- Where does PHP run? Not just on application servers: appliances, forgotten VMs in the DMZ, container images, NAS boxes, phone systems, internal tools another team installed years ago.
- Which SAPI?
mod_phpis the worst case, PHP-FPM has different conditions. Without this you can’t set priorities. - Is it exposed? PHP reachable from the internet and PHP on an isolated management network don’t carry the same urgency.
- Who updates it? The distribution package, a container’s base image, a hand-compiled binary, or the appliance vendor. Those are four different processes on four different timelines.
- Who owns it? If there’s no name next to the system, nobody applies the patch.
There’s also a version trap. Distributions backport fixes without changing the
main version number: a Debian PHP 8.2 can be fixed even if the string doesn’t
say 8.2.35. An inventory that just compares php -v against the list of safe
versions gives you false positives on one side and false confidence on the
other. You need to look at the package version and the distribution’s
advisory.
Where I’d start on a single Linux host#
These are the commands I’d use to answer the questions above on one machine. They don’t replace an inventory tool, but they help you understand what that tool should be collecting.
# Which PHP packages are installed (package version, not just PHP's)
dpkg -l | grep -i php # Debian/Ubuntu
rpm -qa | grep -i php # RHEL and derivatives
# PHP binaries outside the package manager (hand-compiled, bundled in products)
find / -xdev -type f -name 'php*' -perm -u+x 2>/dev/null
# Does Apache load mod_php?
apachectl -M 2>/dev/null | grep -i php
# Running PHP processes and listening ports
ps aux | grep -E 'php-fpm|php-cgi|frankenphp|lsphp' | grep -v grep
ss -ltnp | grep -i php
# Mitigation status
php -i | grep -i 'session.upload_progress.enabled'With containers the problem moves to images: you need to check which PHP version each image in use contains, and an image scanner does that better than a command run by hand.
I’d ignore the X-Powered-By header. Many setups disable it, and its absence
doesn’t mean PHP isn’t there.
Final thoughts#
Patching PHP2Shell takes ten minutes per server. Finding out which servers exist, and which products have PHP inside, can take weeks, and during those weeks the exploit is out there for anyone.
That’s why asset inventory looks to me like the least glamorous and most decisive control in vulnerability management. When a critical CVE lands in a component this widespread, how fast an organisation responds depends almost entirely on work done beforehand: knowing what it has, how it’s configured, who’s responsible for it and who can update it.
Two things remain open. The first is how you really inventory the software inside appliances, where you have no shell and depend on the list the vendor publishes; the honest answer, for now, is that I don’t know. The second is the role of SBOMs: in theory they solve exactly this problem, in practice I’ve yet to see an appliance vendor ship a usable one. It’s one of the topics I want to study in the coming weeks.
Sources#
- NCSC-NL, php2shell: operational information (product list as of 30 October 2026)
- Qualys, PHP2Shell: Find, Mitigate, and Patch Now
- Sophos, Advisory: PHP2Shell Vulnerabilities (CVE-2026-109618, CVE-2026-109619)
- Akamai, Understanding PHP2Shell: Everything You Need to Know
- watchTowr, CVE-2026-109618: Exploited Remote Code Execution in PHP
Originally written in Italian, translated with AI assistance and reviewed by the author.