PHP2Shell: Two Critical PHP Flaws That Turn One HTTP Request into Code Execution
PHP2Shell is a critical PHP remote code execution vulnerability pair, tracked as CVE-2026-109618 and CVE-2026-109619. On 21 October 2026 the PHP project shipped emergency releases for both flaws in the language runtime itself. Each is rated 9.2 (Critical) on CVSS v4.0, and the community nicknamed the pair PHP2Shell.
Neither flaw needs a login, a vulnerable plugin or a sloppy application. A crafted request to a stock PHP server can be enough to run an attacker’s code as the web worker account.
This guide explains how the two bugs work, who is exposed, which versions fix them, and how to patch Debian, Ubuntu and AlmaLinux servers, including PHP versions the PHP project no longer supports.
What Is PHP2Shell? The Short Version
Patch to PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or to your distribution’s fixed package, then restart PHP.
- Update. Both PHP advisories say updating is the only complete fix.
- Restart afterwards. A worker keeps running the old code until it restarts. Apache mod_php needs a full restart, not a reload.
- mod_php is the highest-risk setup. PHP demonstrated code execution there before any application code runs, using default limits.
- PHP 8.1 and older get no official release. Ubuntu, Debian (via paid ELTS), AlmaLinux and others have shipped or promised backports. Plan a move to a supported branch anyway.
- If you cannot patch today, turn off session.upload_progress.enabled, remove the controllable memory limit, and cap multipart requests. These reduce exposure but do not close the holes.
PHP2Shell Timeline: What Happened and When
PHP2Shell is not the only critical flaw to land this year. We also covered CVE-2026-41940, where a misplaced CRLF exposes millions of servers and Copy Fail (CVE-2026-31431), a Linux privilege escalation.
Date (UTC) | Event |
|---|---|
20 Oct | Details of both bugs leak before a fix exists. Coordinated disclosure had been planned for 22 Oct, 14:00. |
20 Oct, 19:33 | PHP’s security team announces a critical out-of-band release for 21 Oct, 14:00, after the leak. |
21 Oct, around midday | PHP releases 8.5.12, 8.4.27, 8.3.36 and 8.2.35 ahead of its 14:00 schedule and publishes both advisories. |
21 Oct, evening | Debian (DSA-6574-1, DLA-4848-1), Ubuntu (USN-8948-1 and -2) and AlmaLinux publish fixed packages. |
Exploitation status. Reports about in-the-wild exploitation conflict, and public proof-of-concept code of unverified quality is circulating. Treat both flaws as exploitable today.
Why Abandoning a Request Halfway Causes Remote Code Execution
Both bugs come from how PHP exits a request when something goes badly wrong. PHP uses longjmp() to jump straight out of whatever it was doing. That is fast and simple, but it skips any cleanup the interrupted code would have done.
Most of the time this is harmless. The two advisories show that in a few places it leaves PHP’s internal state half-built, or pointing at memory that is about to be freed, and that an attacker can steer the runtime into those places on purpose.
The PHP fix removes the jump for out-of-memory errors and resets the wider set of leftover state, so the engine never sees an inconsistent request.
CVE-2026-109618: RCE via Out-of-Memory Corruption
In some contexts, request memory is reachable before it is fully initialised. If any allocation fails at that moment, PHP jumps out and does not account for the early exit, leaving the request memory invalid. The failed allocation does not even need to be related to the broken memory. When the request shuts down, PHP reads or branches on that invalid state. The advisory classes it as use-after-free, access of an uninitialised pointer and improper handling of exceptional conditions.
How an attacker triggers out-of-memory on demand
The advisory describes three multipart-parsing problems that let a request exhaust memory while looking harmless:
- A multipart request without a usable declared content length can slip past the initial post_max_size check and keep supplying decoded body data.
- A single multipart part can carry an unlimited number of headers. Representing them inside PHP costs far more memory than they take on the wire, so a request within post_max_size can still exhaust memory_limit.
- Empty file fields do not count toward max_file_uploads but still create file metadata arrays, and nested field names multiply that work.
Out-of-memory can also be reached through application-specific input that triggers a large allocation.
Why mod_php is the worst case
Apache mod_php always runs the request shutdown procedure, even when request startup failed. Automatic body parsing during startup can exhaust memory in a context where state is temporarily invalid, and shutdown then processes it. All of this happens before any of your application code executes.
PHP validated full remote code execution over HTTP against mod_php with default input and memory limits and a normal prefork setup with multiple workers. More workers and unrelated traffic only raise the number of attempts needed.
Why PHP-FPM is harder to exploit
PHP-FPM exits the child process when request startup fails, which removes the invalid state before it can be consumed. After a normal startup, out-of-memory can still strike in a path that mishandles the early exit, so source analysis says FPM is susceptible. No complete FPM exploit has been demonstrated for this CVE.
CVE-2026-109619: RCE via Stale Global State
A PHP worker handles many requests in a row and keeps global state (error, session, object and extension state) that points into the memory of the current request. That state must be reset before the request’s memory is freed. When it is not, the next request handled by the same worker can touch a dangling pointer before normal initialisation replaces it. With a carefully shaped heap layout, that use-after-free becomes code execution. The advisory also lists memory disclosure as a confirmed impact.
Exploitation needs a producer and a consumer, usually in two different HTTP requests that land on the same worker:
- The producer stores request-owned state in a global location, and the reset is skipped. A fatal error can skip it, including parse and compile failures, uncaught errors, a failed require, and “Maximum execution time exceeded”. Out-of-memory can do the same. Even a harmless warning during late shutdown can repopulate the state after it was cleared.
- PHP frees the request heap while the worker’s global still refers to it.
- The same worker takes another request.
- The consumer reads, frees or branches on the stale value before initialisation resets it.
The two confirmed consumers
INI diagnostics before request initialisation. Any warning, notice or deprecation raised while applying configuration can consume stale error state. It does not need to be fatal, displayed or logged, so the @ operator, display_errors=0 and a lowered error_reporting do not help.
Examples include deprecated or invalid settings such as auto_detect_line_endings=1, legacy assert.* settings on PHP 8.3+, legacy session settings on 8.4+, malformed memory_limit or size values, and paths rejected by open_basedir. With mod_php this configuration comes from .htaccess and Apache php_value, php_flag and php_admin_* directives. With FPM it comes from PHP_VALUE and PHP_ADMIN_VALUE parameters sent by nginx or Apache’s mod_proxy_fcgi.
Session upload progress. This feature reaches session handling before ordinary session initialisation, so it can consume session or decoder state left behind by an interrupted request. It is on by default and works even if your application never calls session_start(). This is the confirmed path against both mod_php and PHP-FPM.
The strongest demonstrated case is PHP 8.5 with fatal_error_backtraces enabled. Fatal error reporting stores backtrace state owned by the request, and the reset is always missed during shutdown. A diagnostic on a later request then hits the stale value. This is the basis of the confirmed mod_php exploitation.
Against FPM, the validated path publishes state during application execution after a successful startup, hits a fatal error, and then uses session upload progress as the early consumer. It works on a normal dynamic pool but depends on a suitable application operation that an attacker can make fail. The advisory also flags other error, exception, object and extension state as conditional risks, and warns that no configuration setting covers every such path.
Who Is Affected by PHP2Shell?
Any PHP installation that serves web requests is at risk. Vendors report that the flaws reach back as far as PHP 5.4, which is why older branches matter. The advisories give this picture by runtime:
Runtime | CVE-2026-109618 (out-of-memory) | CVE-2026-109619 (stale state) |
|---|---|---|
Apache mod_php | Full RCE confirmed, before application code runs | RCE confirmed (PHP 8.5 backtrace path and session upload progress) |
PHP-FPM | Susceptible; no complete exploit shown | RCE confirmed through session upload progress; needs a failing application operation |
FrankenPHP | Likely susceptible (source analysis, same shutdown behaviour as mod_php) | No default early INI consumer found, but extensions or apps may expose others |
LiteSpeed (LSAPI) | Models FPM; application-specific exploits likely but unvalidated | Conditional candidate through early environment INI values |
Which PHP Versions Fix PHP2Shell?
Branch | Vulnerable | Fixed in | Notes |
|---|---|---|---|
8.5 | Below 8.5.12 | 8.5.12 | Supported |
8.4 | Below 8.4.27 | 8.4.27 | Supported |
8.3 | Below 8.3.36 | 8.3.36 | Supported |
8.2 | Below 8.2.35 | 8.2.35 | Security support ends 31 Dec 2026 |
8.1, 8.0, 7.4 | All | No official release | PHP published source-only patch branches; some vendors have backported |
7.3 and older, 5.x | All | No official fix | Vendor extended support only |
For 7.4, 8.0 and 8.1, the PHP project pushed source-only branches to php-src: PHP-7.4-CVE-2026-109618-109619, PHP-8.0-CVE-2026-109618-109619 and PHP-8.1-CVE-2026-109618-109619. There is no tarball and no version bump, so they are meant for people who build PHP themselves. One tracker describes them as a minimal mitigation that also switches off upload progress.
The patch changes how memory exhaustion behaves. Instead of an immediate jump, PHP now raises a delayed, graceful out-of-memory at the next VM safe point, and exits immediately with _exit() if consumption reaches a second, higher threshold. Memory-heavy jobs are worth testing after the upgrade.
How to Patch PHP2Shell
- Find where your PHP comes from: your distribution, a third-party repository, a Docker image or a bundled runtime such as FrankenPHP. Patch from that same source.
- Install the fixed build using the distribution steps below.
- Restart every process that runs PHP (see next section).
- Check that nothing still runs the old code. This lists processes still mapped to deleted PHP files. Empty output means you are clean:
sudo grep -lE ' /(usr|lib|opt).*\(deleted\)$' /proc/[0-9]*/maps
5. Verify the package, not just php -v. Ubuntu and Red Hat style distributions backport fixes into the old version number, so Ubuntu 24.04’s fixed PHP still reports 8.3.6.
Restarting PHP-FPM, mod_php, LiteSpeed and Docker
- PHP-FPM: systemctl restart php8.3-fpm on Debian and Ubuntu, systemctl restart php-fpm on the RHEL family.
- Apache mod_php: do a full restart. reload and apachectl graceful do not load the new library.
- LiteSpeed: upgrade the lsphp packages, then run pkill lsphp. Restarting LiteSpeed alone leaves the old workers running.
- Docker and FrankenPHP: pull or rebuild the image with docker build –pull and recreate the containers. FrankenPHP bundles PHP, so apt and dnf do not fix it; FrankenPHP 1.13.2 ships the fixed PHP.
- Queue workers and Supervisor jobs: restart them too.
PHP2Shell Workarounds If You Can’t Patch Yet
Both advisories are blunt: updating is the only complete mitigation, and no generic configuration setting covers every path. The steps below break specific links in the attack chains and buy you time. Put settings in php.ini, a conf.d file or the FPM pool, not in ini_set() or .user.ini, which apply too late. Restart PHP after each change.
Mitigating CVE-2026-109619 (stale state)
- Set session.upload_progress.enabled = 0. This closes the demonstrated session path on mod_php and FPM. PHP suggests leaving it off after patching if you do not need it, since few apps use the feature and it gets little testing. Normal uploads keep working.
- On PHP 8.5, also set fatal_error_backtraces = 0. That removes the producer behind the strongest confirmed exploit.
- Clean up configuration that raises warnings before a request starts: deprecated or invalid INI values, and per-directory or PHP_VALUE overrides. Hiding the warnings does not help.
- Remove fatal errors and timeouts an attacker can trigger in upload, import, error-page, destructor, serialisation, plugin and callback code.
- If throughput allows, set pm.max_requests = 1 in FPM so each worker serves only one request and cannot poison the next.
Mitigating CVE-2026-109618 (out-of-memory)
- Set memory_limit = -1 in a way applications cannot override, for example php_admin_value[memory_limit] = -1 in the FPM pool, and check that no pool, directory or app setting restores a finite limit. This removes the predictable, attacker-controllable failure but is not complete: if an underlying mmap() fails, PHP can still take the dangerous path.
- Keep post_max_size low and enforce the decoded request size at your frontend.
- Lower max_input_nesting_level and bound the number of input variables, multipart parts, headers per part and uploads.
- Add a container or cgroup memory limit as a backstop, ideally one that kills a single FPM worker rather than the whole pool. Do not use RLIMIT_AS or RLIMIT_DATA, because they make mmap() fail at the limit, which triggers the vulnerable path.
- If an endpoint does not need multipart forms, block multipart/form-data at the proxy or WAF before it reaches PHP. This breaks real uploads, so lift it after patching.
Architecture changes and last resorts
- Move from Apache mod_php (prefork) to the event MPM with PHP-FPM. That removes the application-independent paths PHP confirmed.
- Audit logs with log_errors=On and error_reporting=E_ALL in startup configuration. Look for repeated warnings before scripts run, fatal and parse errors, and execution timeouts. A clean log is not proof of safety.
- The UK NCSC advises considering taking offline any system that cannot be fixed right away.
How to Patch PHP2Shell on Debian
Debian ships the new upstream release rather than a backport, so the package version matches PHP’s.
Release | PHP | Fixed package | Advisory |
|---|---|---|---|
13 (trixie) | 8.4 | 8.4.27-1~deb13u1 | DSA-6574-1 |
12 (bookworm) | 8.2 | 8.2.35-1~deb12u1 | DLA-4848-1 |
11 (bullseye) | 7.4 | Paid only (Freexian ELTS) | Debian LTS ended 31 Aug 2026 |
sudo apt-get update
sudo apt-get install --only-upgrade 'php8.2*' libapache2-mod-php8.2
dpkg -l 'php*' | awk '/^ii/{print $2, $3}'
Swap in your own PHP version and repeat for each one installed. The libapache2-mod-php package only matters if you run mod_php, and apt skips it if it is absent. The same commands work for packages.sury.org, which now carries fixed 8.2 to 8.5 builds for Debian 12 and 13.
How to Patch PHP2Shell on Ubuntu
Canonical’s fixes are in USN-8948-1 and USN-8948-2. Ubuntu 25.04 and 25.10 are end of life and get nothing.
Release | PHP | Fixed package | Support |
|---|---|---|---|
26.10 | 8.5 | 8.5.9-0ubuntu2.1 | Standard |
26.04 LTS | 8.5 | 8.5.4-0ubuntu1.5 | Standard |
24.04 LTS | 8.3 | 8.3.6-0ubuntu0.24.04.13 | Standard |
22.04 LTS | 8.1 | 8.1.2-1ubuntu2.28 | Standard |
20.04 LTS | 7.4 | 7.4.3-4ubuntu2.29+esm5 | Ubuntu Pro (ESM) |
18.04 LTS | 7.2 | 7.2.24-0ubuntu0.18.04.17+esm14 | Ubuntu Pro (ESM) |
16.04 LTS | 7.0 | 7.0.33-0ubuntu0.16.04.16+esm22 | Ubuntu Pro (ESM) |
14.04 LTS | 5.5 | 5.5.9+dfsg-1ubuntu4.29+esm18 | Ubuntu Pro (ESM) |
sudo apt update
sudo apt install --only-upgrade 'php8.3*' libapache2-mod-php8.3
zgrep -E 'CVE-2026-10961[89]' /usr/share/doc/php8.3-fpm/changelog.Debian.gz
Check the local changelog rather than apt changelog, which can return a 404 for packages in universe. On 20.04 and older, enable ESM and run sudo pro fix CVE-2026-109618 to see what applies. If you use ppa:ondrej/php on 22.04 or 24.04, fixed 8.2 to 8.5 builds are published there too, and if your mirror lags you can point sources at ppa.launchpadcontent.net.
How to Patch PHP2Shell on AlmaLinux
AlmaLinux 8, 9 and 10 all ship affected PHP, and PHP-FPM is the default on every release. The AlmaLinux team backported the fixes ahead of Red Hat, put them in the testing repository on 21 October and has been rolling them into production. Covered streams:
- AlmaLinux 8: php:7.4 and php:8.2
- AlmaLinux 9: the default PHP 8.0, plus php:8.2 and php:8.3
- AlmaLinux 10: the default PHP 8.3 and php8.4
dnf module list php # AlmaLinux 8 and 9: the enabled stream is marked [e]
sudo dnf upgrade 'php*'
rpm -q --changelog php-fpm | grep -E 'CVE-2026-10961[89]'
sudo systemctl restart php-fpm
If the update has not reached your mirror yet, follow the testing-repository instructions in AlmaLinux’s own PHP2Shell post. These packages only cover PHP from AlmaLinux’s repositories. PHP from Remi or another third party needs that vendor’s fix, and Remi’s repository was not expected to ship one on release day.
On AlmaLinux 8, mod_php loads only if Apache runs the prefork MPM. Check with:
httpd -M 2>/dev/null | grep -E 'mpm_|php'
If you see mpm_prefork_module together with a php module, you have the highest-risk setup. Move to the event MPM with PHP-FPM.
What About older PHP versions like 8.1, 8.0, 7.4 and Older?
The PHP project fixed only 8.2 to 8.5. A vendor backport does not protect an old branch from the next bug, because nobody upstream is reviewing it anymore. Here is where each platform stands:
Platform | Old PHP | Option |
|---|---|---|
Debian 12, 13 | 8.2, 8.4 | Fixed upstream builds; update normally |
Debian 11 | 7.4 | Freexian ELTS (paid), or upgrade to Debian 12 or 13 |
Ubuntu 22.04 | 8.1 | Fixed in standard support (8.1.2-1ubuntu2.28) |
Ubuntu 20.04, 18.04, 16.04, 14.04 | 7.4, 7.2, 7.0, 5.5 | Ubuntu Pro (ESM), or upgrade the release |
AlmaLinux 8 | 7.4 and 8.2 streams | Patched; moving to 8.2 is a small step |
AlmaLinux 8 | 7.2 (the default stream), 7.3, 8.0 | Retired streams, not in the first round; switch streams |
AlmaLinux 9 | 8.0 default, 8.2, 8.3 | Patched |
AlmaLinux 9 | 8.1 | Retired stream; switch streams |
To leave a retired AlmaLinux stream, test your application against the new one and then run:
sudo dnf module reset php
sudo dnf module enable php:8.2
sudo dnf distro-sync 'php*'
Note that php:7.2 is still the default module stream on AlmaLinux 8, so a plain dnf install php there gives you an unsupported version.
If you build PHP from source, apply the PHP-7.4, PHP-8.0 or PHP-8.1 patch branches directly. If nobody will patch your version, keep every mitigation from the workaround section in place, move to a supported release quickly, and treat taking the site offline as the safe option until then. PHP 8.2 loses security support on 31 December 2026, so aim for 8.4 or 8.5.
PHP2Shell Patch Checklist
- List every PHP version and source on every server, including container and bundled PHP.
- Patch from the right source and confirm the package version.
- Restart PHP-FPM, Apache (fully, for mod_php), LiteSpeed workers and queue workers.
- Run the deleted-mapping check to prove nothing still runs old code.
- Keep session.upload_progress.enabled = 0 unless you truly need upload progress bars.
- Remove temporary multipart blocks once you are patched.
- Review web logs from 20 October onward for odd multipart POSTs, repeated worker crashes or out-of-memory kills, and treat any suspicious hit as a possible compromise.
- Schedule your migration off PHP 8.1 and older.
PHP2Shell FAQ
What is PHP2Shell? A nickname for CVE-2026-109618 and CVE-2026-109619, two critical (CVSS v4.0 9.2) PHP flaws that can lead to remote code execution without authentication.
Which PHP versions fix PHP2Shell? PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35, or your distribution’s backported package.
Is PHP-FPM affected by PHP2Shell? Yes. CVE-2026-109619 is confirmed exploitable through session upload progress. CVE-2026-109618 is susceptible by source analysis, but no complete FPM exploit has been shown.
Why does Ubuntu 24.04 still show PHP 8.3.6 after patching? Ubuntu backports fixes without changing the upstream version number. Check the package changelog instead of php -v.
Do I need to restart Apache after patching? Yes, a full restart for mod_php. Reload and graceful do not load the new library.
Can I mitigate PHP2Shell without patching? Partially. Disable session.upload_progress.enabled and apply the other workarounds, but updating is the only complete fix.
Is PHP 7.4 or 8.1 patched? Not by an official release. PHP published source-only patch branches, and vendors such as Ubuntu, Freexian (ELTS) and AlmaLinux have shipped backports.
Sources and Further Reading
- PHP advisories: GHSA-w8hw-w9fx-j79w (CVE-2026-109618) and GHSA-6wmf-4rv5-p3rp (CVE-2026-109619)
- AlmaLinux: PHP2Shell blog post
- Ubuntu: CVE-2026-109618 and PHP2Shell fixes announcement
- Debian security tracker: CVE-2026-109618
- Mattias Geniar: how to apply the patches
- Help Net Security,
- and Wiz