PHP2Shell: What the Critical PHP Flaws Mean and How to Patch Them

On 21 October 2026, the PHP project shipped emergency releases for two critical flaws in the PHP runtime itself: CVE-2026-109618 and CVE-2026-109619. Public reporting calls the pair PHP2Shell. Both score 9.2 on CVSS v4.0, and both can let an unauthenticated threat actor run code on a web server. The bugs sit in PHP, not in WordPress, Laravel or your own code, so any site that serves PHP may be exposed. This post explains what went wrong, how an attack works and what you need to do today.
How PHP2Shell came to light
Researchers from Calif, Wiz and OpenAI reported both flaws to the PHP security team, with Quarkslab also credited on CVE-2026-109618. The CVE identifiers were reserved on 12 October 2026. On 20 October, PHP announced that a critical security release would follow the next day at 14:00 UTC.
Details then leaked online before that release, under the PHP2Shell name. Security firms rushed to confirm the bugs, and watchTowr reported that it had reproduced CVE-2026-109618. Cloudflare deployed web application firewall (WAF) rules about nine hours before disclosure and turned them on for all customers at 03:02 UTC on 21 October.
PHP published fixed releases and two advisories later that day. By then, watchTowr's honeypots were already recording exploitation attempts, and Cloudflare reported threat actors changing payloads to slip past its rules. At the time of writing, neither CVE is in the CISA Known Exploited Vulnerabilities (KEV) catalogue.
What goes wrong inside PHP
PHP handles a fatal problem in a request, such as running out of memory, with a bailout. A bailout uses the C function longjmp() to jump straight out of the code that was running, rather than unwinding it step by step. It is fast, but any code that was halfway through setting something up never finishes.
That shortcut is the root of both flaws:
- CVE-2026-109618, out-of-memory corruption. An allocation fails while request memory is only partly initialised, and PHP's shutdown code then reads that memory as if it were valid. PHP confirmed code execution against Apache mod_php with default limits.
- CVE-2026-109619, stale global state. A fatal error leaves global state, such as session or error data, pointing at memory that PHP frees at the end of the request. A later request reads that freed memory, a bug class known as use-after-free.
PHP runs requests in long-lived worker processes, under PHP-FPM or Apache's prefork model. One worker serves request after request, so state left behind by one request can be seen by the next. That reuse is what turns a failed request into a foothold.
How an attack works
The diagram below shows the pattern the researchers described. The threat actor needs no account and no bug in the application. One trigger is PHP's session upload progress feature. It writes upload status into the session while PHP parses a multipart request, before any application code runs. Researchers showed that even a page that only prints "OK" can be reached this way.

What happens after the second request depends on timing and on the layout of the heap, the memory PHP allocates while it runs. Often the worker simply crashes. One technique returns about 67 KB of worker memory inside a PHPSESSID cookie, which can expose passwords, tokens and API keys. With careful heap grooming, the result is remote code execution as the PHP process user.
The CVSS vector rates attack complexity as high for this reason. Even so, working exploitation has already been seen. This post describes the attack at the level defenders need and includes no exploit code. Only test for PHP2Shell on systems you own or have written permission to test.
Which versions are affected
Every supported branch before these releases is affected. Update to at least:
| Branch | Fixed release |
|---|---|
| 8.5 | 8.5.12 |
| 8.4 | 8.4.27 |
| 8.3 | 8.3.36 |
| 8.2 | 8.2.35 |
Reports suggest the bugs reach back to PHP 5.4, although PHP has not tested every old release. PHP 8.1 and earlier are out of regular support, and PHP published limited source backports for 7.4, 8.0 and 8.1 only. If you run anything older, plan an upgrade rather than a patch.
PHP-FPM, Apache mod_php, php-cgi, FrankenPHP and LiteSpeed are all affected, although exploitation is easier on some setups than others. Linux distributions often backport fixes without changing the upstream version number, so check your distribution's advisory rather than the version alone.
Check your servers
Start by finding every PHP installation, including containers and PHP bundled with appliances. On Linux, check the version and the installed packages:
php -v
dpkg -l 'php*' | grep ^ii # Debian and Ubuntu
rpm -qa 'php*' # Red Hat, Fedora and similar
Be careful: php -v reports the command-line PHP, which may not be the build that serves your web requests. Confirm the web-facing version with your PHP-FPM binary, such as php-fpm8.3 -v, or with a temporary phpinfo() page that you remove straight afterwards.
Next, check whether the upload progress feature is on:
php -i | grep session.upload_progress.enabled
session.upload_progress.enabled => On => On
On is the default. The command-line and web server settings often live in different files, so check the FPM or Apache configuration too.
Patch, then restart
Updating PHP is the only complete fix, and both PHP advisories say so plainly. Install the update, then restart PHP so no worker keeps running the old code:
sudo apt update && sudo apt upgrade # Debian and Ubuntu
sudo dnf upgrade 'php*' # Red Hat, Fedora and similar
sudo systemctl restart php8.3-fpm # Or apache2 / httpd for mod_php
Rebuild and redeploy container images as well, since a running container keeps its old PHP until you replace it.
If you cannot patch today
Temporary changes reduce exposure but do not make an unpatched server safe. Set them at server level, in php.ini or the FPM pool configuration, then restart every worker. Settings made with ini_set() or a .user.ini file do not work here.
; php.ini: removes the trigger used by several known techniques
session.upload_progress.enabled = 0
; PHP 8.5 only
fatal_error_backtraces = 0
; Or, in a PHP-FPM pool file
php_admin_flag[session.upload_progress.enabled] = off
The advisories add these steps:
- Keep
post_max_sizelow (8M without uploads, 32M with them) and enforce request size limits at the reverse proxy. - Block multipart requests at the front end for applications that never accept uploads.
- Set
pm.max_requests = 1in PHP-FPM if you can afford the cost, so each worker serves one request and no stale state survives. - Keep WAF protections on during and after patching, as defence in depth.
For CVE-2026-109618, PHP also suggests enforcing memory_limit = -1, which removes the memory cap that the bailout depends on. This trades one risk for another, since a single request can then use up server memory. Treat it as a short-term measure and pair it with a container or cgroup memory limit.
Look for signs of attack
Exploitation attempts are noisy, because they rely on failed requests and crashed workers. Look for:
- Unusually large multipart requests, or requests with very many form fields.
- Oversized
PHPSESSIDvalues in requests or responses. - Bursts of "Allowed memory size exhausted" errors from one source.
- Worker crashes, logged as segmentation faults.
sudo grep -E 'exited on signal 11|Segmentation fault|Allowed memory size' \
/var/log/php*-fpm.log /var/log/apache2/error.log
PHP-FPM records a crashed worker like this:
[22-Oct-2026 09:14:03] WARNING: [pool www] child 41872 exited on signal 11 (SIGSEGV) after 1520.338119 seconds from start
If you find signs of compromise, look for unexpected processes run by the PHP user, changed application files, new web shells and unusual outbound connections. Then rotate every secret the PHP process could read.
The wider lesson
PHP2Shell is a reminder that the runtime under your application is part of your attack surface. A fully patched content management system still falls if the PHP beneath it does not. Keep an inventory of every runtime you run, know who owns each one, and make sure you can patch and restart it within hours, not weeks.
Further reading
- GHSA-w8hw-w9fx-j79w – PHP's advisory for CVE-2026-109618, with its configuration mitigations.
- GHSA-6wmf-4rv5-p3rp – PHP's advisory for CVE-2026-109619.
- PHP news archive 2026 – The release announcements and fixed versions.
