131% more reported CVEs per day in 2026 than last year.
← All vulnerabilities

PHP2Shell: unauthenticated RCE in PHP and how to check whether your builds are affected

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)EventSource
11 and 21 AugustCalif reports the two bugs to the PHP projectCalif
17 OctoberThe first exploitation attempt Amazon saw in its honeypots, before disclosureAmazon Threat Intelligence
20 OctoberPHP announces a security release for 21 October, 14:00 UTC, fixing two vulnerabilities rated CVSS 4.0 9.2php.net
21 OctoberPHP 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 repositoryPHP advisory, NCSC-NL
21 OctoberThe official Docker php images are updated to the fixed versionsdocker-library/php
21 OctoberwatchTowr reports exploitation of both CVEs in the wild; CISA publishes an alertwatchTowr, CISA
22 OctoberSearchlight Cyber publishes php2shell-scannerGitHub
23 OctoberPHP advises setting session.upload_progress.enabled=0 for a separate, unlikely RCE vector, CVE-2026-115560php.net
26 OctoberCISA adds both CVEs to the KEV catalog, with a due date of 29 October for US federal agenciesCISA 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.

BranchAffectedFixed inUpstream support
8.5before 8.5.128.5.12Active
8.4before 8.4.278.4.27Active
8.3before 8.3.368.3.36Security fixes only, until 31 December 2027
8.2before 8.2.358.2.35Security fixes only, until 31 December 2026
8.1, 8.0, 7.4all versions, as reported by Calif and watchTowrno release; minimal source backports in php-srcEnd of life
7.3 and older, including PHP 5all versions, as reported by Calif and watchTowrno upstream fixEnd 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

  1. 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.
  2. 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.
  3. 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 as php_admin_value in 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, set post_max_size = 32M. Keep upload_max_filesize below post_max_size."
  • Use a container or cgroup memory limit as the availability boundary, and don't substitute RLIMIT_AS or RLIMIT_DATA, "as these can cause the vulnerable allocation failure."

For CVE-2026-109619:

  • "Set session.upload_progress.enabled=0 and restart every worker. Use php.ini, an FPM pool or server configuration that applies before request parsing. Do not rely on ini_set() or FPM .user.ini."
  • "On PHP 8.5, set fatal_error_backtraces=0 and 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

  1. Ask the running PHP, not the image you meant to deploy. Run php-fpm -v (or php -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.
  2. 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.
  3. 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}'
  1. 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.
  2. If you still rely on the interim settings, check each pool with the php-fpm -i command above, and keep them until the upgrade is done everywhere.
  3. 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:

Research and analysis:

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.

Frequently asked questions

What is PHP2Shell?

PHP2Shell is the name used for two critical vulnerabilities in PHP itself, CVE-2026-109618 and CVE-2026-109619. Both can let an unauthenticated web request run code on a server that runs PHP. PHP fixed them on 21 October 2026 and rates each 9.2 under CVSS 4.0.

Is PHP2Shell being exploited?

Yes. Amazon Threat Intelligence reported an exploitation attempt dated 17 October 2026, before disclosure, and watchTowr reported exploitation of both CVEs in the wild on 21 October. CISA added both CVEs to its Known Exploited Vulnerabilities catalog on 26 October 2026.

Which PHP versions fix PHP2Shell?

PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35, released on 21 October 2026. Every earlier release of those branches is affected. Distribution packages may carry the fix under an older version number, so check your distribution's security tracker.

Is PHP 7 or PHP 5 affected, and is there a fix?

PHP 8.1 and older no longer get releases from the PHP project. Calif and watchTowr report that PHP 7 and PHP 5 are affected too. The PHP security team published minimal source backports for 7.4, 8.0 and 8.1 as branches in php-src; for older versions there is no upstream fix. Upgrade, or ask your distribution or long-term support vendor.

Does PHP2Shell affect PHP-FPM, Apache mod_php, FrankenPHP and LiteSpeed?

Yes. PHP's advisory names all installations serving web requests, including PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed, and considers the risk highest for mod_php. Upgrade the command-line PHP too; it usually comes from the same build.

What can I do if I can't upgrade right now?

Set memory_limit=-1 in a way applications can't override, set post_max_size to 8M (32M if you need uploads), and set session.upload_progress.enabled=0, then restart every worker. PHP says these measures reduce exposure to specific attack paths but do not make an installation safe. CISA advises taking PHP systems offline if you can't patch.

How do I know whether a server was compromised?

Look for form-upload POST requests containing PHP_SESSION_UPLOAD_PROGRESS, PHP workers that start shells or download tools, and unexpected file changes, going back to 17 October 2026, when Amazon saw the first exploitation attempt. If an internet-facing server ran an affected PHP without the interim settings and you can't rule exploitation out, treat it as compromised: rebuild it from a clean image and rotate every secret the web worker could read.