How to Fix PHP2Shell (CVE-2026-109618 and CVE-2026-109619)
By DoggByte ·
If you run anything written in PHP, and statistically you do, there’s a good chance you have a homework assignment this week. Two critical bugs in PHP itself, nicknamed PHP2Shell, let an unauthenticated stranger run code on your web server. CISA added them to its list of actively exploited vulnerabilities on October 26, so “I’ll get to it after the weekend” has left the building.
This post walks you through fixing it. No panic required, just a short list and a coffee.
I’m not a security vendor, so I’ll lean on the people who are and link them as we go. Where I’m giving you commands, they’re for common Linux setups. Yours might use different package or service names, so read before you paste.
What’s actually broken
There are two bugs, CVE-2026-109618 and CVE-2026-109619, both rated 9.2 out of 10 (CVSS v4.0). In plain words:
- CVE-2026-109618: when PHP runs out of memory at an unlucky moment, it can bail out while a request is only half set up, and the cleanup afterwards trusts data that isn’t valid. A crafted multipart request (the kind used for file uploads) can trigger it.
- CVE-2026-109619: a worker that handles several requests can keep a pointer to memory that was freed at the end of an earlier request, and a later request can use it before it gets reset.
The result in both cases is remote code execution as the web server’s user, without logging in.
It affects every PHP installation that serves web requests: Apache with mod_php, PHP-FPM,
php-cgi, FrankenPHP and LiteSpeed. mod_php is the riskiest, because there the attack can land
before your application code even starts. The command line (php script.php in a terminal) isn’t
what these write-ups are about.
The fixed releases are:
| Branch | Fixed in |
|---|---|
| 8.5 | 8.5.12 |
| 8.4 | 8.4.27 |
| 8.3 | 8.3.36 |
| 8.2 | 8.2.35 |
PHP 8.1 and older are no longer supported, but the PHP security team published minimal source backports for 7.4, 8.0 and 8.1, and some distributions and LTS vendors ship their own fixes.
Step 1: Find out what you’re running
Start with what’s actually serving your site, because the php in your terminal and the PHP behind
your web server are often two different installs.
php -v # the command-line PHP
php-fpm -v # PHP-FPM, if you use it (it may be called php-fpm8.4 and so on)
apachectl -M | grep -i php # is mod_php loaded into Apache?
On a host with several PHP versions, check each one. Also look for PHP hiding inside other things: containers, control panels, and appliances that bundle it. CISA’s advice is to inventory all of it, and I agree, mostly because the one you forgot is the one that gets popped.
Step 2: Update PHP
The only complete fix is a PHP build that contains both patches. Pick the route that matches how you installed it.
Debian, Ubuntu and friends
sudo apt update
sudo apt list --upgradable 2>/dev/null | grep -i php
sudo apt install --only-upgrade 'php*'
RHEL, Rocky, Alma, Fedora and friends
sudo dnf upgrade 'php*'
Docker
Rebuild your image from a patched base and redeploy. Pinning the tag is the tidy way to be sure:
FROM php:8.4.27-fpm
Then pull, rebuild, and restart the containers. A running container keeps its old PHP until it’s replaced, no matter how many new images are sitting on the disk.
Built from source
Grab the fixed release for your branch from php.net and rebuild.
“But the version number didn’t change”
Distributions often backport a fix and keep the old version number, so 8.3.30 in your package
list might already be patched. Don’t guess. Ask the package’s changelog:
apt changelog php8.3-fpm | grep -iE '109618|109619' # Debian and Ubuntu
rpm -q --changelog php-fpm | grep -iE '109618|109619' # RHEL family
If both CVE numbers show up, you’re covered. If neither does, check your vendor’s security page before trusting that package.
One more heads-up from the write-ups: users of the rpms.remirepo.net repository had no updates
available when this was published, and were told to move to distribution packages or build from
source. Check whether that has changed for you.
Step 3: Restart your workers
Installing a new PHP does not change the PHP that’s already running. Old workers hang on to the old code in memory until they’re restarted. Many package updates restart the service for you, but “many” isn’t “all”, so do it yourself:
sudo systemctl restart php8.4-fpm # use your actual service name
sudo systemctl restart apache2 # or httpd, if you run mod_php
Use systemctl list-units | grep -i php if you can’t remember what yours is called.
Step 4: Verify it took
Check the version again with php-fpm -v (or whatever you used in step 1), then check that the
running process isn’t a ghost of the old install:
sudo ls -l /proc/$(pgrep -o -f 'php-fpm: master')/exe
If the output ends in (deleted), that process is still running the binary you replaced, and it
needs another restart.
Finally, load a PHP page on your site and confirm it still works. Patching the hole is great, but patching it into a blank page is a different kind of outage.
Can’t patch right now?
If you really can’t update today, there are stopgaps. The Rapid7 write-up is clear that these reduce exposure but do not fix the bugs, so treat them as a bandage and not a cure.
Settings only count if they’re set where a request can’t change them: php.ini, the FPM pool
config, or the server config. ini_set() and .user.ini are applied too late to help. Restart
your workers after any change.
-
Turn off session upload progress (for CVE-2026-109619):
session.upload_progress.enabled = 0 -
On PHP 8.5, turn off fatal error backtraces (also CVE-2026-109619):
fatal_error_backtraces = 0 -
Keep request sizes small. Set
post_max_sizeto8Mif you don’t need uploads, or32Mif you do, withupload_max_filesizebelow it. If your app has no use for multipart requests at all, block them at your reverse proxy or firewall. -
For CVE-2026-109618, Rapid7 lists an administratively enforced
memory_limit = -1as a workaround. That removes the limit that triggers the bug, but it also removes a guard rail, so only do it where your app and host can take it, and don’t useRLIMIT_ASorRLIMIT_DATAas a substitute, because those can trigger the vulnerable path. -
Put a WAF in front. Cloudflare has switched on initial rules for the known exploit routes, including on free plans, but nobody presents that as a replacement for patching.
If you’re on PHP 8.1 or older and can’t move, CISA’s guidance is blunt: take the PHP-based system offline until it’s patched. That’s a hard sentence to read about a site you care about. It is still better than the alternative.
Step 5: Check you weren’t visited first
Exploit attempts were seen in the wild before the public announcement, so patching is only half the job. Amazon’s honeypot logged one on October 17, four days before disclosure. A few things worth doing:
-
Look at what your PHP workers are running. The warning sign from CISA is PHP worker processes launching shells or downloading tools. A quick look at the process tree is a start:
ps -eo pid,ppid,user,comm --forest | grep -A3 -E 'php-fpm|apache2|httpd'A web worker with a
sh,bash,curlorwgetchild is not normal. -
Skim your logs for weird multipart POST requests and for bursts of PHP fatal errors or memory errors around the time the bugs became public.
-
Look for files that shouldn’t be there, such as recently modified
.phpfiles in upload or cache folders. -
Remember what a clean log means. Rapid7 puts it well: the absence of relevant log entries is not proof an installation is safe. If the machine matters and you have any doubts, restoring from a known-good backup after patching beats hoping.
CISA also published SIGMA detection rules in its GitHub repository if you run something that can
use them, and there’s an open-source scanner from Searchlight Cyber, php2shell-scanner, that tests
whether a host is exposed. Read any tool before you point it at your servers, as you would with
anything else you download.
One more thing on the radar
A third PHP issue, CVE-2026-115560, was published on October 23. Rapid7 reports that it has no patch yet, that a mitigation exists, and that exploitation depends on a non-standard configuration. That’s all I can confirm, so check the PHP project’s own advisory and your vendor for the current status rather than taking my word for it.
Wrap-up
The short version, for the person who scrolled straight to the bottom (I see you):
- Find every PHP that serves web requests.
- Update to 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or a vendor build that lists both CVEs.
- Restart the workers.
- Verify the running version.
- Look for signs someone beat you to it.
This site isn’t built on PHP, so I got to enjoy this one from the sidelines, which is a rare treat. If yours is, go patch. The coffee can wait until it’s done.
Sources and further reading: