PHP2Shell: how it works and how to check you're patched
PHP2Shell is a pair of critical PHP bugs, CVE-2026-109618 and CVE-2026-109619, that allow unauthenticated remote code execution. What they are, how they work, which versions fix them, and how to verify your patch actually landed.PHP2Shell is the name for two critical memory-safety bugs in PHP itself, CVE-2026-109618 and CVE-2026-109619, that let an unauthenticated attacker run code on a web server, and the only complete fix is updating PHP to 8.5.12, 8.4.27, 8.3.36 or 8.2.35 (or a vendor backport of those fixes) and restarting every PHP worker.
Facts are as of 22 October 2026 and come from the PHP project's advisories and the vendors linked at the end.
If you are not technical
PHP is the language behind WordPress, Drupal, Joomla and a large share of the web. These bugs sit in PHP, not in your site, so updating WordPress or a plugin does not help. Ask whoever runs your hosting or your developer three questions, and ask for the answers in writing:
- Which PHP version does my site actually run today, and is it one of the fixed versions or a vendor build that contains the fix?
- Does anything else on the server run its own copy of PHP (containers, a control panel, a bundled application)?
- Were the PHP processes restarted after the update?
What is PHP2Shell?
PHP2Shell is two vulnerabilities in the PHP engine, published on 21 October 2026. Both are rated critical (CVSS v4 9.2) and both can give an attacker remote code execution as the web server's PHP account, without logging in.
| CVE | Advisory | What goes wrong |
|---|---|---|
| CVE-2026-109618 | GHSA-w8hw-w9fx-j79w, "RCE via OOM corruption" | PHP runs out of memory while a request is still being set up, aborts, and leaves half-initialised data that later cleanup code trusts. |
| CVE-2026-109619 | GHSA-6wmf-4rv5-p3rp, "RCE via stale global state" | A finished request leaves state pointing at freed memory, and a later request handled by the same worker uses it. |
Neither bug is in WordPress, Drupal, Joomla or any plugin. Researchers demonstrated exploits against all three CMSs, but the flaws are in PHP, so a one-line "Hello World" script on an affected version is exposed too.
How does PHP2Shell work?
In plain terms: PHP has an emergency exit for when it runs out of memory. That exit can fire at a moment when PHP's bookkeeping for the current request is only half built. PHP then runs its normal cleanup on that half-built data, and an attacker who shaped the memory beforehand can turn the confusion into code execution. The second bug is the same family of mistake across requests: one request leaves something dangling, the next one picks it up.
CVE-2026-109618: out-of-memory handling
PHP uses longjmp() to leave the current context when an allocation fails. The advisory says request memory is "temporarily reachable before it is fully initialized". If the allocation that fails happens in that window, the jump leaves request memory invalid, and request shutdown may read or branch on it. The failed allocation does not have to be related to the invalid memory.
The advisory lists ways an attacker can push a request into that state while looking normal to PHP's limits:
- A multipart request with no usable declared content length can pass the initial
post_max_sizecheck and keep supplying decoded body data. - A multipart part can carry an unlimited number of headers, which use far more PHP memory than their size on the wire and can exhaust
memory_limit. - Empty file controls do not count toward
max_file_uploadsbut still create file metadata arrays, and nested field names multiply that work.
The fix removes the longjmp(). PHP now raises a delayed, graceful out-of-memory error at the next safe point in the VM, and an immediate hard exit with _exit() if memory use crosses a second, higher threshold.
CVE-2026-109619: stale global state
PHP keeps global state for errors, sessions, objects and extensions, and a long-lived worker carries it from one request to the next. If that state still points at memory owned by a finished request, it is a use-after-free waiting for a consumer. The advisory describes exploitation as a producer request that leaves state behind and a consumer request that acts on it before normal initialisation resets it. Both must reach the same worker.
Producers include fatal errors (parse and compile failures, uncaught errors, a failed require, execution timeouts) and out-of-memory conditions. Consumers include INI diagnostics raised before request initialisation and session upload progress handling. The advisory names two confirmed paths: on PHP 8.5, backtrace state kept by fatal_error_backtraces; and a session lifecycle case, validated against mod_php and PHP-FPM, where out-of-memory during input processing leaves decoder globals pointing at freed memory that a later upload-progress operation uses.
Which PHP setups are exposed most
The PHP project says all PHP installations serving web requests are at risk, and rates mod_php highest. The advisory for CVE-2026-109618 breaks it down by SAPI (the way PHP is connected to a web server):
| SAPI | What the advisory says |
|---|---|
| Apache mod_php | Runs request shutdown even when startup fails, so exhausting memory during automatic body parsing leads to shutdown on invalid state "before any application-specific code begins execution". Exploit validated over HTTP with default PHP input and memory limits. |
| PHP-FPM | Exits the child when startup fails, which limits exposure. "No complete FPM exploit has been demonstrated", but source analysis considers it susceptible. The session path of CVE-2026-109619 was validated against FPM. |
| FrankenPHP | Source analysis suggests it is likely susceptible, similar to mod_php. |
| LSAPI (LiteSpeed) | Modelled on FPM. Application-specific exploits are likely but unvalidated. |
Exploit reliability depends on the PHP build, extensions, front-end behaviour and memory layout. That is a reason not to read "FPM has no demonstrated exploit" as "FPM is safe".
Which PHP versions are affected and which fix it?
The advisories list every release below the fixed versions as affected, and the fixes shipped on 21 October 2026.
| Branch | Fixed in | Notes |
|---|---|---|
| 8.5 | 8.5.12 | |
| 8.4 | 8.4.27 | |
| 8.3 | 8.3.36 | |
| 8.2 | 8.2.35 | |
| 8.1, 8.0, 7.4 | No official release | The PHP security team published minimal source backports. Linux distributions and LTS vendors may ship their own backported fix. |
| 7.3 and older | No fix from the PHP project | Upgrade, or get a vendor build that states it contains the fix. |
How far back the bugs reach is contested. Wiz says every PHP release of the last decade is affected, back to 5.4. The PHP project's advisories list affected ranges only for the four supported branches. A member of the PHP security team has called the old-version claims an overreach, noting no exploit has been shown on those versions. An unpatched PHP 7.4 is an unpatched PHP 7.4, whatever the public evidence says.
Two common misreadings are worth closing. Updating WordPress, a theme or a plugin does not protect you, because this is a runtime fix (Automattic says so for its own customers' guidance). And a web application firewall helps but does not replace the update; Cloudflare describes its own rules as defence in depth and recommends patching the PHP runtime itself.
PHP2Shell timeline
| Date (2026) | Event |
|---|---|
| 17 October | Amazon Threat Intelligence sees an exploitation attempt in its MadPot honeypot telemetry, four days before disclosure. |
| 20 October | News of the bugs leaks ahead of the planned release. |
| 21 October, 03:02 UTC | Cloudflare's WAF rules for the bugs go live on all plans. |
| 21 October, around 13:30 to 14:00 UTC | PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35 and the two advisories are published; sources differ on the exact minute, and the planned release time was 14:00 UTC. |
Reports of exploitation differ in strength. Amazon saw attempts from what looks like several actors and says it is unclear whether any of them would have succeeded on real systems. Cloudflare reports attack traffic against its customers and attackers changing payloads to get around its rules. If you want to look back through your logs, Amazon's advice is to start from 17 October, not from the disclosure date.
How to fix PHP2Shell
Update PHP to a fixed release for your branch, or to a vendor package that contains the backport, then restart every PHP process so the new code is loaded. A package upgrade does not change PHP processes that are already running. The PHP advisory calls updating "the only reliable mitigation" for CVE-2026-109619 and the patch "the only complete mitigation" for CVE-2026-109618.
- Managed hosting or a control panel: apply the vendor's PHP update and ask for confirmation of the resulting version. The PHP team notes Linux distributions and LTS vendors received the patches in advance.
- Your own server: update the PHP packages, then restart PHP-FPM or Apache (mod_php), for example
systemctl restart php8.2-fpmorsystemctl restart apache2. Unit names vary by distribution and version. - Containers: the PHP inside an image does not change when the host updates. Pull or rebuild the image on a fixed PHP tag and recreate the container.
- PHP 8.1 or older: there is no official fixed release. Either a vendor you trust ships a build that states it contains the fix, or you move to a supported branch. 8.3 or newer is the better target.
Checklist before you patch
Patching the PHP you know about is the easy half. These checks find the rest. Each one has a terminal route and a route for people who do not have shell access.
1. List every PHP on the machine
Terminal
which -a php
dpkg -l | grep -i php # Debian, Ubuntu
rpm -qa | grep -i php # RHEL, AlmaLinux, CloudLinux
ls -d /opt/cpanel/ea-php* # cPanel EasyApache installsNo terminal
Open the PHP version selector in your control panel (MultiPHP Manager in cPanel, the PHP settings page for a domain in Plesk). It lists installed versions and which one each site uses. If you have no panel, ask your host which PHP versions are installed on the server.
2. Find which PHP the web server really uses
The version php -v prints is the command-line PHP. The web server can run a different one, and one machine can serve different sites with different versions.
Terminal
ps aux | grep -E 'php-fpm|httpd|apache2|lsphp' | grep -v grep
php-fpm8.2 -v # use the binary name from the line above
apachectl -M | grep -i php # shows a mod_php module if Apache loads oneNo terminal
The panel's per-site PHP version setting shows what each site is configured to run. To see what the web server reports, a temporary page containing <?php phpinfo(); prints the version in its first lines. It also exposes server details to anyone who finds it, so load it once and delete it immediately.
3. Read the package changelog, not only the version
Distributions and vendors often fix a bug without changing the upstream version number, so 8.1.34 can contain the fix and a plain 8.4.26 does not. The reverse also happens: an end-of-life build can look current and contain nothing. The changelog names the CVE when the fix is in.
Terminal
apt-get changelog php8.2-fpm | grep -i -E '109618|109619' # Debian, Ubuntu
rpm -q --changelog <package-name> | grep -i -E '109618|109619' # RPM-basedNo match means the package does not claim the fix. It does not prove the package is vulnerable, so ask the vendor. A match shows the vendor says it backported the fix. It does not show the backport covers every code path, which is why the post-patch checks below still matter.
No terminal
Find your vendor's advisory for the two CVE numbers (cPanel, CloudLinux, Plesk and the major distributions each publish one) and note the fixed package version it names. Compare that to what your host says is installed.
4. Look for PHP you did not install on purpose
Containers, appliances and bundled applications carry their own PHP, and the host's package manager cannot see or update it. The Dutch NCSC keeps a growing list of software products that embed PHP in its php2shell repository.
Terminal
docker ps --format '{{.Names}}\t{{.Image}}\t{{.Ports}}'
docker exec <container-name> php -v
docker image inspect --format '{{.Created}}' <image>An image built long ago, still running and published to a port, is the pattern to look for. A container with no php binary may run PHP in a separate container, so check its neighbours.
No terminal
Ask whoever deployed each application what PHP version it ships with, and whether the vendor has released a fixed build. Do the same for any product that runs a PHP-based admin page, such as a database tool, a monitoring dashboard or a help-desk system.
5. Decide what to do with end-of-life branches
Anything on 8.1 or older needs a decision now, not after the update window: a vendor build that names the fix in its changelog, or a move to a supported branch. Moving branches can break code, so take a code backup and compare a handful of key pages before and after the switch.
Checklist after you patch
1. Restart and re-check the web-facing version
Repeat check 2 above. The version the web server reports should now be a fixed release or a vendor build whose changelog names the CVEs. If the processes in ps started before the update, they are still the old code.
ps -o pid,lstart,cmd -C php-fpm8.2 | head # start times must be after the update2. Recreate containers, then verify inside them
docker compose pull && docker compose up -d --force-recreate
docker exec <container-name> php -v3. Keep upload progress off if you do not use it
The PHP advisory for CVE-2026-109619 says it might be prudent to set session.upload_progress.enabled=0 even after patching if you do not need the feature, because attackers are looking for flaws in this hardly-used feature. Set it in php.ini or the FPM pool configuration, not with ini_set() or .user.ini, and restart the workers. Check the effective value from the configuration the web server loads, since php -i on the command line can show different settings from the web SAPI.
4. Test file uploads and login by hand
Patches change error handling, and one published 7.4 rebuild reports that session upload progress is always disabled after the fix, so progress bars that poll the session stop updating. Upload a file, log in, and complete a checkout or form on each important site. Look at the error log afterward.
5. Re-test from outside your network
Check from a machine outside your network that only the ports you intend are reachable, including any published container ports. Anything listening that you cannot explain is worth stopping until you can.
6. Get it in writing
If a host or vendor manages PHP for you, ask for the installed version per site and the time the PHP processes were restarted. "Updated" is not the same as "updated and restarted".
What if you cannot patch yet?
The PHP project's workarounds reduce exposure to specific attack paths. They do not make an installation safe, and the advisory says there is no complete generic configuration mitigation.
| Setting | Covers | Limit |
|---|---|---|
memory_limit=-1, enforced with php_admin_value in the FPM pool or Apache config, with no pool, directory or app setting putting a finite limit back |
CVE-2026-109618 | Removes the controllable PHP memory-limit failure. An underlying allocation failure can still reach the vulnerable path. |
Low post_max_size (8M without uploads, 32M with), upload_max_filesize below it, and request limits at the web server or proxy for decoded size, field count, multipart parts, headers and nesting depth |
CVE-2026-109618 | Limits must also cover requests with no usable Content-Length. |
Container or cgroup memory limit, not RLIMIT_AS or RLIMIT_DATA |
CVE-2026-109618 | An availability boundary only. Address-space limits can cause the vulnerable allocation failure. |
session.upload_progress.enabled=0, then restart every worker |
CVE-2026-109619 | Blocks the confirmed session paths, not other variants. Needs php.ini or pool or server config, not ini_set() or .user.ini. |
PHP 8.5: fatal_error_backtraces=0, then restart every worker |
CVE-2026-109619 | Blocks the confirmed backtrace path only. |
The advisory also asks you to fix attacker-reachable fatal errors and execution timeouts, and to remove invalid or deprecated per-request settings that emit diagnostics before initialisation. Hiding diagnostics with error_reporting, display_errors or @ does not help.
Use these as a bridge of hours or a few days. A configuration that blocks the two confirmed paths tells you nothing about the ones nobody has published yet.
How can you tell if a server was attacked?
No log check proves a server was untouched. These are the signals the vendors describe, starting from 17 October:
- POST requests containing the string
PHP_SESSION_UPLOAD_PROGRESSin form uploads (Amazon). Ordinary access logs do not record request bodies, so this needs a WAF, reverse proxy or capture that does. - Unusual multipart requests followed by memory-limit errors or repeated HTTP 500 responses, unusually large or malformed
PHPSESSIDcookies, and unexplained PHP worker churn (Wiz). - Unexpected process execution or file changes on web servers (Amazon).
grep -i 'Allowed memory size' /var/log/php*.log /var/log/php-fpm/*.log 2>/dev/null
grep -i 'exited on signal' /var/log/php*-fpm.log 2>/dev/nullLog paths differ by system. The first line finds memory exhaustion messages. The second finds PHP-FPM workers that crashed; a worker killed by a signal such as SIGSEGV is worth reading in context. A match is only a lead. If you find one on a server that was exposed before patching, treat it as a possible compromise and escalate to whoever handles incidents for you.
PHP2Shell FAQ
Does updating WordPress or my plugins fix PHP2Shell?
No. The bugs are in PHP, so only a PHP update protects you. Automattic's guidance says updating WordPress, a theme or plugins "will not protect you on its own".
Is PHP running only from the command line affected?
The PHP advisory describes the risk as applying to installations serving web requests (PHP-FPM, mod_php, php-cgi, FrankenPHP and LiteSpeed). A PHP used only for scheduled scripts and never exposed to web requests is outside that description, but patch it with the rest.
Do I need to restart anything after updating?
Yes. Restart PHP-FPM or Apache (for mod_php) so the running workers load the fixed code. Restart workers after changing any ini setting as well.
Is a WAF enough?
No. Cloudflare's own rules are a defence-in-depth layer and its guidance is to patch the runtime. Keep the WAF on while you update.
Is PHP 7.4 safe if the distribution still ships it?
Only if the package changelog or the vendor's advisory names CVE-2026-109618 and CVE-2026-109619 as fixed. The PHP project published source backports for 7.4, 8.0 and 8.1, so a vendor build that includes them can be fixed under an old version number.
Sources
- PHP security advisory, 21 October 2026 (php.net)
- GHSA-w8hw-w9fx-j79w: RCE via OOM corruption (php-src)
- GHSA-6wmf-4rv5-p3rp: RCE via stale global state (php-src)
- CERT-EU security advisory 2026-018
- Wiz: PHP2Shell
- Amazon Threat Intelligence: PHP2Shell exploitation prior to public disclosure
- Cloudflare: protections against the PHP2Shell vulnerabilities
- Automattic: your sites are protected against the PHP2Shell vulnerabilities
- NCSC-NL php2shell repository
- What the PHP2Shell rumors got wrong (a PHP security team member)
- Patching PHP 7.4 for php2shell in a container
A note on WebPixie: PHP is not used anywhere in our infrastructure, so these vulnerabilities do not affect us. We wrote this guide to help teams that do.