On 21 October 2026 the PHP project shipped emergency releases for two critical bugs in the PHP engine itself, CVE-2026-109618 and CVE-2026-109619, now widely called PHP2Shell. Both can give an unauthenticated attacker remote code execution with a crafted HTTP request, and on Apache with mod_php the attack can fire before any of your application code runs. Patching WordPress, Drupal or your framework does nothing here: the fix is a new PHP binary and a restart of every worker.
This article is for anyone who runs PHP in production, directly or through a host. You will learn which versions fix the bugs, why the server setup matters as much as the version, how to check what your web workers are actually running, how to patch without leaving stale workers behind, and which configuration changes reduce the risk if you cannot update today.
| CVEs | CVE-2026-109618 (OOM corruption) · CVE-2026-109619 (stale global state) |
| Severity | CVSS v4.0 9.2, Critical, for each |
| Fixed in | PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35 |
| Most exposed | Apache mod_php, where a full exploit was validated before application code runs |
| Authentication needed | None |
| Complete mitigation | Updating PHP; configuration changes only narrow specific paths |
What is PHP2Shell?
PHP2Shell is the name used by researchers and press for two memory-safety bugs in how PHP recovers from fatal errors, above all running out of memory, while it handles a request. The PHP advisory describes them in one line each: out-of-memory handling "can interrupt PHP while internal request data is only partially initialized", and request-owned internal state "can survive after its backing memory has been freed", to be used by a later request on the same worker.
Wiz, which reported both, says the two CVEs cover four exploitable bugs, that every PHP version released in the last decade is affected by at least one of them, and that it demonstrated pre-authentication code execution against WordPress, Drupal and Joomla. OpenAI says Calif found one of the flaws with its GPT-5.6 Sol model as part of its Patch the Planet programme; the GitHub advisories list reporters from Wiz and OpenAI among others. The disclosure was coordinated for about two months, then partly leaked: according to Check Point, a Hacker News post on 20 October announced the bugs before a patch existed, and PHP published an advance notice the same evening.
The bug is below your application. Updating WordPress or Laravel does not fix it; a new PHP binary and a restart of every worker does.
Which PHP versions fix CVE-2026-109618 and CVE-2026-109619?
Update to 8.5.12, 8.4.27, 8.3.36 or 8.2.35. Every earlier release on those branches is affected, according to both GitHub advisories. PHP 8.1 and older get no upstream release, but the security team published minimal source backports for 7.4, 8.0 and 8.1, and Linux distributions received the patches before disclosure.
| Branch | Vulnerable | Fixed release | Notes |
|---|---|---|---|
| 8.5 | < 8.5.12 | 8.5.12 | Also exposed through fatal_error_backtraces, new in 8.5 |
| 8.4 | < 8.4.27 | 8.4.27 | Debian trixie: 8.4.27-1~deb13u1 (DSA-6574-1) |
| 8.3 | < 8.3.36 | 8.3.36 | |
| 8.2 | < 8.2.35 | 8.2.35 | End of life on 31 December 2026 |
| 8.1, 8.0, 7.4 | All | None upstream | Vendor backport, the published source backports, or upgrade |
Two packaging details from the advisory matter. First, rpms.remirepo.net was not going to have updates on release day, and PHP urges its users to switch to distribution packages or build from source. Second, a distribution backport may fix the bug without changing the upstream version number, so on a vendor build the version string alone does not prove you are patched; check the vendor's advisory. At the time of writing, the Debian tracker listed trixie's php8.4 as fixed and bookworm's php8.2 as still vulnerable.
How the two bugs work
Both bugs start with a request that makes PHP die badly, usually by exhausting memory_limit, and end with PHP trusting memory that is no longer valid. The difference is when the damage is read: in the same request's cleanup, or in the next request the same worker serves.
CVE-2026-109618: an out-of-memory jump that leaves half-built state
PHP handles an out-of-memory error with longjmp(), abandoning whatever it was doing. The advisory explains that some request memory is reachable before it is fully initialised, so a failed allocation at the wrong moment leaves it invalid, and request shutdown then reads or branches on it. Attackers can reach that moment with multipart form parsing: a body with no usable Content-Length slips past the first post_max_size check, parts can carry unlimited headers, and empty file fields do not count towards max_file_uploads but still allocate arrays. The fix removes the longjmp() entirely, replacing it with a graceful "soft" OOM at the next safe point and an immediate _exit() above a higher threshold.
CVE-2026-109619: stale pointers that outlive the request
A persistent worker keeps global state between requests. The second advisory describes a producer, such as a fatal error, timeout or OOM, that leaves a global pointing at request memory, and a consumer that reads it in the next request before normal initialisation resets it. Two consumers are confirmed: session upload progress, which is on by default and works even if the application never calls session_start(), and on PHP 8.5 the fatal error backtrace that a later diagnostic follows.
- Request A → Worker global keeps a pointer: producer
- Worker global keeps a pointer → Request A memory freed
- Request A memory freed → Request B
- Request B → Pre-init code reads the pointer: consumer
- Pre-init code reads the pointer → Use-after-free, then RCE
That is also why worker recycling and many workers lower the success rate without removing the risk: the advisory says producer and consumer must land on the same process, which an attacker can retry until it happens.
Is PHP-FPM safer than mod_php?
Somewhat, for now. The advisories validated full remote code execution on Apache mod_php with default limits, because it runs request shutdown even when request start-up failed. PHP-FPM kills the child in that case, which blocks the start-up path, but the session upload progress exploit was still validated against FPM.
| Server API | OOM corruption (109618) | Stale global state (109619) |
|---|---|---|
Apache mod_php | Full RCE validated over HTTP, before application code | Confirmed, including .htaccess and php_value paths |
| PHP-FPM | Start-up path blocked; later OOMs may still reach it; no full exploit shown | RCE validated through session upload progress |
| FrankenPHP | Likely susceptible from source analysis | No early consumer found in the default configuration |
| LiteSpeed (LSAPI) | Application-specific exploits likely, not validated | Conditional candidate |
How to check which PHP version your web server runs
Check the binary that serves HTTP, not the one on your PATH. On many hosts the CLI, the FPM service and mod_php come from different packages, and only the last two matter for this bug.
# CLI (useful, but not proof)php -v # PHP-FPM: every installed version, e.g. php-fpm8.3, php-fpm8.4ls /usr/sbin/php-fpm* 2>/dev/null && for f in /usr/sbin/php-fpm*; do "$f" -v | head -1; done # Debian and Ubuntu: installed PHP packages and their versionsdpkg -l 'php*' | awk '/^ii/ && /(fpm|apache2|cgi)/ {print $2, $3}' # Containers: the version inside the running imagedocker exec <container> php -vThen compare the version with the fixed release for its branch. This one-liner runs on any PHP 7.4 or later and exits non-zero when it finds an unpatched upstream build:
php -r '$fixed = ["8.5" => "8.5.12", "8.4" => "8.4.27", "8.3" => "8.3.36", "8.2" => "8.2.35"];$branch = PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;if (!isset($fixed[$branch])) { echo PHP_VERSION, ": no upstream fix on this branch, use a vendor backport or upgrade\n"; exit(2);}$ok = version_compare(PHP_VERSION, $fixed[$branch], ">=");echo PHP_VERSION, $ok ? ": patched\n" : ": VULNERABLE, update to {$fixed[$branch]}\n";exit($ok ? 0 : 1);'How to patch PHP2Shell without leaving vulnerable workers running
Install the fixed package, then restart every process that loaded the old binary. A graceful reload is not enough to be sure: the stale-state bug lives inside long-running workers, and the advisory's own mitigations all say "restart every worker".
Update the package or image
Debian 13 (trixie) with PHP 8.4Shell sudo apt updatesudo apt install --only-upgrade php8.4-fpm php8.4-cli libapache2-mod-php8.4dpkg -l php8.4-fpm | awk '/^ii/ {print $3}' # expect 8.4.27-1~deb13u1For containers, rebuild on a base image at the fixed patch release and redeploy; a pinned tag at an older patch version stays vulnerable no matter how often you restart it. If you build from source, the tarballs are on the PHP downloads page.
Restart every PHP process
restart, not reloadShell sudo systemctl restart php8.4-fpm # every FPM version you havesudo systemctl restart apache2 # mod_php lives inside Apachepgrep -a php-fpm # all PIDs should be newVerify from the outside
Hit a page that prints the version in a non-public route, or read it from the
X-Powered-Byheader if you still expose it, and runcheck-php2shell.shwith the FPM binary. Then turnexpose_phpoff if it was on: Check Point's figures below show how many servers announce their version to scanners.
That last point is not cosmetic. Using Shodan banners, Check Point counted about 884,000 internet-facing hosts advertising PHP 8.1 or older on 20 October, roughly 59% of about 1.5 million hosts that reported a version. Banners are not proof of exposure, but they are what an attacker's first scan reads.
What to change if you cannot update PHP today
Reduce exposure with configuration, but plan the update anyway: the advisory is explicit that these measures "do not make an installation safe". Each setting must apply before PHP parses the request, so put it in php.ini, the FPM pool or the Apache configuration with php_admin_*, never in ini_set() or .user.ini, and restart all workers afterwards.
; Partial mitigations for CVE-2026-109618 / CVE-2026-109619. Not a fix.php_admin_value[memory_limit] = -1 ; pair with a per-worker cgroup limitphp_admin_value[post_max_size] = 8M ; 32M if you need uploadsphp_admin_value[upload_max_filesize] = 4M ; keep below post_max_sizephp_admin_flag[session.upload_progress.enabled] = off; PHP 8.5 only:; php_admin_flag[fatal_error_backtraces] = off# Partial mitigations. Apply, then: a2enconf php2shell && systemctl restart apache2php_admin_value memory_limit -1php_admin_value post_max_size 8Mphp_admin_value upload_max_filesize 4Mphp_admin_flag session.upload_progress.enabled offclient_max_body_size 8m; # Refuse multipart bodies before they reach PHPif ($content_type ~* "multipart/form-data") { return 415;}Why memory_limit=-1? The OOM exploit relies on a predictable per-request limit that the attacker can hit on purpose. Removing it shifts the boundary to the operating system, which is why the advisory asks for a container or cgroup limit that kills only the affected worker, and warns against RLIMIT_AS and RLIMIT_DATA: they make mmap() fail, which is exactly the vulnerable path. Disabling session upload progress blocks the confirmed stale-state route, and the advisory suggests leaving it off after patching if you do not use it.
Two things do not help, according to the advisory: hiding diagnostics with error_reporting, display_errors or @, and relying on log silence. Deprecated or invalid per-request INI settings that emit a warning before start-up are themselves a consumer for CVE-2026-109619, so remove them rather than mute them. In front of PHP, Cloudflare enabled default WAF rules for all plans, including Free, about nine hours before disclosure; that buys time, not safety.
How to tell if someone tried PHP2Shell on your servers
Look below the application. Wiz suggests watching for unusual multipart requests followed by memory-limit errors, bursts of HTTP 500s, unexpectedly large or malformed PHPSESSID cookies in responses, and PHP worker churn you cannot explain. Application logs may show almost nothing, because the attack happens before your code runs.
# Memory-limit fatals in the PHP-FPM logsudo grep -c 'Allowed memory size' /var/log/php8.4-fpm.log # Children killed or exiting abnormallysudo grep -E 'exited on signal|exited with code' /var/log/php8.4-fpm.log | tail # Bursts of 500s on multipart POSTs in the nginx access logawk '$6 ~ /POST/ && $9 == 500' /var/log/nginx/access.log | wc -lThe PHP2Shell timeline
| Date (2026) | Event |
|---|---|
| 19 August | First bug found (Wiz) |
| 20–21 August | Unauthenticated RCE confirmed on Drupal, then WordPress |
| 24 August | Reported to the PHP security team |
| 21 September | PHP acknowledges and starts a coordinated fix |
| 20 October | Details leak in a Hacker News post; PHP publishes an advance notice at 19:33 UTC (Check Point) |
| 21 October, 03:02 UTC | Cloudflare default WAF protection live for all plans (Cloudflare) |
| 21 October, 14:00 UTC | PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35 released (php.net) |
Questions people ask about PHP2Shell
What is PHP2Shell?
PHP2Shell is the name for two critical PHP engine bugs, CVE-2026-109618 and CVE-2026-109619, disclosed on 21 October 2026. Both can let an unauthenticated attacker run code on a server through a crafted HTTP request, without any bug in the application.
Which PHP versions fix PHP2Shell?
PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35. PHP 8.1 and older receive no upstream release; use a vendor backport, the PHP team's source backports for 7.4, 8.0 and 8.1, or upgrade.
Is updating WordPress or my plugins enough?
No. The bugs are in the PHP runtime, below the application. You need a patched PHP binary and a restart of every PHP worker; Cloudflare's write-up makes the same point.
Is PHP-FPM vulnerable, or only mod_php?
Both. Apache mod_php is the most exposed, with full RCE validated before application code runs. On PHP-FPM the session upload progress route to CVE-2026-109619 was also validated.
Can I mitigate PHP2Shell without updating?
Only partly. Setting memory_limit to -1 with a cgroup limit, lowering post_max_size and disabling session.upload_progress block the confirmed paths, but the PHP advisory says no configuration makes an installation safe.
Is PHP2Shell being exploited in the wild?
As of 21 October, watchTowr and Huntress said they had not seen in-the-wild exploitation, though details had leaked and watchTowr had reproduced one bug. Check the CISA Known Exploited Vulnerabilities catalogue for the current status.
Was PHP2Shell found by AI?
Partly. OpenAI says Calif found one flaw with its GPT-5.6 Sol model, and Wiz says it independently found and reported both with AI's help.
Sources and further reading
- PHP: Security advisory, 21 October 2026 and advance notice
- GitHub advisories: GHSA-w8hw-w9fx-j79w, RCE via OOM corruption and GHSA-6wmf-4rv5-p3rp, RCE via stale global state
- Wiz: PHP2Shell technical write-up
- Cloudflare: WAF protection for the PHP vulnerabilities
- OpenAI: coordinated disclosure of critical vulnerabilities
- Check Point: what we know about the leaked PHP vulnerabilities and SecurityWeek on exploitation risk
- Debian security tracker: CVE-2026-109618
Discussion
Join the discussion
Sign in with Google, Apple or an email code to comment and reply.