PHP2Shell (CVE-2026-109618 and CVE-2026-109619): How the PHP Bugs Work, What They Can Cost You and How to Patch
The two critical PHP remote code execution bugs fixed on 21 October 2026, explained in plain terms: how the attacks work, which servers are most at risk, what a break-in can cost, and how to patch supported and end-of-life PHP versions.
By Muhammad Saif · Updated
On 21 October 2026 the PHP project released fixes for two critical vulnerabilities, CVE-2026-109618 and CVE-2026-109619, now widely called "PHP2Shell". Both let an attacker who can send HTTP requests to your server run their own code on it, without logging in. Both are rated 9.2 out of 10 (CVSS v4.0).
If you run PHP on a server you manage, whether it's WordPress, Laravel, Magento, Drupal or a custom app, treat this as urgent. This guide explains what the bugs are, who is most exposed, what a break-in can cost and exactly how to patch, including on old PHP versions that no longer get official updates.
The short version
| What | Two memory-safety bugs in PHP itself, not in WordPress, Laravel or any framework |
| Impact | Unauthenticated remote code execution with the privileges of the web server's PHP worker |
| Severity | 9.2 (CVSS v4.0). A score of 10.0 that circulated on 20 October was wrong |
| Fixed in | PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35 |
| Old versions | PHP 8.1 and older get no official release. Source-code backports exist for 7.4, 8.0 and 8.1 |
| Most at risk | Apache with mod_php, where the attack can succeed before any of your code runs |
| Also affected | PHP-FPM, php-cgi, FrankenPHP and LiteSpeed |
| Temporary mitigation | Turn off session.upload_progress and tighten request limits. These reduce risk but are not a fix |
How PHP handles a request
Both bugs live in the part of PHP that sets up and cleans up each web request, so it helps to know that lifecycle first.
PHP doesn't start a fresh process for every visitor. A pool of worker processes (Apache's prefork workers with mod_php, or a PHP-FPM pool) handles requests one after another. For each request a worker:
- Starts up the request. It reads the incoming data: form fields, uploaded files, cookies and session information.
- Runs your code. WordPress, Laravel or whatever your app is.
- Shuts down and cleans up. It frees the memory the request used, so the worker is ready for the next visitor.
Two settings matter here. memory_limit caps how much memory one request may use. When a request hits that limit, PHP stops it immediately with a fatal error. And because workers are reused, anything not cleaned up properly after one request can still be there when the next request arrives.
CVE-2026-109618: memory corruption when a request runs out of memory
When a request hits memory_limit, PHP abandons it straight away. The bug is that this can happen while PHP is halfway through setting up some internal data. The data is left partly initialized, and the cleanup step that runs afterwards treats it as valid. Working on half-built data corrupts memory, and an attacker who can shape that corruption can take over the process.
The PHP Foundation describes it as a request reaching memory_limit "at exactly the wrong moment". According to Rapid7's analysis of the advisory:
- An attacker can force that moment with crafted multipart requests, the format used for forms and file uploads. These can exhaust memory even when their size looks like it's within PHP's configured limits.
- Application code that allocates memory based on what a visitor sends can also trigger it.
- With Apache mod_php, cleanup runs even when request startup fails. So the attack works on the incoming request itself, before your application code is involved. The advisory reports a working attack over plain HTTP with PHP's default input and memory limits.
What about PHP-FPM? Here the sources differ. Rapid7 and Qualys say FPM ends a worker when request startup fails, which blocks the specific startup attack, and Rapid7 says the advisory reports no complete FPM exploit for this CVE. The PHP Foundation says remote code execution was also confirmed on FPM through application-level code paths. Assume FPM is exploitable and patch it too.
CVE-2026-109619: stale data from an earlier request (use-after-free)
The second bug is about what a worker carries from one request to the next. If a request ends with a fatal error, such as running out of memory or hitting the execution time limit, the cleanup step can be cut short. Some internal state is left pointing at memory that has already been freed. When the next request handled by the same worker uses that state, PHP reads and writes memory that may now hold something else. This kind of bug is called a use-after-free, and it's a well-known route to running attacker-controlled code.
Rapid7 describes two ways to reach the stale state:
- Session upload progress. This PHP feature reports how far a file upload has got, and it's on by default. It reads session data very early in a request, before normal setup, and it does so even if your application never calls
session_start(). That makes it an ideal early consumer of the stale state. The attack has been confirmed against both mod_php and PHP-FPM. - Fatal error backtraces (PHP 8.5 only). The
fatal_error_backtracessetting, on by default in 8.5, gives a second route on mod_php.
On FPM the attacker needs more: a request that leaves stale state behind, a fatal error, and then a second request that lands on the same worker. That's harder, not impossible.
Qualys also reports that researchers have shown the bugs can leak a worker's memory without logging in. That memory can hold database passwords, session tokens and API keys. No other source I read confirms this, but it's a good reason to rotate secrets if you were exposed (see below).
Which servers are most at risk
| Setup | Risk | Why |
|---|---|---|
| Apache + mod_php | Highest | Both bugs can be triggered before your code runs |
| PHP-FPM behind Nginx or Apache | High | The CVE-2026-109619 session route is confirmed, and the PHP Foundation reports FPM code execution for CVE-2026-109618 too |
| FrankenPHP, LiteSpeed, php-cgi | High | Listed as affected. Long-running workers keep state across many requests |
Docker images built on php:* | Depends on the base image | Old images keep the vulnerable PHP until you rebuild and redeploy |
| Shared hosting | Depends on your host | Only the host can patch PHP. Check their status page |
| PHP on the command line only | Lower | Not discussed by the advisories, since there are no outside HTTP requests. Long-running queue workers still need restarting after the update |
Timeline
According to the PHP Foundation's account of the release:
| When (UTC) | What happened |
|---|---|
| 11 August 2026 | Calif and OpenAI report the out-of-memory root cause to the PHP security team |
| 20 October, ~15:00 | The CVE IDs leak online with the wrong release date and the wrong score (10.0). No technical details leaked |
| 20 October, 19:30 | PHP publishes a pre-announcement early because of the leak |
| 20 October, ~20:00 | A proof-of-concept video showing attacks on several targets is accidentally posted |
| 21 October, 07:41 | Fixed container images are published before the planned release time |
| 21 October, 14:00 | PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35 are released, a day earlier than planned |
Is it being exploited? The PHP Foundation says it had not identified any working exploits at the time of release. Rapid7 expects exploitation in the wild and a public exploit soon. Qualys reports that exploitation attempts have been seen in the wild, citing reports from Amazon, Cloudflare and Patchstack. With a video of the attack already public, patch as though attacks have started.
What a successful attack can cost you
Code execution as the PHP worker means the attacker can do anything your website can do. In practice that includes:
- Stealing your data. The worker can read your application's config files, including
.envandwp-config.php, so the attacker gets your database password and with it your customer records, orders and password hashes. - Stealing secrets and money. API keys for payment providers, email services, cloud storage and AI providers are usually in the same config files. Stolen cloud keys can run up large bills, for example by mining cryptocurrency on your account.
- Skimming payment cards. Attackers can inject JavaScript into checkout pages that copies card details as customers type them. You may not notice for weeks.
- Planting backdoors. Hidden PHP files or cron jobs let the attacker come back even after you patch, which is why patching alone doesn't clean an already compromised server.
- Ransomware and data loss. Files and databases the worker can reach can be encrypted or deleted, including backups stored on the same server.
- SEO spam and blacklisting. Spam pages and redirects get your domain flagged by Google and browsers, and organic traffic can take months to recover.
- Spam and abuse from your server. Phishing emails sent from your IP get it blacklisted, so even your legitimate emails stop being delivered.
- Reaching the rest of your network. A web server is often a stepping stone to databases, admin panels and internal systems.
On top of the direct damage come downtime, the cost of investigating and rebuilding the server, and legal obligations. If personal data of people in the EU or UK is exposed, GDPR usually requires you to notify the regulator within 72 hours, and fines can reach €20 million or 4% of global annual turnover. If you take card payments, a breach also brings PCI DSS consequences from your payment provider. Patching is far cheaper than any of this.
Step 1: Find out what you're running
Check every server, container image and PHP version. Many servers have more than one installed.
# PHP version used on the command line
php -v
# Every installed PHP binary and FPM service (Debian/Ubuntu layout)
ls /usr/bin/php* /usr/sbin/php-fpm* 2>/dev/null
# Is Apache loading PHP as a module (mod_php)?
apachectl -M 2>/dev/null | grep -i php
# Current state of the settings that matter here
php -i | grep -E 'session.upload_progress.enabled|fatal_error_backtraces|memory_limit|post_max_size'
Don't rely on the version number alone. Debian, Ubuntu, Red Hat and other distributions often backport security fixes without changing the PHP version you see. Check the package changelog for the CVE IDs instead:
# Debian / Ubuntu (adjust the package name to your version)
zgrep -E 'CVE-2026-10961[89]' /usr/share/doc/php8.3-fpm/changelog.Debian.gz
# RHEL, AlmaLinux, Rocky, Fedora
rpm -q --changelog php-fpm | grep -E 'CVE-2026-10961[89]'
If both CVE IDs appear, that package is patched.
Step 2: Apply the mitigation now
If you can't patch within the hour, reduce the risk first. Set these at the server level: in php.ini, in each FPM pool with php_admin_value, or in the Apache config. Rapid7 notes that ini_set() in application code and FPM's .user.ini files take effect too late to help.
; php.ini: blocks the known routes for CVE-2026-109619
session.upload_progress.enabled = 0
; PHP 8.5 only: closes the backtrace route
fatal_error_backtraces = 0
; Keep request bodies small: 8M if you don't accept uploads, 32M if you do
post_max_size = 8M
upload_max_filesize = 6M
For a PHP-FPM pool, the equivalent is:
php_admin_flag[session.upload_progress.enabled] = off
Then restart PHP-FPM or Apache so every worker picks it up. The advisories also mention removing memory_limit (-1) to dodge the out-of-memory trigger. Rapid7 warns that an allocation failure can still reach the vulnerable code, and an unlimited limit lets a single request eat the server's memory, so treat that as a last resort. None of these settings is a complete fix. They buy you time to patch.
Step 3: Patch
Supported versions (8.2, 8.3, 8.4, 8.5)
Update to 8.2.35, 8.3.36, 8.4.27 or 8.5.12 or later, or to your distribution's patched package.
# Debian / Ubuntu, including the ondrej/php (sury.org) repositories
sudo apt update && sudo apt install --only-upgrade 'php8.3*'
# RHEL family
sudo dnf upgrade 'php*'
# Then restart every PHP service
sudo systemctl restart php8.3-fpm # or: sudo systemctl restart apache2
Things that are easy to miss:
- Restart everything that runs PHP. Updating the package doesn't replace code that's already loaded in memory. Restart PHP-FPM, Apache, and long-running workers such as Laravel queues, Horizon, Octane, FrankenPHP and RoadRunner.
- Hosting panels don't always patch PHP for you. Laravel Forge says its automatic security updates don't update PHP. On cPanel, Plesk and similar panels, check that the panel's own PHP builds have been updated, not just the operating system's.
- Rebuild container images. Pull the fixed base image, rebuild your image and redeploy. A running container keeps the old PHP until it's replaced.
- Check every version on the server. Patching
php8.3does nothing for a site still running onphp7.4-fpm.
Old versions (8.1, 8.0, 7.4 and earlier)
PHP 8.1 and older are end of life, so there is no official release with these fixes. Your options:
- Distribution or vendor packages. Some long-term-support distributions and extended-support PHP vendors ship patched builds of older versions. Check your vendor's advisory for the two CVE IDs.
- Build a patched PHP yourself. The PHP project published source branches with the fixes backported to 7.4, 8.0 and 8.1. You compile PHP from that source with the same version, extensions and build options as your current one, test it, then swap it in with a way back if anything breaks. Your application doesn't change, because it's the same PHP version with the security fix added.
- Even older versions (5.x, 7.0 to 7.3). There's no published backport. The fix has to be ported by hand to the older code, which is skilled but doable work.
- Upgrade the application to a supported PHP version. This is the right long-term answer, but it can take weeks if the code relies on old behaviour, so it's usually done after a backported build has closed the hole.
A backported build closes these two holes. It doesn't fix the other bugs found in old PHP versions since they went end of life, so plan the upgrade anyway.
Your own code
Patching PHP fixes the root cause. It's still worth closing the paths these attacks lean on in your application:
- Limit work driven by visitors' input. Endpoints that build big arrays, decode large JSON or parse uploaded files based on a visitor's input are exactly where an attacker can force a request out of memory. Put size limits on them before processing.
- Reject oversized requests at the web server, for example with Nginx's
client_max_body_size, before PHP ever sees them. - Remove reliance on upload progress if your app uses it, so the setting can stay off for good.
- Run PHP with least privilege. A worker that can't write to the web root or read unrelated secrets limits what a successful attack can do.
Step 4: Check you weren't already hit
None of the sources published reliable indicators of compromise, and Rapid7 points out that clean logs don't prove a server is safe. If a server was exposed before you patched, especially with mod_php, check:
# PHP files created or changed since the CVE IDs leaked
find /var/www -name '*.php' -newermt '2026-10-20' -ls
# Cron jobs for the web server user
sudo crontab -u www-data -l
# Processes running as the web server user, and network connections opened by PHP or Apache
ps -u www-data -o pid,etime,cmd
sudo ss -tunp | grep -E 'php|apache2|httpd'
If you find anything unexpected, treat the server as compromised: take it offline, rebuild it from a known-good image, and rotate every secret it held, including database passwords, API keys, the application key and session secrets. Even if nothing turns up, rotating secrets on an exposed mod_php server is a sensible precaution.
Checklist
- Listed every server, container image and PHP version in use
- Turned off
session.upload_progressat server level (andfatal_error_backtraceson 8.5) - Updated supported versions to 8.2.35, 8.3.36, 8.4.27 or 8.5.12, or a patched distribution package
- Patched or replaced every end-of-life PHP version
- Restarted PHP-FPM, Apache and every long-running PHP worker
- Rebuilt and redeployed container images
- Confirmed the fix from the package changelog, not only the version number
- Checked for signs of compromise and rotated secrets on exposed servers
- Planned the upgrade off any end-of-life PHP version
Sources
- PHP Foundation: Today's PHP security release, and how we're strengthening PHP security
- Rapid7: CVE-2026-109618 and CVE-2026-109619, critical unauthenticated remote code execution in PHP
- Qualys: PHP2Shell, PHP patches two critical RCE vulnerabilities
- Laravel Forge: PHP security advisory
- Codeable: what WordPress site owners need to do
Need it done for you? I patch servers against these CVEs, including building patched PHP for old versions and fixing exposed paths in your code, for $100 to $1,200 depending on how many servers and versions you have. See what's included.