System architecture

PHP2Shell (CVE-2026-109618, CVE-2026-109619): which PHP servers are exposed and how to patch today

PHP2Shell is two pre-auth RCE bugs in PHP itself. Which versions fix them, which servers are most exposed, and what to set if you cannot update today.

Kemo
10 min read
A deep crack running along the centre line of a desert road, seen from ground level.
On this page
  1. What is PHP2Shell?
  2. Which PHP versions fix CVE-2026-109618 and CVE-2026-109619?
  3. How the two bugs work
  4. Is PHP-FPM safer than mod_php?
  5. How to check which PHP version your web server runs
  6. How to patch PHP2Shell without leaving vulnerable workers running
  7. What to change if you cannot update PHP today
  8. How to tell if someone tried PHP2Shell on your servers
  9. The PHP2Shell timeline
  10. Questions people ask about PHP2Shell
  11. Sources and further reading

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.

CVEsCVE-2026-109618 (OOM corruption) · CVE-2026-109619 (stale global state)
SeverityCVSS v4.0 9.2, Critical, for each
Fixed inPHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35
Most exposedApache mod_php, where a full exploit was validated before application code runs
Authentication neededNone
Complete mitigationUpdating PHP; configuration changes only narrow specific paths
PHP2Shell at a glance, from the PHP security advisories of 21 October 2026

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.

BranchVulnerableFixed releaseNotes
8.5< 8.5.128.5.12Also exposed through fatal_error_backtraces, new in 8.5
8.4< 8.4.278.4.27Debian trixie: 8.4.27-1~deb13u1 (DSA-6574-1)
8.3< 8.3.368.3.36
8.2< 8.2.358.2.35End of life on 31 December 2026
8.1, 8.0, 7.4AllNone upstreamVendor backport, the published source backports, or upgrade
Fixed PHP releases and what to do on each branch, per the PHP advisory of 21 October 2026

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.

One PHP worker processRequest AOOM or fatal errorWorker global keeps a pointerRequest A memory freedRequest Bsame workerPre-init code reads the pointerUse-after-free, then RCEproducerconsumer
  • 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
CVE-2026-109619 needs two requests on the same worker: one that leaves a stale global behind, and one that reads it before PHP resets it.

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 APIOOM corruption (109618)Stale global state (109619)
Apache mod_phpFull RCE validated over HTTP, before application codeConfirmed, including .htaccess and php_value paths
PHP-FPMStart-up path blocked; later OOMs may still reach it; no full exploit shownRCE validated through session upload progress
FrankenPHPLikely susceptible from source analysisNo early consumer found in the default configuration
LiteSpeed (LSAPI)Application-specific exploits likely, not validatedConditional candidate
Exposure by server API, as described in GHSA-w8hw-w9fx-j79w and GHSA-6wmf-4rv5-p3rp

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.

find the PHP your web server actually usesShell
# 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 -v

Then 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:

check-php2shell.shShell
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);'
Run it with the same binary that serves requests. A vendor backport can be patched while still reporting an older version.

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".

  1. 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~deb13u1

    For 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.

  2. 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 new
  3. Verify from the outside

    Hit a page that prints the version in a non-public route, or read it from the X-Powered-By header if you still expose it, and run check-php2shell.sh with the FPM binary. Then turn expose_php off 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.

/etc/php/8.4/fpm/pool.d/www.conf (excerpt)Text
; 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

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.

quick log triage (paths vary by distribution)Shell
# 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 -l

The PHP2Shell timeline

Date (2026)Event
19 AugustFirst bug found (Wiz)
20–21 AugustUnauthenticated RCE confirmed on Drupal, then WordPress
24 AugustReported to the PHP security team
21 SeptemberPHP acknowledges and starts a coordinated fix
20 OctoberDetails leak in a Hacker News post; PHP publishes an advance notice at 19:33 UTC (Check Point)
21 October, 03:02 UTCCloudflare default WAF protection live for all plans (Cloudflare)
21 October, 14:00 UTCPHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35 released (php.net)
How PHP2Shell went from discovery to release, from Wiz, Check Point, Cloudflare and 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

Discussion

Join the discussion

Sign in with Google, Apple or an email code to comment and reply.

One practical article a week

Follow k13 in any RSS reader. No account needed.