Loading...
Loading...
PHP2Shell is a PHP vulnerability pair, CVE-2026-109618 and CVE-2026-109619, that lets an unauthenticated request achieve PHP remote code execution, demonstrated on Apache mod_php and conditional on PHP-FPM. Both score 9.2 under CVSS v4.0 and are fixed in PHP 8.5.12, 8.4.27, 8.3.36, and 8.2.35, released 21 October 2026.
Last updated 22 October 2026, from advisories and reporting published through that date.
Someone tried PHP2Shell against an Amazon honeypot on 17 October, four days before public disclosure and three days before the CVE IDs leaked. That's pre-disclosure exploitation: an attempt hit a honeypot before any public patch existed, and Amazon still can't say how the attacker got the details.
Every PHP release before the fixed ones is affected, so "are we on an affected version?" no longer separates one host from another. Your risk now turns on whether this application can meet the conditions the exploit needs and whether the patched runtime is the one answering requests. A third question, whether anything landed before you patched, decides your cleanup. A banner or version-string check answers none of them, and a package-aware scanner only reads what's installed.
PHP2Shell is two CVEs covering use-after-free and related memory-corruption flaws in the PHP engine, which let an unauthenticated request run code on the server. That makes it a PHP RCE that no application update can fix. The bugs sit in the runtime, below WordPress, Drupal, Laravel, and every Composer package, so only a patched PHP binary closes them.
PHP's advisories describe the two by mechanism. CVE-2026-109618, "RCE via OOM corruption," is an out-of-memory error that interrupts PHP while it's building internal request data, after which cleanup treats the half-built data as valid. CVE-2026-109619, "RCE via stale global state," is state owned by one request that outlives its memory and gets used by a later request on the same worker. File tickets against PHP's own IDs, GHSA-w8hw-w9fx-j79w and GHSA-6wmf-4rv5-p3rp. Wiz's write-up maps the CVEs to component bugs differently, and a tracker that mixes the two will confuse whoever closes the ticket.
mod_php is the bad case. PHP demonstrated an unauthenticated request triggering the attack before any application code runs, and its release announcement lists PHP-FPM, php-cgi, FrankenPHP, and LiteSpeed as at risk too.
AI helped on both sides of this one. Calif and Wiz used models to find the flaws, and Wiz credits frontier models with turning a long-standing weakness into a working exploit.
Governments treated it as an emergency. The NSA told organizations that couldn't patch immediately to take systems offline, and government sites in Belgium, the Netherlands, Finland, France, and Germany went dark as a precaution, the Guardian reported on 21 October.
Every PHP release before 8.5.12, 8.4.27, 8.3.36, and 8.2.35 on a supported branch is vulnerable, and Wiz traces at least one of the flaws back to PHP 5.4. So PHP 8.2 is vulnerable until you're on 8.2.35. Branches past PHP end of life get no fixed release at all, only source backports and vendor packages.
| Branch | Fixed release | Security support (php.net) |
|---|---|---|
| 8.5 | 8.5.12 | Until 31 December 2029 |
| 8.4 | 8.4.27 | Until 31 December 2028 |
| 8.3 | 8.3.36 | Until 31 December 2027 |
| 8.2 | 8.2.35 | Until 31 December 2026 |
| 8.1, 8.0, 7.4 | PHP's minimal source backports, or a distribution or LTS vendor package | Ended 31 December 2025, 26 November 2023, and 28 November 2022 |
| 5.4 to 7.3 | No upstream fix; some LTS vendors backport | Ended between 2015 and 2021 |
Any 8.2 release before 8.2.35 is vulnerable. On 8.1 and older, your fix is a backport someone else maintains, which is one more reason to prove it on the running system.
Probably. Apache mod_php hosts face the demonstrated path at default memory limits, while PHP-FPM hosts need the application to cooperate, so how each host runs PHP decides what you fix first. Even the 9.2 score assumes that: the official CVSS v4.0 vector rates Attack Complexity High and Attack Requirements Present.
Red Hat goes further. As of 22 October, its CVE page scores CVE-2026-109618 at 8.1 under CVSS v3.1, against NVD's 9.8 on the same scale, because standard RHEL runs PHP-FPM, where Red Hat judges remote code execution hard to achieve in practice. On FPM, PHP only validated the stale-state path when the application performed a suitable operation after startup.
The exploitation numbers measure different things. Cloudflare counted attempts: tens of thousands in the first 45 minutes, then "hundreds of thousands within two hours, and millions now," as chief executive Matthew Prince put it in an update the Guardian reported on 22 October. Ryan Austin of Calif, who helped find the flaw, said in the same report: "At the moment it doesn't look like exploitation has taken off or been widespread." I read those as two different measurements, attempts versus compromises, though watchTowr and Amazon both report exploitation in the wild.
Attempts are cheap. Whether one lands depends on your SAPI, your limits, your extensions, and what your application does when a request fails. Order the work that way:
In reach, yes. In mechanics, no. Log4Shell (CVE-2021-44228) lived in Log4j, a Java logging library that applications pulled in as a dependency, often buried several layers deep, so the hard part was finding it. PHP2Shell lives in the runtime underneath every PHP application on the host, so the fix is a patched PHP binary and a restart.
Version-string scanners can't judge it. A version string names the branch a package came from, and it can't say whether the fix is in that package or whether that binary is the one answering HTTP requests. Backported builds fool it in both directions, so a clean scan can be wrong and a red one can be fixed already.
Distributions backport fixes without changing the upstream version, so a patched Ubuntu or RHEL package can still report 8.1.x and get flagged, a failure Red Hat warns about on the same CVE page. The worse miss runs the other way. php -v reports the command-line binary, and php -i reads the CLI's php.ini; neither is what your web server loads. CIRCL's 21 October advisory warns against relying "solely on a PHP version string."
Inventories also miss PHP nobody installed on purpose. It ships inside vendor appliances, in hosting control panels such as cPanel and Plesk, in Windows software that bundles its own php-cgi, and in container base images pinned to an old layer.
To test whether PHP is vulnerable on a host, check the package, then the serving binary. Paths and names vary by distribution, so treat these as starting points:
# Debian / Ubuntu
zgrep -E 'CVE-2026-109618|CVE-2026-109619' /usr/share/doc/php8.*-common/changelog.Debian.gz
php-fpm8.3 -v # use your branch; php -v shows the CLI
apache2ctl -M | grep -i php # a loaded PHP module means mod_php
# RHEL / AlmaLinux / Rocky
rpm -q --changelog php-common | grep -E 'CVE-2026-109618|CVE-2026-109619'
php-fpm -v
httpd -M | grep -i php
A loaded module puts that host in the highest-risk group.
They're a stopgap. Each interim control in PHP's advisories covers one path to one CVE, and the advisory for the stale-state flaw says "There is no complete generic configuration mitigation." Use them for hours while the PHP patch rolls out, then revert every one of them.
| Control | What it reduces | What it misses |
|---|---|---|
memory_limit=-1, enforced administratively |
The predictable memory-limit failure behind the known CVE-2026-109618 exploit | Other allocation failures. One request can exhaust the host's memory, so use it only where that's survivable, and never on shared hosting |
session.upload_progress.enabled=0 in php.ini or the FPM pool, then restart every worker |
The confirmed session upload-progress path of CVE-2026-109619 | Other stale-state paths. Set through ini_set() or .user.ini, it applies too late to help |
fatal_error_backtraces=0 (PHP 8.5 only) |
The confirmed fatal-error backtrace path | Every other stale-state path |
| Blocking multipart requests at the proxy | Upload-based triggers | Datadog reproduced CVE-2026-109618 with a standard form-encoded POST |
| WAF rules | Exploit shapes the vendor has seen | Traffic that reaches the origin directly, and request bodies the engine truncates before inspecting |
Don't substitute RLIMIT_AS or RLIMIT_DATA for a memory cap. They cause the allocation failures the first flaw depends on.
Turning display_errors off or suppressing errors with @ doesn't mitigate PHP2Shell either, because PHP reads the stale state before it decides whether to report anything.
If I could do one thing tonight for a host I can't patch, I'd take it off the internet. That's also what the NSA advised, and CERT-EU advises considering isolating internet-facing applications until the fix is verified.
Check the running worker, then the package, then the application. Persistent workers keep the code they loaded at startup, so a host can carry the fixed package on disk and the vulnerable runtime in memory. CIRCL's guidance is to verify that new web requests reach the patched runtime, since updated files change nothing while old workers remain active.
php-fpm -v, apache2ctl -M), not the CLIOn Linux, run this as root right after the package update. It lists processes still running a PHP binary or module that the update replaced:
grep -lE '/(php|php-fpm|php-cgi|libphp)[0-9.]*(\.so)? \(deleted\)$' /proc/[0-9]*/maps
Each /proc/<pid>/maps path it prints belongs to a stale process that needs restarting. Some packages restart services during the upgrade, so an empty result right after the update is possible. Run it as root. An ordinary user can't read other processes' maps, so a non-root run returns empty output that looks exactly like a clean, fully restarted host. After the restart it should come back empty. That proves the old binary is gone from memory. Pair it with the changelog check to show the new one is patched.
Revert the emergency settings while you're there; a memory_limit=-1 left in place becomes the next incident. A patch ticket shouldn't close on the package version alone. Close it on the host checks and a retest, the same argument behind why vulnerability remediation should end at a retest.
Possibly, and the disclosure date can't tell you. Start the look-back no later than 17 October 2026, the earliest attempt anyone has published, and go further back wherever log retention allows. Amazon's honeypot data points the same way: "defenders should not assume the public disclosure date is the earliest date of exploitation."
Coordinated disclosure held until two days before the targeted release date, then broke:
| Date (UTC) | What happened |
|---|---|
| 17 October | Amazon Threat Intelligence logs an exploit attempt against its MadPot honeypots from an anonymization network, per Amazon's write-up |
| 20 October, around 15:00 | The CVE IDs leak with a wrong CVSS score of 10.0, and PHP pulls the release forward a day |
| 21 October, 07:41 | Chainguard publishes patched images more than six hours before the coordinated release, so the fix could be studied early, according to the PHP Foundation |
| 21 October, 14:00 | PHP releases the fixed versions; watchTowr reports exploitation in the wild through its honeypot network the same day |
Datadog Security Labs' 21 October analysis points to evidence in three places. Requests show unusual form submissions, such as very long field names or binary data in form values. The PHP error log shows memory-exhaustion errors during request initialization, before any script has run. Processes show web workers spawning shells.
In EDR or audit data, that last signal is a process-creation event where php-fpm, apache2, httpd, or lsphp is the parent of sh, bash, curl, or wget. Don't wait for a crash or an error code. Datadog's own exploit returned HTTP 200 and left the worker running, so a 200 doesn't clear a request.
Rotate on exposure, not on proof. PHP lists memory disclosure among the confirmed impacts, and Wiz demonstrated it returning worker memory to the client, including passwords, tokens, API keys, and certificates, with no shell involved. Treat what the PHP process could read on an exposed host as disclosed, starting with application secrets such as WordPress salts, Laravel's APP_KEY, and database credentials.
Clean logs won't settle it. PHP's advisory warns that "absence of an error in logs is not proof of safety," default access logs record little of a request body, and a failure can happen before PHP writes anything. The UK's National Cyber Security Centre has told organizations to check for backdoors installed by an attacker. If you find a web shell, a new admin account, or an unfamiliar cron entry, preserve evidence before you rebuild.
Patch fast, then keep testing, because an annual pentest describes your exposure on the day it ran. PHP2Shell went from public release to millions of attack attempts overnight, by Cloudflare's count, and the next PHP vulnerability will run on the same clock. A spring pentest report can't tell you which applications that traffic reaches now. Neither can a scan.
The window of exposure on a flaw like this runs from the moment it's introduced until the fix is verified on the running system. For PHP2Shell, the first half is history you can't change. The second half is yours, and annual testing samples it once a year. That's the gap.
For critical vulnerabilities in internet-facing systems, Amazon's 21 October advice is to measure patch and mitigation timelines in hours, because "a 7-day or 30-day SLA assumes a delay that no longer exists." Hours-scale patching only works if something checks, within hours, that the fix actually held. For PHP application security, that's the argument for continuous testing: it lets you re-ask the question whenever a disclosure lands, an image is rebuilt, or the application ships.
Here's the loop Simbian runs across its Agents, from the first finding to a verified fix.
![]()
None of this would have found PHP2Shell before Calif did, and nothing at the application layer closes the mod_php path, which fires before application code runs. Only the patch does. What the loop gives you is proof of where you're exposed, cover while you can't patch, and evidence that the fix held. Wiz says the patch "eliminates all variants in the patched release." Datadog says "we consider the underlying pattern risky." One speaks to this release, the other to the class of PHP vulnerabilities behind it.
I'd plan for another bug in this class, because the same models that found this one are available to attackers.
On a supported branch, the patch is a package update and a restart. Proving the fix held, and knowing what happened before you patched, is the longer job.
To see which endpoints and failure paths in one of your PHP applications an attacker can reach, start a free pentest with the AI Pentest Agent, no credit card required.
Q: What PHP versions are vulnerable to PHP2Shell, and which are fixed? Every release before PHP 8.5.12, 8.4.27, 8.3.36, and 8.2.35 on its branch is vulnerable, and those fixed releases shipped on 21 October 2026. Branches past PHP end of life, including 8.1, 8.0, and 7.4, rely on PHP's minimal source backports or a vendor package. PHP 8.2 leaves security support on 31 December 2026.
Q: I'm on PHP 8.1 or older. What do I do about PHP2Shell? Install a patched package from your distribution or LTS vendor, or build from PHP's minimal source backports for 7.4, 8.0, or 8.1, and confirm the changelog names both CVE IDs. Upstream has no fixed release for those branches. If neither option exists for your platform, isolate the host and plan the move to a supported branch.
Q: Is PHP-FPM vulnerable to PHP2Shell, or only mod_php? Both are affected, at different levels of risk. PHP demonstrated unauthenticated remote code execution on Apache mod_php before any application code runs. On PHP-FPM, the validated path for CVE-2026-109619 needed a suitable application operation, and for CVE-2026-109618 PHP says no complete FPM exploit has been demonstrated. Patch both.
Q: Does updating WordPress fix the PHP vulnerability? No. The flaws are in the PHP runtime, not in WordPress, plugins, or Composer packages, so application updates leave them in place. You need the fixed PHP release or your vendor's patched package, a restart of every PHP worker, and rebuilt container images. On managed hosting, ask the provider which PHP build serves your site.
Q: Is disabling upload progress enough to stop PHP2Shell? It isn't. Setting session.upload_progress.enabled to 0 closes only the confirmed upload-progress path of CVE-2026-109619, and only when it's set in php.ini or the FPM pool with every worker restarted. Other stale-state paths stay open, and CVE-2026-109618 needs its own controls. Updating PHP is the only complete fix.
Q: Is PHP vulnerable to PHP2Shell if my scan is clean? Possibly. A scanner that reads version strings can't tell whether the fix is in the package or whether that binary serves web requests, so backported builds produce false clears and false alarms. Confirm the changelog names both CVE IDs, check the serving binary with php-fpm -v or apache2ctl -M, and restart every worker.
Q: Does a WAF protect against PHP2Shell? Only partly, and only for traffic that passes through it. WAF rules match exploit shapes vendors have already seen, so they miss requests that reach the origin directly and request bodies the engine truncates. Datadog reproduced CVE-2026-109618 with a standard form-encoded POST, so blocking multipart uploads isn't enough either.
Q: Has PHP2Shell been successfully exploited? Accounts differ. Amazon says PHP2Shell is now broadly exploited in the wild, while Calif's Ryan Austin said exploitation didn't look widespread. Amazon also recorded an attempt on 17 October 2026, four days before disclosure, and says it's unclear whether its pre-release attempts would have succeeded, so look back to 17 October at minimum.
Q: Should I rotate secrets after PHP2Shell? Yes, on any host that was exposed before patching. PHP lists memory disclosure under CVE-2026-109619 (Wiz files it under CVE-2026-109618; one release fixes both), and it can return worker memory to an attacker without leaving a web shell. Prioritize the passwords, tokens, API keys, and certificates the PHP process could read, starting with application secrets and database credentials.