PHP2Shell (CVE-2026-109618, CVE-2026-109619): am I affected, and how do I fix it?
Two unauthenticated remote code execution flaws in the PHP runtime itself, rated CVSS 9.2, that fire before a single line of your application runs. This is the practical page: the fixed versions, a safe self-check that reads what your web server actually runs, distro and Docker status, end-of-life options, and the mitigations that help versus the ones that only feel like they do.
Planck Proof · Offensive Security Team · October 21, 2026 · Updated 21:00 UTC · 12 min read
PHP2Shell in 30 seconds
PHP2Shell is the name researchers and the press gave to two memory-safety flaws in the PHP interpreter, CVE-2026-109618 and CVE-2026-109619. Both are rated CVSS v4 9.2 Critical, need no authentication, and, in the PHP project's own words, include “a demonstrated exploit where an unauthenticated request can trigger the attack before any application code runs.” Every PHP installation serving web requests is in scope. The only complete fix is updating PHP and restarting every PHP process.
| PHP branch | Fixed in | Notes |
|---|---|---|
| 8.5 | 8.5.12 | Also set fatal_error_backtraces=0 if you cannot patch yet |
| 8.4 | 8.4.27 | Recommended upgrade target |
| 8.3 | 8.3.36 | Security-only branch |
| 8.2 | 8.2.35 | End of life December 31, 2026; plan the move now |
| 8.6 (pre-release) | 8.6.0 RC4 | Earlier RCs are vulnerable |
| 7.4, 8.0, 8.1 | Source backport only | No binary release from php.net; see the end-of-life section |
| 5.x to 7.3 | No fix | Vulnerable code reportedly dates to 5.4.0; commercial support only |
Sources: the php.net security announcement and the two GitHub advisories, GHSA-w8hw-w9fx-j79w and GHSA-6wmf-4rv5-p3rp.
What the two bugs actually are
The two CVEs are distinct bugs that share a CVSS vector (AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H) and a list of CWEs led by CWE-416, use after free. At a defender's level of detail:
- CVE-2026-109618, RCE via out-of-memory corruption. When PHP hits its memory limit it aborts the request with a non-local jump. If that happens while request state is only partly initialized, the shutdown path later treats the half-built memory as valid. The advisory names multipart request parsing edge cases and large application allocations as triggers. The fix replaces the immediate abort with a delayed “soft” out-of-memory error at the next safe point, backed by a hard exit at a higher limit.
- CVE-2026-109619, RCE via stale global state. Some global state owned by one request (error, session, object and extension state) can survive into the next request on the same worker after a fatal error, an out-of-memory error, or a diagnostic raised late in shutdown. The next request then uses freed memory before it is reset. Session upload-progress handling, which is on by default and does not need
session_start(), and INI diagnostics applied before request startup are the documented consumers. Memory disclosure is a confirmed impact alongside code execution.
The PHP team credits Ryan of Calif.io, Wiz, and OpenAI as reporters. Wiz's write-up describes four underlying bugs grouped under the two CVEs and reports unauthenticated RCE against stock WordPress, Drupal and Joomla container images; Sansec reports the same against Magento. Those are researcher results, not public exploits.
Am I affected? The decision table
Being on an unpatched version means you are affected. What changes with your setup is how urgent it is. The PHP advisory scopes it by SAPI, the interface between PHP and your web server:
| How PHP runs | PHP_SAPI value | Risk per the advisory |
|---|---|---|
| Apache mod_php | apache2handler | Highest. Full unauthenticated RCE demonstrated for both CVEs, before app code runs. Patch first. |
| PHP-FPM (nginx, Apache proxy, Caddy) | fpm-fcgi | Affected. 109618 susceptible per source analysis; a session-lifecycle RCE for 109619 was validated against FPM. Needs two requests landing on the same worker, which persistent pools provide. |
| php-cgi, FrankenPHP, LiteSpeed (LSAPI) | cgi-fcgi, frankenphp, litespeed | In scope. FrankenPHP and LSAPI rated “likely” affected. |
| CLI only (cron, queue workers, no HTTP) | cli | Not the web-request attack surface the advisory describes. Patch on your normal cadence. |
| Shared or managed hosting | Varies | Your host patches the runtime. Ask them, and confirm with the self-check below. |
How to check: the PHP your web server really runs
The most common mistake today is running php -v in a shell and calling it done. That reports the CLI binary. Your web traffic may be served by a different PHP-FPM pool, a different container image, or mod_php inside Apache. Check what the web server executes.
1. A safe self-check script. It is read-only: it prints the version, the SAPI and the settings that matter, and compares the version against the upstream fix. Drop it in your web root under a random filename, ideally behind your existing auth or an IP allowlist, load it once, and delete it.
<?php
// php2shell-check.php: read-only exposure check. Delete after use.
header('Content-Type: text/plain');
$fixed = ['8.2' => '8.2.35', '8.3' => '8.3.36', '8.4' => '8.4.27',
'8.5' => '8.5.12', '8.6' => '8.6.0RC4'];
$branch = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION;
echo "PHP_VERSION ", PHP_VERSION, "\n";
echo "PHP_SAPI ", PHP_SAPI, "\n";
if (isset($fixed[$branch])) {
$ok = version_compare(PHP_VERSION, $fixed[$branch], '>=');
echo "upstream fix ", $fixed[$branch], $ok ? " -> OK\n"
: " -> BELOW FIX (unless your distro backported it: check the changelog)\n";
} else {
echo "upstream fix none for $branch (end of life)\n";
}
foreach (['session.upload_progress.enabled', 'fatal_error_backtraces',
'memory_limit', 'post_max_size', 'upload_max_filesize'] as $k) {
printf("%-32s %s\n", $k, var_export(ini_get($k), true));
}
2. From the shell, these benign commands answer the same questions across a host:
# Is mod_php loaded into Apache? (highest risk)
apachectl -M 2>/dev/null | grep -i php
# Version of each FPM binary actually installed (not just the CLI)
ls /usr/sbin/php-fpm* 2>/dev/null | xargs -I{} sh -c '{} -v | head -1'
# Settings the FPM pool really uses
php-fpm8.3 -i | grep -E 'upload_progress.enabled|fatal_error_backtraces|memory_limit|post_max_size'
# Installed packages
dpkg -l 'php*' | awk '/^ii/{print $2, $3}' # Debian / Ubuntu
rpm -qa 'php*' | sort # RHEL / Alma / Rocky / SUSE
# Distros backport: look for the CVE in the package changelog, not the version
zgrep -E 'CVE-2026-10961[89]' /usr/share/doc/php8.3-fpm/changelog.Debian.gz
rpm -q --changelog php-fpm | grep -E 'CVE-2026-10961[89]'
# After upgrading: any process still running the old, deleted PHP binary?
sudo grep -l 'php.*(deleted)' /proc/[0-9]*/maps 2>/dev/null
# Containers: check every image you run, not the one you think you run
docker run --rm <image> php -v
Adjust the FPM binary and package names to your branch. On Ubuntu with Pro, sudo pro fix CVE-2026-109618 resolves and applies the right package. For fleets, Tenable published plugins 115719 to 115722, and Rapid7 ships a Velociraptor artifact (Generic.Applications.PHPInventory) that inventories PHP installs across endpoints.
Distro packages: why the version number lies
Debian, Ubuntu, Red Hat and SUSE backport security fixes into the version they shipped, so a fully patched Ubuntu 24.04 still reports PHP 8.3.6, not 8.3.36. Trust the package changelog. Status as of the evening of October 21 (it changes hourly, follow the links):
| Platform | Status |
|---|---|
| Debian 13 / 12 | Fixed: php8.4 8.4.27-1~deb13u1 (DSA-6574-1), php8.2 8.2.35-1~deb12u1 (DLA-4848-1). Debian 11 through Freexian ELTS. Tracker |
| Ubuntu | Fixed: 26.04 8.5.4-0ubuntu1.5, 24.04 8.3.6-0ubuntu0.24.04.13, 22.04 8.1.2-1ubuntu2.28 (USN-8948-1/-2). 20.04 and older through Ubuntu Pro ESM. Ubuntu |
| RHEL | Bulletin RHSB-2026-013 published, rated Important; fixed packages not yet listed. Red Hat |
| SUSE | SLES 15 SP7 fixed (php8 8.3.36); SLES 16 in QA; other products still listed as affected. SUSE |
| Amazon Linux 2023 | Not yet fixed; AWS says it is working on it. Issue #1148 |
| Homebrew | Fixed: php 8.5.12, [email protected] 8.4.27, [email protected] 8.3.36, [email protected] 8.2.35 |
| Plesk | Linux PHP 8.2 to 8.5 fixed; Windows and 7.4 to 8.1 builds in progress. Plesk KB |
| Heroku | Fixed runtimes available; you must redeploy to pick them up. Changelog |
| CloudLinux / TuxCare | alt-php and ELS builds rolling out across old branches. CloudLinux |
The php.net announcement also warns that the Remi repository would not have updates on release day; check Remi's status before relying on it.
Docker and Kubernetes: pull is not enough
The official php images received patched tags on October 21: 8.2.35, 8.3.36, 8.4.27 and 8.5.12. The 8.1 and 7.4 tags are end of life and were not rebuilt, so anything built FROM php:8.1 stays vulnerable no matter how often you pull. For the rest:
- Rebuild your application images with
docker build --pullso the new base layer is actually used, then redeploy. A cached base layer is the most likely way a “patched” cluster stays vulnerable. - Recreate the running containers or roll the Kubernetes deployment. A pulled image does nothing for a pod that is already running.
- Pin to the patch tag (
php:8.4.27-fpm) rather than a floating tag for the next week, so you can prove which version is in production. - Search your Dockerfiles, Helm charts and CI templates for
FROM php:and every vendor image that embeds PHP. The Dutch NCSC maintains a list of affected products that ship PHP inside them.
End-of-life PHP (7.4 to 8.1) and shared hosting
This is where PHP2Shell hurts most. W3Techs puts PHP on 69.3 percent of websites whose server-side language is known, and 34.7 percent of those sites run PHP 7 or PHP 5, major versions with no official binary fix. Count PHP 8.0 and 8.1 as well and the unpatchable share is larger still. Your options, from best to worst:
- Upgrade to PHP 8.4 or 8.5. Skip 8.2, which leaves support at the end of the year.
- Use a distro or commercial backport: Ubuntu Pro ESM, Debian ELTS through Freexian, TuxCare ELS, or CloudLinux alt-php all ship patched builds for older branches.
- Build from the official source backports. The PHP team published minimal branches for 7.4, 8.0 and 8.1. You own the build and every future fix.
- Isolate it. If none of the above is possible this week, take the application off the public internet, as CERT-EU recommends, until it is patched.
On shared WordPress or cPanel hosting, you cannot patch the runtime yourself. Use the self-check above to see the version and SAPI your site actually gets, and if it is below the fix, open a ticket and ask for a date.
Why your API's authentication will not save you
This is the part most coverage skips, and it matters to every team running a PHP API backend. In a Laravel, Symfony or Slim API, authentication is application code: a middleware that checks a bearer token or session before the controller runs. PHP2Shell does not wait for it. PHP parses a multipart/form-data POST body and runs session upload-progress handling at request startup, before your front controller is even loaded. A request with no token at all reaches the vulnerable code path on any route that hits PHP, including the 401 your middleware was about to return.
So “all our endpoints require auth” is not a mitigation. What does reduce exposure is anything that rejects the request before it reaches PHP: an API gateway or reverse proxy that enforces authentication at the edge, a web server rule that refuses multipart bodies on routes that never accept uploads, a WAF in front of an origin that cannot be reached directly. None of those is a fix, but they are the difference between “reachable by the internet” and “reachable by authenticated clients.” Map which of your PHP routes are reachable unauthenticated today; that is the order to patch and verify in.
Mitigations that help, and myths that do not
The PHP project is blunt: “There are no complete mitigations, updating is the only safe choice.” If you need hours, not days, these are the stopgaps it documents, and the ones people get wrong:
- Helps, for CVE-2026-109619: set
session.upload_progress.enabled=0inphp.ini, the FPM pool or server config (not withini_set()or.user.ini), then restart every worker. On PHP 8.5, also setfatal_error_backtraces=0. Remove per-requestphp_valuesettings in.htaccessandPHP_VALUEfastcgi parameters. - Helps, for CVE-2026-109618: set
memory_limit=-1throughphp_admin_valueso applications cannot lower it, cappost_max_sizeat 8M (32M if you need uploads), enforce request-size limits at the front end, and use a cgroup or container memory limit as the real ceiling. - Backfires: do not use
RLIMIT_ASorRLIMIT_DATAas the memory boundary. The advisory warns they can trigger the vulnerable path. - Myth: “turn off upload progress and you are safe.” It is the most repeated advice today and it only addresses one of the two CVEs. CVE-2026-109618 is untouched.
- Myth: “the WAF covers it.” Cloudflare shipped rules to every plan, including Free, and other WAF vendors followed. Cloudflare's own CTO called them a stopgap. They do nothing if your origin is reachable directly, and attackers were already changing payloads to slip past the first version.
- Myth: “we updated WordPress.” The flaw is in the interpreter. Drupal's security team says it plainly: updating PHP is the only complete mitigation.
- Easy to miss: restart properly. mod_php lives inside Apache, so it needs a full restart, not a graceful reload. FPM pools need every worker replaced. The
/proccheck above shows any process still running the deleted binary.
Exploitation status: attempts yes, compromises unconfirmed
Reports today look contradictory but describe different things. Attack traffic is confirmed: Cloudflare reported early attacks against customers from about 07:43 UTC, and watchTowr sees exploitation attempts against both CVEs on its honeypots, adding that nobody has succeeded so far. Successful compromise is not publicly confirmed: Huntress had seen none across its partner base, and CISA had observed none at publication. Proof-of-concept repositories are circulating on GitHub; the PHP security team said it found no functional exploit among them at release, and CERT-EU notes initial PoCs “have begun to circulate.” With a researcher demo video already leaked and patched binaries available to diff, assume a reliable exploit is a matter of days.
Two numbers circulating are wrong. The leaked CVSS 10.0 score is not the official rating, which is 9.2. And the claim that “every version since PHP 5” is exploitable is not in the official advisory: a member of the PHP security team notes that the mechanism dates to the old allocator but no exploit has been shown on those versions. Treat old versions as vulnerable anyway; they have no fix.
Signs of compromise to look for
No vendor has published indicators of compromise yet. The behavioral signals researchers point to:
- Web logs: bursts of
multipart/form-dataPOSTs followed by HTTP 500s or memory-limit errors, oversized or malformedPHPSESSIDcookies, multipart bodies on routes that never accept uploads. - Process: PHP workers (php-fpm, php-cgi, httpd, apache2) restarting unexpectedly, or spawning shells, interpreters,
curl,wget,nc, or recon commands likeid,whoamianduname. - Filesystem: new PHP files in writable web directories, new cron entries, new local accounts or SSH keys.
Corelight published Zeek and Suricata detections; its authors warn the body-marker rule is noisy. If you were exposed and unpatched between the leak on October 20 and your upgrade, review that window specifically.
Timeline
- August 11, 2026: Calif.io reports an unconditional RCE to PHP; OpenAI independently reports the same root cause the same day.
- August 24 and September 18: Wiz and OpenAI report additional attack vectors.
- October 20: the CVE IDs leak publicly ahead of coordinated disclosure, with an incorrect 10.0 score. PHP pulls the release forward from October 22 to October 21. A researcher demo video is briefly published.
- October 21, 07:41 UTC: patched container images from one vendor appear hours before the embargo lifts.
- October 21, about 14:00 UTC: coordinated release of PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35, per the PHP Foundation's account.
After the patch: the bugs a version bump cannot fix
PHP2Shell is a version problem, and version problems have a satisfying end: a check that returns OK. You do not need a pentest to find it, and you should not wait for one. Run the check, patch, restart, verify.
The lesson worth keeping is the one from the API section. The attack surface that mattered today sat below your authentication layer, where no endpoint list or auth middleware could see it. The same is true of the bugs that remain after the patch: an object one tenant can read that belongs to another, a function a low-privilege role can call, a token that authorizes more than it should. No version number tells you those are fixed. That is the layer Operator tests: it reads your OpenAPI or GraphQL schema, exercises every operation as each real role, and reports a finding only with a runnable proof an engineer can replay. If this week's scramble left you unsure what your PHP API exposes and to whom, the free first scan maps that surface. Start with API authorization testing or our API security checklist.
Changelog
- 2026-10-21 21:00 UTC: First published. Status reflects vendor pages as of the evening of October 21.
PHP2Shell questions
How do I check if my server is vulnerable to PHP2Shell?
Check the PHP your web server actually runs, not the CLI. Serve a temporary, access-restricted script that prints PHP_VERSION and PHP_SAPI, or run php-fpm -v for each pool. Upstream PHP is fixed in 8.5.12, 8.4.27, 8.3.36 and 8.2.35. Distribution packages often keep an older version number with the fix backported, so on Debian, Ubuntu, RHEL or SUSE check the package changelog for CVE-2026-109618 and CVE-2026-109619 instead of trusting the version string.
Which PHP versions fix CVE-2026-109618 and CVE-2026-109619?
PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35, released October 21, 2026, plus PHP 8.6.0 RC4. The PHP project also published minimal source-only backports for 7.4, 8.0 and 8.1, with no binary releases. Versions older than 7.4 have no fix from the PHP project.
Is PHP-FPM affected by PHP2Shell, or only mod_php?
Both. The PHP advisory covers every installation serving web requests: PHP-FPM, mod_php, php-cgi, FrankenPHP and LiteSpeed. mod_php carries the highest risk because the PHP team demonstrated a full unauthenticated exploit against it before any application code runs. PHP-FPM is susceptible per source analysis, and a session-lifecycle RCE path for CVE-2026-109619 was validated against FPM. Patch either way.
My server runs PHP 8.1 or older. Is there a PHP2Shell patch?
Not from php.net as a binary. The PHP team published source-only backport branches for 7.4, 8.0 and 8.1. Ubuntu Pro ESM, Debian ELTS through Freexian, TuxCare ELS and CloudLinux alt-php ship patched builds for many older branches. The durable fix is moving to PHP 8.4 or 8.5; PHP 8.2 itself reaches end of life on December 31, 2026.
Does updating WordPress, Drupal or Laravel fix PHP2Shell?
No. PHP2Shell is a flaw in the PHP runtime, not in any application or framework, and it can be triggered before your application code runs. Drupal's security team states that updating PHP is the only complete mitigation. Update the PHP interpreter, then restart every PHP worker or the Apache process that embeds mod_php.
Does disabling session.upload_progress protect against PHP2Shell?
Only partly. Setting session.upload_progress.enabled=0 in php.ini or the FPM pool reduces exposure to CVE-2026-109619, but it does nothing for CVE-2026-109618, the out-of-memory corruption bug. The PHP project says there are no complete mitigations and that updating is the only safe choice.
Related
API security best practices
The 2026 checklist for the layer a runtime patch does not touch: authorization, tenancy, and object-level access.
Read → GuideAPI authorization testing
The full authorization matrix: roles, tenants, and object properties, tested with real accounts rather than one logged-in session.
Read → BlogHow agentic pentesting proves an exploit
Why a finding without a replayable proof is a guess, and what proof looks like on every finding.
Read →Patched PHP2Shell? Now see what your API exposes
Give Operator your API spec and a token per role. It tests every operation across identities and proves any object or function one user can reach that belongs to another. The first scan is free.