Community
3
HostiServer
2026-10-21 11:19

PHP2Shell: What to Do After CVE-2026-109618 and CVE-2026-109619

⏱️ Reading time: ~12 minutes | 📅 Published: October 2026

PHP2Shell: what to do on your server after CVE-2026-109618 and CVE-2026-109619

🚨 Emergency steps: can't update right now? Do this first: Only a fresh PHP version fully closes both vulnerabilities: 8.5.12, 8.4.27, 8.3.36, or 8.2.35. If an update isn't possible right away (for example, a legacy application doesn't run on the new version), do this minimum immediately: disable session.upload_progress.enabled in php.ini or a file in conf.d and restart php-fpm or Apache; limit request size; take the application off public access if you can. This reduces the chance of exploitation, but doesn't remove the vulnerability: disabling this one option closes only one vector. Disabling the file-upload form in the application itself provides no protection, because the vulnerable request handling happens inside PHP itself and fires before the application's code ever runs. For step-by-step instructions, see section 6.

On October 21, 2026, the PHP team released emergency updates for two critical vulnerabilities in the language's core: CVE-2026-109618 and CVE-2026-109619. Both score 9.2 on CVSS v4.0, both allow unauthenticated remote code execution, and both affect not a particular application but the PHP engine itself. The pair has already picked up the name PHP2Shell.

Details became public before the fixes shipped. According to researchers, exploitation attempts are already being seen on the internet. At the same time, no confirmed successful attacks have been reported as of publication, though that's expected to change soon. So this isn't "a large-scale compromise that has already happened"; it's the moment when attackers have a working tool and administrators still have time to close the door. The gap between those two states is measured in hours.

This article isn't a news recap. It's a working action plan for anyone responsible for a VPS or dedicated server: how to find every copy of PHP on the machine (there are usually more than you'd expect), how to update correctly, what to do when an immediate update isn't possible, and how to check whether you've already had a visit. We'll cover containers separately, since this story has its own wrinkles in Docker stacks.

ℹ️ Notice to Hostiserver customers: we're sending customers a separate email with a short action list for these vulnerabilities. This article is the expanded version of that email, with commands, configuration examples, and a checklist. If PHP is running on your VPS or dedicated server and you need help checking or updating it, reach out through support chat: we're available 24/7.

⚠️ Don't run "ready-made exploits" from the internet. High-profile vulnerabilities almost always attract repositories, scripts, and "scanners" that promise to test or attack, but actually install malware on whoever runs them. We haven't found confirmed reports of such fakes for PHP2Shell specifically in verified sources, but the rule stays the same regardless: code from an unknown source doesn't run on production servers. Checking your own system only requires the PHP version and the commands below.

ℹ️ Related articles: if your PHP runs in containers, keep "Docker in Production" (resource limits, restarts) and "Docker + CI/CD: Automatic Deployment" (rebuilding and rolling out an image) nearby. You'll need both in section 7.

2. What's known about the vulnerabilities

2.1 Two bugs, one result

Identifier What it is CVSS v4.0
CVE-2026-109618 Memory corruption during out-of-memory handling: a request is interrupted while internal structures are still partially initialized, and the subsequent cleanup accesses invalid data 9.2
CVE-2026-109619 Stale global state: memory from a previous request is freed, but references to it remain accessible, and the next request in the same worker process operates on invalid memory 9.2

Researchers describe the mechanism with slightly different emphasis (some tie one of the bugs to the upload-progress handler in sessions), but the practical conclusion is the same: the vulnerability sits in PHP's internal request handling. Exploitation doesn't require a vulnerable plugin or a file-upload form in your application. The attack travels through multipart/form-data HTTP requests, and according to researchers it can fire before your site's own code even starts running.

2.2 What this means for protecting an application

The usual response of "we updated WordPress and the plugins" doesn't work here. The interpreter itself is vulnerable, so neither a WAF plugin inside a CMS nor maintenance mode helps: PHP processes the request before the application gets a chance to inspect it. The only way to fully close the problem is updating PHP.

2.3 Which configurations are affected

According to analysts, every way of handling web requests is affected: PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP, and LiteSpeed. The highest risk is assigned to mod_php, since the exploit can fire there earliest. That doesn't make FPM safe; it's simply a priority order for when there isn't time to cover every server at once.

2.4 Fixed versions and the ones left without a patch

PHP branch Fixed version Support status
8.5 8.5.12 Active support
8.4 8.4.27 Active support through Dec 31, 2026
8.3 8.3.36 Security fixes through Dec 31, 2027
8.2 8.2.35 Security fixes through Dec 31, 2026
8.1 and earlier (7.x, 5.x) No official patch No longer supported by the PHP project

For the 7.4, 8.0, and 8.1 branches, the project published minimal backport patches as source code, suitable for anyone building PHP themselves. Some distributions and long-term-support vendors (CloudLinux, Ubuntu ESM, and others) ship their own fixed packages under the old version numbers, so for distribution packages, don't rely on the version number alone: check your vendor's package notes (changelog).

3. "We don't have PHP": why that answer needs checking

The most common first reaction to news like this is: "our main product is written in another language, this doesn't concern us." That's often true for the main product. But on a real server, PHP has a habit of showing up where you don't expect it. Security researchers note that the number of places you need to check for vulnerable PHP versions grows very quickly.

3.1 Where PHP hides most often

  • Admin tools. phpMyAdmin, Adminer, webmail clients like Roundcube, monitoring dashboards, old hosting control panels.
  • Billing and CRM. WHMCS and similar systems, inventory systems (DCIM), forums, internal wikis.
  • Old sites. A "temporary" landing page put up three years ago and forgotten, but still reachable from outside.
  • Containers. An image where PHP is just a dependency of one build script or tool.
  • Third-party services on your server. Clients' or contractors' own projects that an administrator isn't formally responsible for, but which run on the same machine.

3.2 Why "check by reading only" is the right approach

The initial stage requires no changes on the server at all: versions, processes, packages, images, logs. It's safe, it's fast, and it's the sensible place to start before restarting or updating anything.

4. Step 1: find every copy of PHP on the server

4.1 Packages and versions on the host

php -v
php-fpm -v 2>/dev/null || ls /usr/sbin | grep php-fpm
apachectl -M 2>/dev/null | grep -i php
dpkg -l | grep -i php # Debian / Ubuntu
rpm -qa | grep -i php # RHEL / AlmaLinux / Rocky

An important detail: the version shown on the command line (php -v) and the version actually answering web requests can differ. A server often runs several PHP versions at once (say, 7.4 for an old site and 8.3 for a new one), and the one handling web traffic isn't necessarily the one in $PATH. Check the CLI, the FPM pools, and the Apache modules separately.

4.2 What's actually listening on the network

ps aux | grep -E 'php-fpm|php-cgi|apache2|httpd|litespeed' | grep -v grep
ss -tlnp | grep -E ':80|:443|:9000'

4.3 Containers

docker ps --format '{{.Names}}\t{{.Image}}'
docker images --format '{{.Repository}}:{{.Tag}}' | grep -i php
docker exec <container> php -v

Don't rely on the image name: its tags don't always match the actual contents. There's only one reliable check: run php -v inside the container. Images built on top of other images (nested Dockerfiles) can be carrying a copy of PHP nobody remembers.

4.4 What to record

For every instance you find, note four things: server, application, PHP version, and whether it's reachable from the internet. That's your priority list: public systems first, internal ones after. This register will later become the basis for regular patch management rather than a one-off reaction to a headline.

5. Step 2: update PHP

5.1 Distribution packages

The best route: take the fixed package from your distribution's repository. As of October 21, updates are available for Ubuntu, Debian 13, RHEL 9 (the php:8.2 module), CloudLinux, and cPanel (backports for 7.4, 8.0, and 8.1). For other systems, check your distribution's security tracker.

sudo apt update && sudo apt upgrade     # Debian / Ubuntu
sudo dnf upgrade 'php*' # RHEL-compatible

⚠️ Third-party repositories may lag behind. According to reports, the Remi repository still didn't have the update on the first day of the release. If you installed PHP from it or from a PPA, check specifically there for the fixed version rather than assuming it's already available.

5.2 Restarting the processes is mandatory

Updating the package alone doesn't change the PHP that's already running: live processes keep the old version in memory. Applying the fix requires a restart:

sudo systemctl restart php8.3-fpm     # unit name depends on your version and distribution
sudo systemctl restart apache2 # if you're using mod_php

This is one of the most common mistakes during emergency patching: the package is updated, php -v shows the new version, but the FPM pool is still running the old code in memory.

5.3 Verifying after the update

php -v
php-fpm8.3 -v # or the version matching your pool
systemctl status php8.3-fpm

Check the result against the table in section 2.4: 8.5.12, 8.4.27, 8.3.36, or 8.2.35 (or your distribution's package with the fix under a different numbering). If you're using a distribution package with an old-looking version number, check the changelog for a mention of CVE-2026-109618 and CVE-2026-109619.

5.4 Containers: rebuild, don't update in place

A container isn't updated with commands inside a running instance. The correct sequence is: pull a fresh base image with the fixed PHP version, rebuild the image with no cache, roll it out, and verify the result.

docker compose build --pull --no-cache app
docker compose up -d --no-deps app
docker compose exec app php -v

Know the limit here: a rebuild only pulls in the fixed version once the matching base image has actually been published. Check the result of php -v, not the tag. If a fixed image isn't available yet, use the temporary measures from the next section and repeat the rebuild later.

6. Step 3: if you can't update right away

There are situations where an application is incompatible with the new minor version, where a maintenance window is needed, or where PHP is on a branch with no patch at all. Below is a list of temporary measures drawn from guidance published after the release. All of them reduce risk, but none remove the vulnerability. The only full cure is updating.

Measure What it does Caveat
Take the application off the internet Restrict access by IP, VPN, or basic-auth at the web server or firewall level; for a public site, a read-only mode (GET and HEAD) The most reliable temporary option. The CMS's own maintenance mode doesn't help
session.upload_progress.enabled=0 Disables upload-progress tracking, tied to one of the bugs Set it in php.ini or the FPM pool config, not via ini_set() or .user.ini. Workers need a restart
fatal_error_backtraces=0 For PHP 8.5: disables backtraces on fatal errors Branch 8.5 only. Worker restart is mandatory
Lower post_max_size and request limits Less chance of pushing a request into the critical out-of-memory state. Recommended: 8M without file uploads, 32M with them Can break large file uploads, so verify functionality after the change
Web-server-level limits Limiting request size, the number of multipart fields and parts, and headers Don't rely on this alone: WAF rules add to the defense but don't cure PHP
memory_limit=-1 (with a hard outer limit) According to one recommendation, prevents the out-of-memory state that one of the bugs starts from Risky: without another limit, a single request could exhaust the server's RAM. Only apply it alongside a container or cgroup limit, and revert it after updating

6.1 Disabling upload_progress and finding the right php.ini

This is the first and fastest step. Set this in the right file:

session.upload_progress.enabled = Off

Where the configuration lives depends on your distribution and how PHP was installed, and also on exactly what's handling requests: FPM, Apache, and the CLI often each have their own php.ini.

Environment Where to look
Debian / Ubuntu /etc/php/<version>/fpm/php.ini and conf.d/ next to it; for Apache, /etc/php/<version>/apache2/
RHEL / AlmaLinux / Rocky /etc/php.ini and the /etc/php.d/ directory
The official php Docker image /usr/local/etc/php/ (the php.ini file needs to be created or copied from a template) and /usr/local/etc/php/conf.d/
Not sure For FPM: php-fpm -i | grep -i "Loaded Configuration" (with a version suffix if needed, e.g. php-fpm8.3). The php --ini command only shows the CLI configuration, which can differ

After the change, make sure to restart php-fpm or Apache, or the running processes will keep the old settings. You can verify the setting took effect in the actual web process like this:

php-fpm8.3 -i | grep -i "upload_progress.enabled"

The output should say Off. Keep its limits in mind: disabling upload_progress closes only one vector. The second bug needs request-size and memory limits as well (see the next subsections), and there's still only one full solution: updating PHP.

6.2 Example for an FPM pool

; /etc/php/8.3/fpm/pool.d/www.conf
php_admin_value[session.upload_progress.enabled] = 0
php_admin_value[post_max_size] = 8M
php_admin_value[upload_max_filesize] = 4M

The php_admin_value directive matters because the application's own code can't override it, unlike ini_set().

6.3 Example for Nginx

server {
    client_max_body_size 8m;
    # ...
}

6.4 Memory limits at the container level

For Docker, a container memory limit (deploy.resources.limits.memory in Compose, covered in the production article) acts as insurance against one failing process taking all the host's memory. A cgroup limit on the PHP-FPM systemd unit works the same way. It's best to avoid RLIMIT_AS and RLIMIT_DATA: the guidance directly recommends limiting memory through cgroups instead.

6.5 What these measures don't guarantee

No single item in the table fully closes either bug. These are a bridge to a maintenance window, not a substitute for updating. Set a specific date for when the temporary restrictions will be lifted and PHP updated, or "temporary" easily turns into a permanent state.

7. Step 4: checking whether you've already had a visit

Exploitation was observed before the fixes shipped: according to researchers, the first attempts were recorded at 07:43 UTC on October 21, while the fixes landed at 14:00 UTC. So updating the server isn't enough; you also need to confirm nothing got in during that window. Focus on logs from October 20 onward.

ℹ️ A note on the limits of this check: there are no public indicators of compromise (IoCs) as of publication that would let you say definitively "clean" or "compromised." So no findings in the logs means "no traces found," not "guaranteed safe." Read the result that way.

7.1 Web server logs

grep -i 'php2shell' /var/log/nginx/access.log* /var/log/apache2/access.log* 2>/dev/null
grep ' POST ' /var/log/nginx/access.log* | awk '{print $1, $7}' | sort | uniq -c | sort -rn | head -30

The first line searches for the php2shell User-Agent, which some public exploits are reportedly using. But a competent attacker changes it easily, so an empty result guarantees nothing. The second shows which addresses and URLs received the most POST requests: a sharp spike of requests to unusual addresses, especially large multipart requests, is worth a closer look.

7.2 New or modified files

find /var/www -type f -name '*.php' -newermt '2026-10-20' -ls
find /var/www -type f -newermt '2026-10-20' ! -name '*.log' -ls | head -50

New PHP files in upload directories, cache folders, or temp directories, places where they shouldn't exist at all, are a standard sign of a web shell. Compare them against your most recent deploy: anything that appeared outside it needs an explanation.

7.3 Processes and connections

ps -eo user,pid,ppid,cmd | grep -E '^(www-data|nginx|apache|php)' | grep -vE 'php-fpm|nginx|apache2'
ss -tnp | grep -E 'ESTAB' | grep -E 'php|www-data'

Watch for the web server process spawning a shell or utilities like curl, wget, nc, and for unexplained outbound connections from the web server's user account. A normal PHP-FPM process doesn't do any of that.

7.4 What to do if you suspect something

  1. Isolate the system from the external network (don't power it off, to preserve memory and state for analysis).
  2. Save copies of the logs and any suspicious files before making any changes.
  3. Rotate passwords, tokens, and keys the PHP process had access to: the first bug can leak memory contents, including passwords, tokens, and TLS keys.
  4. Restore from a backup taken before the incident, rather than "cleaning" the current state in place.

8. So next time this takes an hour, not a day

It's exactly in events like this that you see the difference between infrastructure that's controlled by code and infrastructure that's kept in someone's head. If you've gone through the series on Docker on a VPS and CI/CD, you already have most of what you need.

8.1 The image is built from a tag, not from "latest"

Pin the PHP version in your FROM line (for example, php:8.3-fpm-alpine instead of php:latest). Then reacting to something like this just takes changing one line and running a build, instead of guessing which version is running on every server. That's the same reproducibility principle covered back in the first article of the series.

8.2 Rebuilding on a schedule

Fixes in base images don't make it into your own image automatically. Set up a regular (say, weekly) rebuild with --pull --no-cache in CI, and check php -v as a pipeline step: the build fails if the version is below what's required. Then "the fixed image already exists and we just didn't know" becomes impossible.

8.3 Registry and fast deployment

Automatic deployment (article 4 of the series) turns an update from a manual SSH operation into a single push. And SHA-tagged images let you roll back quickly if a new PHP version breaks your application: you won't have to choose between a vulnerability and downtime.

8.4 Resource limits as ongoing hygiene

Memory and CPU limits on a container (article 3) turned out to double as a security measure here: a low-memory condition is exactly the starting point for one of the bugs. Sensible, standing limits aren't just protection against memory leaks; they also shrink the attack surface.

8.5 Inventory as an ongoing process

The register from section 4.4 (server, application, version, public reachability) should stay alive on an ongoing basis, updated with every deploy. When the next critical update ships, and it will, your answer won't be "let's go look"; it'll be "here's the list, here's the order."

9. PHP 8.1 and older: a question that outlives this one patch

There's no official fix for the 8.1 branch and older, because the PHP project no longer supports them. Support for 8.2 also ends on December 31, 2026, which is two and a half months away. That means the situation of having to decide "what do we patch with" will keep repeating as long as an application stays on an unsupported branch.

9.1 Three options for legacy systems

  • Distribution backport or LTS support. Some vendors (Ubuntu ESM, CloudLinux, cPanel for PHP 7.4/8.0/8.1) ship fixes for old branches. A sensible bridge if migrating the application will take time.
  • Building from source. The PHP project published minimal patches for 7.4, 8.0, and 8.1. Fine for teams that know how to maintain their own build, but it's operational debt.
  • Migrating to a supported branch. The only option that doesn't keep accumulating risk. Make a plan: 8.3 gets security support through the end of 2027, and 8.5 even longer.

9.2 An old site as an isolated project

If an application can't be migrated quickly, isolate it: a dedicated VPS or container with strict limits, access only through a restricted reverse proxy, and no secrets from other systems on the same machine. A cheap standalone server costs less than an incident on shared infrastructure.

10. Summary

PHP2Shell isn't a reason to panic, and it isn't news "for someone else." It's a reason to spend one hour answering three questions any administrator should be able to answer: where is our PHP, which version is it, and who restarted it after the update.

The plan of action comes down to four steps:

  1. Find it. PHP versions on the host and in containers, CLI and FPM separately; public systems first.
  2. Update it. To 8.5.12, 8.4.27, 8.3.36, or 8.2.35, or to your distribution's fixed package. Restart FPM/Apache, verify php -v. For containers: rebuild with --pull --no-cache.
  3. If you can't right away. Take the application off the internet, disable session.upload_progress.enabled, limit request size, set memory limits. This is temporary, with an end date.
  4. Check. Logs from October 20 onward, new PHP files, odd web-server processes. No findings isn't a guarantee, but any findings need an immediate response.

The situation is still developing: check the official PHP project announcements and your distribution vendor for the latest versions and guidance.

🚀 Infrastructure you can patch fast

When a critical update drops, the winner is whoever has isolated environments, a clean inventory, and the ability to roll out a fix quickly. Hostiserver helps you build exactly that kind of foundation.

🖥️ Dedicated Servers

  • From $90/mo, full control over the hardware and software stack
  • No resource neighbors: your attack surface is limited to your own services
  • 24/7 support: we'll help with inventory, updates, and isolating legacy applications

💻 Cloud (VPS) Hosting

  • From $19.95/mo, KVM isolation, dedicated vCPU and RAM
  • A separate VPS for each project: a legacy site doesn't share a server with critical systems
  • Full root access: update PHP and restart services without waiting on your host
  • Easy to scale: a new isolated server comes up in minutes

💬 Not sure which option you need?
💬 Write to us and we'll help you decide!

Frequently Asked Questions

What is PHP2Shell, and why is it dangerous?

PHP2Shell is the name given to a pair of critical PHP core vulnerabilities: CVE-2026-109618 and CVE-2026-109619, both scoring 9.2 on CVSS v4.0. They let an unauthenticated attacker execute code on the server through specially crafted HTTP requests. The danger is that the interpreter itself is vulnerable, not a particular site or plugin, so updating applications doesn't resolve the situation.

How can I quickly find out if my server is vulnerable?

Check the PHP version actually used by the web server, not just the CLI: php -v, php-fpm -v, Apache modules. If it's below 8.5.12, 8.4.27, 8.3.36, or 8.2.35, or it's branch 8.1 or older with no distribution backport, treat the server as vulnerable. For distribution packages, check the vendor's changelog, since the version number may not match upstream's numbering.

I updated the PHP package, is that enough?

No, you also need a restart. Running PHP-FPM or Apache (with mod_php) processes keep the old version in memory until they're restarted. After the update, restart the relevant service and verify the version again. For containers, you need a full rebuild and redeployment of the image, not an in-place update inside the running container.

We run a WordPress or Magento site. Is this just about the CMS?

No. The vulnerability is in PHP, not the CMS, so updating WordPress, Magento, or their plugins doesn't close it. Researchers have confirmed the attack is possible against many popular systems (WordPress, Drupal, Joomla, Magento, Nextcloud, Laravel, and others), but that's not about any specific product; it's that they all run on vulnerable PHP. There's one fix: update PHP on the server.

Is disabling session.upload_progress.enabled enough on its own?

No, it only reduces risk partially. It addresses one of the exploitation mechanisms, and according to analysts, there's no full workaround without updating. Use it as a temporary measure alongside limiting request size and taking the application off external access, but plan on updating PHP as the mandatory final step.

What should I do if I'm on PHP 8.1 or older and can't migrate right now?

Check whether your distribution or panel vendor (Ubuntu ESM, CloudLinux, cPanel, and others) has released a fixed package for your branch. If not, take the application off public access or isolate it on a dedicated server with strict limits, and plan a migration to a supported branch. The PHP project also published minimal source-code patches for 7.4, 8.0, and 8.1, but building them yourself means taking on your own maintenance burden.

How do I tell if the server has already been breached?

There are no definitive public indicators of compromise yet, so look for signs instead: unusual POST requests starting October 20, new PHP files in upload or cache directories, web-server processes spawning a shell or curl/wget, and outbound connections from the web server's user account. An empty result means "no traces found," not "guaranteed clean." If you suspect something, isolate the system, save the logs, and rotate any secrets PHP may have had access to.

Contents

Share this article

MANAGED VPS STARTING AT

$19 95 / mo

NEW INTEL XEON BASED SERVERS

$80 / mo

CDN STARTING AT

$0 / mo

 

By using this website you consent to the use of cookies in accordance with our privacy and cookie policy.