PHP2Shell is two critical vulnerabilities in PHP itself, CVE-2026-109618 and CVE-2026-109619, that can let an unauthenticated web request execute code on any PHP installation serving web requests: PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed. Both are exploited in the wild and listed in CISA's KEV catalog. The fix is PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35; PHP 8.1 and older get no upstream release. Upgrade, then rebuild and redeploy every container image that carries PHP, including old tags still running and images you shipped to customers.
Patching the server you know about is the easy part. The exposure that lingers is the PHP you forgot: a runtime compiled into a base image, an image tag from last year that still runs in production, a CI image, or a product shipped to customers with an end-of-life PHP. This page gets you patched first, then shows how to find the rest.
What happened
| Date (2026) | Event | Source |
|---|---|---|
| 11 and 21 August | Calif reports the two bugs to the PHP project | Calif |
| 17 October | The first exploitation attempt Amazon saw in its honeypots, before disclosure | Amazon Threat Intelligence |
| 20 October | PHP announces a security release for 21 October, 14:00 UTC, fixing two vulnerabilities rated CVSS 4.0 9.2 | php.net |
| 21 October | PHP releases 8.5.12, 8.4.27, 8.3.36 and 8.2.35 and publishes its advisory; the CVE records go public; NCSC-NL opens its php2shell repository | PHP advisory, NCSC-NL |
| 21 October | The official Docker php images are updated to the fixed versions | docker-library/php |
| 21 October | watchTowr reports exploitation of both CVEs in the wild; CISA publishes an alert | watchTowr, CISA |
| 22 October | Searchlight Cyber publishes php2shell-scanner | GitHub |
| 23 October | PHP advises setting session.upload_progress.enabled=0 for a separate, unlikely RCE vector, CVE-2026-115560 | php.net |
| 26 October | CISA adds both CVEs to the KEV catalog, with a due date of 29 October for US federal agencies | CISA KEV |
The name PHP2Shell comes from Calif's write-up and is used across industry coverage; PHP's own advisories don't use it. The CVE records credit Calif, Wiz and OpenAI.
Who is affected
Any PHP that serves web requests. PHP's advisory names PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed, and calls the risk highest for mod_php, "with a demonstrated exploit where an unauthenticated request can trigger the attack before any application code runs." Because the bugs are in PHP itself, applications built on Laravel, Symfony or WordPress are exposed whenever the PHP under them is.
| Branch | Affected | Fixed in | Upstream support |
|---|---|---|---|
| 8.5 | before 8.5.12 | 8.5.12 | Active |
| 8.4 | before 8.4.27 | 8.4.27 | Active |
| 8.3 | before 8.3.36 | 8.3.36 | Security fixes only, until 31 December 2027 |
| 8.2 | before 8.2.35 | 8.2.35 | Security fixes only, until 31 December 2026 |
| 8.1, 8.0, 7.4 | all versions, as reported by Calif and watchTowr | no release; minimal source backports in php-src | End of life |
| 7.3 and older, including PHP 5 | all versions, as reported by Calif and watchTowr | no upstream fix | End of life |
PHP's advisories list only the supported branches. For older ones, Calif reports that "the earliest affected path we found dates to PHP 5.4.0", and watchTowr lists PHP 7 and PHP 5 as affected with no fix. PHP says that branches 8.1 and older "no longer receive updates from the PHP project", but it published minimal source backports for 7.4, 8.0 and 8.1 for people who build from source. Some distributions and commercial long-term support vendors ship backported fixes for end-of-life branches; ask yours. If you test the next major version, PHP 8.6.0 RC4, released the same day, also contains the fixes.
How it works
Both CVEs are memory-safety bugs in the way PHP sets up and tears down a request, below any application code. The CVE records classify both as use after free (CWE-416), along with improper handling of exceptional conditions, access of an uninitialized pointer, uncontrolled memory allocation and incorrect initialization (CWE-755, CWE-824, CWE-1325, CWE-1419). In PHP's words:
- CVE-2026-109618, RCE via OOM corruption: "out-of-memory handling can interrupt PHP while internal request data is only partially initialized. Request cleanup may then treat the invalid data as valid." A request body built to exhaust memory while PHP parses it can trigger that exit. Apache with mod_php runs the cleanup even when request startup failed, which is why the attack works there before any application code runs. For PHP-FPM, PHP's advisory says source analysis supports it as susceptible, though no complete FPM exploit has been demonstrated.
- CVE-2026-109619, RCE via stale global state: "request-owned internal state can survive after its backing memory has been freed. A later request handled by the same worker may use that stale state before normal initialization resets it." It takes two requests that reach the same worker: one that leaves the state behind, for example through a fatal error, and one that uses it. PHP's advisory confirms the session upload progress path against mod_php and FPM, and the PHP 8.5 fatal error backtrace path against mod_php.
That is why PHP's interim settings target memory limits, request body size, session upload progress and fatal error backtraces: each closes one path to the bugs, not the bugs themselves. PHP, as the CNA, scores both 9.2 under CVSS 4.0. NVD's own CVSS 3.1 assessments are 9.8 for CVE-2026-109618 and 8.1 for CVE-2026-109619.
How to check whether you are affected
Start with the hosts you know, then look for PHP in everything you build and ship.
On a host or VM, check the version PHP reports. Check the web SAPI too, since php-fpm or the Apache module can come from a different package than the command line:
php -v
php-fpm -v
Distribution packages (Debian, Ubuntu, Alpine, RHEL) often get the fix as a backport without a new upstream version: Ubuntu 24.04, for example, ships the fix as 8.3.6-0ubuntu0.24.04.13. A version check alone misleads here. List the installed package and compare it with the fixed package version in your distribution's security tracker:
dpkg -l 'php*' | grep '^ii' # Debian, Ubuntu
apk info -v | grep '^php' # Alpine
rpm -qa 'php*' # RHEL, Fedora, Rocky, Alma
In container images. Official php: images compile PHP from source, so the version lives in the image itself, not in a distribution package list. Ask the image directly:
docker run --rm --entrypoint php php:8.3-fpm -v
To check every image you actually run, list them and ask each one. For Kubernetes:
kubectl get pods -A \
-o jsonpath='{range .items[*]}{range .spec.containers[*]}{.image}{"\n"}{end}{end}' \
| sort -u \
| while read -r image; do
version=$(docker run --rm --entrypoint php "$image" -r 'echo PHP_VERSION;' 2>/dev/null)
printf '%s\t%s\n' "${version:-no php}" "$image"
done
Images built FROM debian or FROM alpine with PHP installed by the package manager need the package check above, run inside the image.
In your repositories, search for every place PHP is chosen:
git grep -nE '^FROM[[:space:]]+(.*/)?php:' -- '*Dockerfile*' '*.dockerfile'
git grep -nE 'php-version|image:.*php' -- .github/workflows '*.yml' '*.yaml'
Pinned tags such as php:8.3.30-fpm stay vulnerable until someone changes them. Floating tags such as php:8.3-fpm were rebuilt with the fixed versions from 21 October, but an image built before then keeps the old PHP until you rebuild it. The php constraint in composer.json says which versions your code accepts, not which one runs.
In CI. Build and test jobs often use their own PHP: a php: service container, a setup action with a php-version input, or a runner image. They don't serve production traffic, but they show which images and versions your pipeline produces.
Exposed hosts. Searchlight Cyber's php2shell-scanner probes a URL with large multipart requests, about 29 MiB per path. Run it only against hosts you're authorized to test. Its README warns of false positives on patched PHP with a low memory_limit, and when PHP doesn't disclose its version the scanner can't tell a patched server from a vulnerable one. Treat it as a pointer, not proof. NCSC-NL's repository collects detection rules and a list of products that bundle PHP, with their status.
What today's scan can't tell you. Scanning the images in your registry today covers what you'd deploy today. It doesn't cover an earlier release you shipped to a customer, the old tag a customer still runs, or which of your builds produced images with a vulnerable PHP. For that you need a record of what each build put in, per release. See are we affected? how to assess a new CVE across your builds.
How to fix it, and what to do if you can't patch yet
- Upgrade to 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or to your distribution's fixed package. On 8.1 or older, move to a supported branch; PHP's backports and vendor builds are stopgaps.
- Rebuild every image from a fixed base (pull the base image again, or build without cache) and redeploy. Images already running keep their old PHP until they are replaced, so restart the workloads too.
- Verify with the checklist below, against the running containers, not just the new image.
If you can't upgrade yet, PHP's advisory lists measures that "reduce exposure to specific attack paths but do not make an installation safe." For CVE-2026-109618:
- "Set
memory_limit=-1, preferably in a way applications can't override, such asphp_admin_valuein the FPM pool or Apache configuration, and check that no pool, directory or application setting puts a finite limit back." - "For applications that don't require file uploads, set
post_max_size = 8M. If file uploads are necessary, setpost_max_size = 32M. Keepupload_max_filesizebelowpost_max_size." - Use a container or cgroup memory limit as the availability boundary, and don't substitute
RLIMIT_ASorRLIMIT_DATA, "as these can cause the vulnerable allocation failure."
For CVE-2026-109619:
- "Set
session.upload_progress.enabled=0and restart every worker. Usephp.ini, an FPM pool or server configuration that applies before request parsing. Do not rely onini_set()or FPM.user.ini." - "On PHP 8.5, set
fatal_error_backtraces=0and restart every worker."
Check what each worker actually runs with:
php-fpm -i | grep -E 'memory_limit|post_max_size|session.upload_progress.enabled'
CISA's alert goes further: organizations that can't update immediately, or that still run PHP 8.1 or earlier, "should take PHP-based systems offline until they can be patched or updated." It also advises watching for PHP worker processes that launch shells or download tools.
Verify the fix
- Ask the running PHP, not the image you meant to deploy. Run
php-fpm -v(orphp -v) inside each running container and on each host, and confirm 8.5.12, 8.4.27, 8.3.36, 8.2.35 or later. - Don't trust the version string alone on distribution packages. Debian, Ubuntu, RHEL and other distributions backport the fix into the version they already ship, so a patched PHP can still report 8.3.6: Ubuntu 24.04's fix is
8.3.6-0ubuntu0.24.04.13. Compare the full package version with your distribution's security tracker, or look for the CVE in the package changelog, which usually names it. The trap works the other way too: a scanner that compares only upstream version numbers may flag a backported package as vulnerable. - Check that every workload runs the rebuilt image. A tag that now points to a fixed image doesn't change a pod that pulled the old one. Compare the image digest each container runs with the digest you built:
kubectl get pods -A \
-o jsonpath='{range .items[*]}{range .status.containerStatuses[*]}{.image}{"\t"}{.imageID}{"\n"}{end}{end}'
- Check that every worker restarted. PHP-FPM and Apache keep the old code loaded until they restart; the process check under "Check for compromise" shows any that still run it.
- If you still rely on the interim settings, check each pool with the
php-fpm -icommand above, and keep them until the upgrade is done everywhere. - Run the inventory again from the checking section across your registry, CI images and the releases customers run, not only production.
Check for compromise
PHP2Shell is exploited in the wild, and Amazon saw the first attempt on 17 October 2026, four days before the fix. If an internet-facing PHP ran an affected version without the interim settings at any time since then, check it.
How big the exposure is. Shodan banner counts that Mallory published on 20 October, reported by CybelAngel, found 2.24 million internet-facing hosts advertising PHP; of the roughly 1.5 million that show a version, 883,717 (59%) run a branch with no official fix. These count hosts, not confirmed vulnerable systems: backported packages can look old and be patched, and about 740,000 hosts hide their version.
Appliances and products that bundle PHP. PHP also runs inside appliances, network devices, management interfaces and vendor products where you may not be able to update it yourself. NCSC-NL's inventory tracked 968 such products on 9 November 2026: 205 fixed, 215 vulnerable, 93 under investigation, 240 runtime-dependent and 214 not affected. Check those products separately and apply vendor updates as soon as they're available.
Make sure the fix is really running. On hosts and VMs, a PHP-FPM, php-cgi or Apache process that started before the upgrade still runs the old code. Its memory map then shows the replaced binary or libphp as deleted. Compare process start times with the time of the upgrade too:
for pid in $(pgrep -f 'php-fpm|php-cgi|apache2|httpd|frankenphp'); do
grep -qE '(php|libphp)[^/]*\(deleted\)' "/proc/$pid/maps" 2>/dev/null \
&& echo "PID $pid still runs a replaced PHP"
done
ps -o pid,lstart,args -p "$(pgrep -d, -f 'php-fpm|php-cgi|apache2|httpd|frankenphp')"
Indicators from primary sources:
- Requests: form-upload POST requests containing the string
PHP_SESSION_UPLOAD_PROGRESS, which Amazon Threat Intelligence saw in exploitation attempts. Amazon also lists source addresses (103.216.220.92,31.223.184.166,37.19.200.5,134.209.159.64,103.114.163.188,95.217.98.37) but notes they belong to shared anonymization infrastructure: investigate a match, don't treat it as proof. - Processes and files: PHP worker processes that launch shells or download tools (CISA, which added a Sigma detection rule on 23 October), and unexpected process execution or file changes on web servers (Amazon).
- Logs: in Apache, PHP-FPM, reverse proxy and service logs, warnings or core errors repeated before scripts run, fatal and parse errors, and
Maximum execution time exceeded. PHP's advisory adds that "absence of an error in logs is not proof of safety."
When to assume compromise. If you find any of these indicators, or if an exposed server ran an affected PHP without mitigations and your logs can't rule exploitation out, treat it as compromised. PHP describes the impact as remote code execution as the web worker account, and that account can usually read the application's configuration and secrets.
Rotate what the worker could read. Database passwords, API keys and tokens in environment variables and configuration files, keys that sign sessions or cookies (and the sessions signed with them), and any cloud credentials available to the host. Rotate after the host is rebuilt, so the new secrets never sit on a compromised machine.
Rebuild instead of cleaning. Replace a compromised host or container from a known-good image, and don't carry over files the worker could write, such as upload directories, caches or generated code. Don't reuse images, build caches or artifacts produced on a compromised host.
What to tell your customers
If you ship software that runs on PHP, your customers will ask. Answer with versions and actions, even before you have every answer. A reply you can adapt:
PHP2Shell (CVE-2026-109618, CVE-2026-109619) affects PHP versions before 8.5.12, 8.4.27, 8.3.36 and 8.2.35, and all older PHP branches. [Product] versions [x to y] ship PHP [version] and are affected; version [z], released [date], ships PHP [fixed version]. Please upgrade to [z]. If you can't upgrade right away, apply the PHP settings in our advisory [link] and limit network access to the application. [Product] versions [a to b] are not affected. We will update this notice by [date].
If you're still checking, say so, and say when you'll answer. For a first holding statement and templates for each outcome, see how to answer customers after a supply chain attack.
How CRACI helps
CRACI runs your GitHub Actions jobs and records what each job pulls in as it builds, including container images, apt and apk packages and Composer packages, and it keeps every past build with its SBOM. When a CVE is published, continuous re-evaluation flags the past builds that contained the affected package, so you see which releases were exposed and for how long, not just what today's scan finds. Policy gates can block builds that contain a specific CVE.
One limit for this incident: PHP compiled into an official php: image arrives inside the image, not as a package download. CRACI records the image reference each build pulled, so search your builds for the php: tags as well as for PHP packages.
Find every build that shipped a vulnerable PHP
More on build records: build-time SBOMs, Docker image SBOMs, what container scanning misses, the CISA KEV catalog and CVE publication trends. Other incidents: all vulnerabilities.
For PHP security beyond this incident, see our PHP security guide.
Sources and updates
Primary sources:
- PHP: security advisory of 21 October 2026, informational advisory of 23 October 2026, GHSA-w8hw-w9fx-j79w, GHSA-6wmf-4rv5-p3rp, supported versions
- CVE records: CVE-2026-109618, CVE-2026-109619
- CISA: alert of 21 October 2026, KEV catalog
- NCSC-NL: php2shell repository
- NVD: CVE-2026-109618, CVE-2026-109619
- FIRST: EPSS scores
Research and analysis:
- Calif: PHP2Shell, a tale of two PHP remote vulnerabilities
- Amazon Threat Intelligence
- watchTowr
- Akamai
- CybelAngel
- Searchlight Cyber php2shell-scanner
Updates:
- 2026-11-11: Added NVD's scores, EPSS, CWE classes, credits, latest safe versions and end-of-life dates to the box, PHP 8.6.0 RC4, and sections on how the bugs work, verifying the fix and checking for compromise.
- 2026-11-11: First published.