On 21 October 2026 the PHP project shipped emergency releases for two critical flaws in the PHP engine itself, now widely called PHP2Shell. Both are rated 9.2 on CVSS v4.0, both can give an unauthenticated attacker code execution as the web server user, and both sit in code that runs before your application does. This guide covers what the bugs are, which versions are fixed, how to find every PHP runtime on a server, how to patch on the common platforms, what to do if you cannot patch today, and how to look for signs that someone got in first.
What PHP2Shell is, in plain words
When PHP receives a web request it parses the request body, including file uploads, into internal structures before your code runs. The researchers found two ways to leave those structures in a broken state that a later step trusts:
- CVE-2026-109618 (out-of-memory corruption). If PHP runs out of memory part-way through setting up request data, cleanup can treat a half-built entry as valid. A request crafted to hit
memory_limitat the right moment corrupts memory. - CVE-2026-109619 (stale global state). A PHP worker handles many requests in turn. A fatal error can interrupt the reset between requests, so state from one request (freed memory) is used by the next one on the same worker: a use-after-free. Confirmed paths include session upload progress and, on PHP 8.5 only, fatal error backtraces.
Every way PHP serves HTTP is in scope: PHP-FPM, Apache mod_php, php-cgi, LiteSpeed and FrankenPHP. PHP says the risk is highest with mod_php. The application does not need to call session_start() or use uploads for the request parsing to happen. The PHP CLI used by cron jobs is not reachable over HTTP, but it is often a different build from the one serving web traffic, which is why the inventory step below matters.
Is it being exploited?
Sources differ, so here is what each says. Amazon Threat Intelligence logged exploitation attempts against its honeypots from 17 October, four days before disclosure. watchTowr reports exploitation of both CVEs in the wild from 21 October. Cloudflare saw attempts climb into the millions by 22 October, and Sophos and Arctic Wolf reported attempts to drop a Mirai botnet variant. On the other side, Sansec found that the public proof-of-concept exploits do not work against a default PHP install, and Wordfence and others had not confirmed a successful compromise when reported on 23 October. CISA's 21 October alert said it had not observed successful exploitation, and the CVEs were not in CISA's Known Exploited Vulnerabilities catalog as of its 23 October update. The UK NCSC urged organisations to patch immediately.
Treat it as urgent anyway. Scanning is universal, exploit quality will improve, and if you start looking for compromise, start from 17 October, not the patch date.
Who is affected and what fixes it
| PHP branch | Status | What to run |
|---|---|---|
| 8.5 | Fixed upstream | 8.5.12 or later |
| 8.4 | Fixed upstream | 8.4.27 or later |
| 8.3 | Fixed upstream | 8.3.36 or later |
| 8.2 | Fixed upstream (branch ends 31 Dec 2026) | 8.2.35 or later |
| 8.1, 8.0, 7.4 | End of life, no official release | A patched distro or LTS vendor package, PHP's source backports, or an upgrade |
| 7.3 and older, including PHP 5 | Affected, no upstream fix | Upgrade, a vendor backport if one exists, or isolate the system |
Step 1: find every PHP runtime on the box
The CLI, PHP-FPM and mod_php can be different versions, control panels install their own builds under /opt, and every container carries its own PHP. These checks only read, they change nothing.
# PHP binaries and modules on the root filesystem (repeat for other mounts such as /opt)
sudo find / -xdev -type f \( -name 'php' -o -name 'php[0-9]*' -o -name 'php-fpm*' -o -name 'php-cgi*' \
-o -name 'lsphp' -o -name 'frankenphp' -o -name 'libphp*.so' \) 2>/dev/null
# What is serving traffic right now
ps -eo pid,user,lstart,args | grep -E 'php|apache2|httpd|lsphp|frankenphp' | grep -v grep
# The actual binary behind each PHP-FPM master, then ask it for its version
for pid in $(pgrep -f 'php-fpm: master'); do sudo readlink /proc/$pid/exe; done
# Is Apache loading mod_php? (apache2ctl on Debian/Ubuntu)
sudo apachectl -M 2>/dev/null | grep -i php
# Installed packages
dpkg -l | grep -E '^ii +(php|libapache2-mod-php)' # Debian/Ubuntu
rpm -qa 'php*' | sort # RHEL family, Amazon Linux
# Containers
docker ps --format '{{.ID}} {{.Image}} {{.Names}}'
docker exec <container> php -vCommon panel locations: /opt/cpanel/ea-php*/root/usr/bin/php, /opt/plesk/php/*/bin/php, /opt/remi/php*/root/usr/bin/php and /usr/local/lsws/lsphp*/bin/php. Run each with -v. Do not leave a phpinfo() page on a public site to check; it tells attackers the same thing.
Step 2: patch, by platform
Ubuntu and Debian
Ubuntu fixed the archive packages in USN-8948-1 on 21 October: 22.04 (php8.1 8.1.2-1ubuntu2.28), 24.04 (php8.3 8.3.6-0ubuntu0.24.04.13), 26.04 (php8.5 8.5.4-0ubuntu1.5) and 26.10 (php8.5 8.5.9-0ubuntu2.1). Ubuntu Pro covers 14.04 to 20.04. Debian fixed trixie in DSA-6574-1 (php8.4 8.4.27-1~deb13u1) and bookworm in DLA-4848-1 (php8.2 8.2.35-1~deb12u1).
If you use Ondřej Surý's repository (ppa:ondrej/php or packages.sury.org), its 21 October builds include 8.3.36 and patched builds for older branches. Their changelogs list the two CVEs, but not always in the top entry, so search the whole changelog.
# Check (read-only): is the fix in the installed package? Use your branch's package name
zgrep -E 'CVE-2026-10961(8|9)' /usr/share/doc/php8.3-common/changelog.Debian.gz
# Change: install updates
sudo apt update && sudo apt upgradeRHEL, AlmaLinux and Rocky Linux
Red Hat's bulletin RHSB-2026-013 lists RHEL 7, 8, 9 and 10 as affected (RHEL 7 by CVE-2026-109619 only) and says fixes are being released in stages. Check the RHEL, AlmaLinux or Rocky errata for your PHP stream and confirm the advisory names the CVEs before you rely on it. If you use Remi's repository, Remi published 8.2.35, 8.3.36, 8.4.27 and 8.5.12 on 22 October in remi-modular, and as Software Collections (for example php83-php-* packages and the php83-php-fpm service) in remi-safe.
# Check (read-only)
dnf module list --enabled php 2>/dev/null # which stream is enabled (EL8/EL9)
rpm -q --changelog php-common | grep -E 'CVE-2026-10961(8|9)'
# Change: install updates
sudo dnf upgrade --refresh 'php*'Amazon Linux
On Amazon Linux 2023, php8.2, php8.3, php8.4 and php8.5 are fixed (ALAS2023-2026-3301 to 3304, 22 October). php8.1 on AL2023 and the core php package on Amazon Linux 2 are marked No Fix Planned, so those need an upgrade to a newer stream. The AL2 php8.2 extra is fixed in ALAS2PHP8.2-2026-015. AL2023 pins instances to a release version; if dnf finds nothing, run dnf check-release-update to see whether you need a newer release.
# Change: update your stream, e.g. php8.3
sudo dnf upgrade --refresh 'php8.3*'Windows and IIS
windows.php.net has builds for 8.2.35, 8.3.36, 8.4.27 and 8.5.12. On 26 October these were rebuilt as -1 packages (for example 8.4.27-1) to pick up a separate Brotli fix, so download the latest. Nothing is published for 8.1 or older. Replace the PHP folder that IIS's FastCGI handler points to, keep your php.ini, then recycle the application pools or run iisreset. Check php-cgi.exe -v at the exact path IIS uses. Bundles like XAMPP and WampServer carry their own copy.
cPanel, Plesk and other control panels
Panels ship and update their own PHP builds, which are separate from the OS packages. Run the panel's update (WHM and EasyApache 4 for cPanel, Tools & Settings > Updates or plesk installer for Plesk). Check each installed version under /opt/cpanel or /opt/plesk/php, and confirm the panel's changelog names the CVEs. CloudLinux alt-php is updated separately. Plesk also runs its own PHP engine for the panel itself, so make sure that is covered too.
Docker and Kubernetes
Patched official php image tags (8.2.35, 8.3.36, 8.4.27, 8.5.12 in -fpm, -apache, -cli and other variants) were on Docker Hub by 24 October. The official 8.1 and older tags have not been updated. A running container keeps its old PHP until you rebuild and recreate it, and images that install PHP from distro packages need a rebuild too. Also check pinned digests, base images built by other teams, and third-party images such as WordPress.
# Change: rebuild against fresh base images and recreate containers
docker compose build --pull
docker compose pull
docker compose up -d
# Verify inside the running service
docker compose exec <service> php -vStep 3: restart workers and verify
Restart, do not just reload. Red Hat specifically advises a restart.
# Debian/Ubuntu (match your version)
sudo systemctl restart php8.3-fpm
sudo systemctl restart apache2 # only if Apache uses mod_php
# RHEL family / Amazon Linux
sudo systemctl restart php-fpm
sudo systemctl restart httpd # only if Apache uses mod_php
# Verify (read-only): start times should be after the update
ps -eo pid,lstart,args | grep -E 'php-fpm: master|apache2|httpd|lsphp' | grep -v grep
sudo needrestart -r l # Debian/Ubuntu, if installed
sudo dnf needs-restarting # RHEL familyFor LiteSpeed, restart the server and check that no old lsphp processes are still running. Then repeat the Step 1 inventory and confirm every runtime is on a fixed version or a package whose changelog lists both CVEs.
If you cannot patch today: temporary workarounds
PHP is clear that no configuration change makes an installation safe. These settings only reduce the risk while you patch:
session.upload_progress.enabled = 0blocks the known CVE-2026-109619 path. On PHP 8.5 also setfatal_error_backtraces = 0.memory_limit = -1removes the allocation failure CVE-2026-109618 relies on. Use it only with a hard memory cap outside PHP (container limit or systemdMemoryMax=). Otherwise one request can use all the server's memory.- Keep request bodies small:
post_max_sizeof 8M (32M if you need uploads), withupload_max_filesizebelow it, plus a matching limit in front of PHP (client_max_body_sizein nginx,LimitRequestBodyin Apache). - Do not cap memory with
RLIMIT_ASorRLIMIT_DATA(for example systemdLimitAS=). That can cause exactly the allocation failure the bug needs. - If the application does not take uploads, consider blocking
multipart/form-dataat the proxy or WAF. Amazon also suggests this and has published AWS WAF guidance. Restrict internet access to anything you cannot patch.
Set these where requests cannot override them, before the request is parsed: php.ini, php_admin_flag / php_admin_value in the FPM pool, or php_admin_flag in Apache. Do not use .user.ini or ini_set(). Then restart the workers. For example, in a PHP-FPM pool file:
php_admin_flag[session.upload_progress.enabled] = off
php_admin_value[post_max_size] = 8MStep 4: look for signs of compromise
A successful exploit may not crash anything or leave an error, so finding nothing proves nothing. Check from 17 October onward. These indicators come from Datadog Security Labs and Amazon:
- Bursts of
Allowed memory size of ... bytes exhausted ... in Unknown on line 0. "Unknown on line 0" means PHP died before any script ran. Normal heavy pages can also trigger it, so look for bursts from the same sources. - Apache
AH00052: child pid ... exit signal Segmentation fault (11). A crash can mean an exploit attempt that guessed a memory address wrong. - Multipart requests with
PHP_SESSION_UPLOAD_PROGRESSorPHPSESSIDform fields your app does not use, deeply nested or very long field names, or upload filenames containing<?phpor|O:. Standard access logs do not record request bodies, so check WAF or proxy logs. - The web server user (
www-data,apache,nginx) startingsh,bash,curlorwget. - New or changed PHP files in web roots, unknown binaries in
/tmp,/var/tmpor/dev/shm, new cron entries for the web user, and unexpected outbound connections.
# All read-only. Adjust log paths and the web user for your system.
sudo zgrep -h 'Unknown on line 0' /var/log/php*fpm*.log* /var/log/apache2/error.log* /var/log/httpd/error_log* /var/log/nginx/error.log* 2>/dev/null | grep -c exhausted
sudo zgrep -h 'AH00052' /var/log/apache2/error.log* /var/log/httpd/error_log* 2>/dev/null | grep -i segmentation | tail
sudo find /var/www -type f -name '*.php' -newermt '2026-10-17' -ls # expect your own deploys here too
sudo find /tmp /var/tmp /dev/shm -type f -newermt '2026-10-17' -ls
sudo crontab -u www-data -l
ps -u www-data -o pid,ppid,lstart,args --forest
sudo ss -tnp | grep -E 'php|apache2|httpd'Amazon published source IPs from its honeypots, but warns they are shared anonymisation infrastructure. Use them to prioritise investigation, not as a blocklist.
If you find signs of compromise
- Isolate the host from the internet, or pull it out of the load balancer. Take a disk snapshot and keep the logs before you change anything.
- Do not just delete the web shell and move on. Assume the attacker ran code as the web user and read everything that user could read.
- Rotate every credential the server held: database passwords, API keys in
.envand config files, SMTP credentials and cloud access keys. On AWS, review what the instance role can do and check CloudTrail for its use. See the AWS security baseline checklist. - Rebuild from a known-good image or clean install, patch it, and redeploy application code from source control, not from the compromised disk.
- Check where else it could have reached: other servers that share credentials, and the database for new admin users.
- Work out whether personal data was exposed. In the UK, a reportable breach must go to the ICO within 72 hours of you becoming aware of it (UK GDPR obligations). US organisations should check state breach-notification rules and any contracts.
Sources
- PHP security advisory and releases and supported versions
- watchTowr PHP2Shell FAQ
- Datadog Security Labs analysis
- Amazon Threat Intelligence MadPot report
- Sansec research
- Ubuntu CVE-2026-109618, Debian tracker, Red Hat RHSB-2026-013, Amazon Linux
- CISA Known Exploited Vulnerabilities catalog
HashTech Systems offers a fixed-price PHP2Shell remediation service covering inventory, patching or backporting, worker restarts, verification and a compromise check.

