Skip to content
DoggByte
DoggByte

All posts

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_size to 8M if you don’t need uploads, or 32M if you do, with upload_max_filesize below 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 = -1 as 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 use RLIMIT_AS or RLIMIT_DATA as 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, curl or wget child 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 .php files 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):

  1. Find every PHP that serves web requests.
  2. Update to 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or a vendor build that lists both CVEs.
  3. Restart the workers.
  4. Verify the running version.
  5. 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: