- CVE
- how-to
- OSV
PHP2Shell: which running Kubernetes images and Helm charts carry CVE-2026-109618 and CVE-2026-109619, and why a scanner can say zero
Fixed in PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35. Find every running image with PHP, see which Helm charts ship it, and learn why OSV-based scanners miss some.
StackRadar Team
10 min read
PHP2Shell is two bugs in the PHP engine, CVE-2026-109618 and CVE-2026-109619, that let an unauthenticated request execute code in any PHP installation serving web traffic. PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35, released on 21 October 2026, fix both; older branches get the fix only from a distribution. Exploitation attempts were seen before disclosure. As of 23 October the CISA KEV catalogue does not list either id and EPSS puts both around 0.05, the 93rd percentile. In a Kubernetes cluster the PHP that matters is mostly inside images you did not build, and some of those images are invisible to the scanner you would use to find them.
This post is the same-day version of the evergreen "which running images are affected by a CVE" procedure, applied to PHP2Shell: how to list every running container with PHP in it, the fixed package versions per distribution, which published Helm charts still ship a vulnerable PHP today, how to fix each case, and why an OSV-based scanner, ours included, reports zero for a whole class of affected images.
What PHP2Shell is
The PHP advisory describes two separate faults with the same outcome. CVE-2026-109618 is remote code execution via out-of-memory corruption: PHP handles an out-of-memory condition by jumping out of the current context, and some code paths leave request memory half-initialised when that happens, so the request cleanup that follows treats garbage as valid data. CVE-2026-109619 is remote code execution via stale global state: request-owned state can outlive the memory that backed it, and the next request handled by the same worker can use it before normal initialisation resets it. The second one also means a worker can leak memory from a previous request, which is a credential problem even where the code execution path is hard to reach.
PHP says every installation serving web requests is at risk, including PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed, with mod_php the worst case because the demonstrated exploit fires before any application code runs. Details leaked on 20 October, a day before the planned disclosure, which is why the release came out when it did. The vendor-reported exploitation picture is mixed: honeypot hits from 17 October, attempts seen by several vendors since the release, and at least one research team saying the public proofs of concept need a second weakness on the target. None of that changes what to do.
Are you running it? Find the PHP first
There is no "php" in most of the image names that carry PHP. It is in WordPress, Nextcloud, Zabbix's web frontend, Matomo, Snipe-IT, Moodle, the admin tools, and in whatever your own team built on a php: base image. Start wide, with names, and narrow with the binary.
# Every running container whose image name suggests PHP inside: the
# applications people pull, not only images called php. Names come from the
# pod spec, which is what was asked for; the digest check is further down.
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{range .spec.containers[*]}{.name}={.image}{" "}{end}{"\n"}{end}' \
| grep -iE 'php|wordpress|nextcloud|owncloud|zabbix|matomo|snipe|drupal|joomla|mediawiki|moodle|icinga|checkmk|nominatim|adminer|phpmyadmin|phppgadmin|rss|laravel|magento|roundcube|bookstack|librenms|limesurvey|dolibarr|typo3' \
| sortThat list has false positives (an image called rss that is Go) and misses anything named after the product instead of the stack. It is the starting list, not the answer. The answer is the binary:
# The authoritative check, per container: what the binary says.
kubectl exec -n <namespace> <pod> -c <container> -- php -v
# Debian and the php.net images report the fixed release directly: 8.5.12,
# 8.4.27, 8.3.36, 8.2.35. Ubuntu backports the fix and keeps the old upstream
# number, so "PHP 8.3.6" tells you nothing; the package revision does.
kubectl exec -n <namespace> <pod> -c <container> -- sh -c 'dpkg -l "php*" 2>/dev/null | grep "^ii" || apk info -v 2>/dev/null | grep "^php"'The Ubuntu caveat in that block matters more than it looks. Ubuntu 24.04's fixed package is php8.3 version 8.3.6-0ubuntu0.24.04.13, and php -v inside it prints PHP 8.3.6 before and after the fix. On Debian and on the php.net images the upstream version changes, so php -v alone is enough there. Where the output is a package version, this is the table to compare against, taken from the OSV records on 23 October:
| Distribution | Package | Fixed in |
|---|---|---|
| php.net source and official images | php | 8.5.12, 8.4.27, 8.3.36, 8.2.35 |
| Debian 13 | php8.4 | 8.4.27-1~deb13u1 |
| Debian 12 | php8.2 | 8.2.35-1~deb12u1 |
| Ubuntu 26.04 | php8.5 | 8.5.4-0ubuntu1.5 |
| Ubuntu 24.04 | php8.3 | 8.3.6-0ubuntu0.24.04.13 |
| Ubuntu 22.04 | php8.1 | 8.1.2-1ubuntu2.28 |
| Ubuntu 20.04 (Pro) | php7.4 | 7.4.3-4ubuntu2.29+esm5 |
Alpine, Red Hat and the Bitnami packages have their own trackers; check them for your release rather than trusting a table in a blog post. The chart index page for CVE-2026-109618 shows the fixed version next to every affected package it has seen in the wild, and is regenerated as the OSV records change.
Which Helm charts still ship a vulnerable PHP
We render the latest version of every chart on Artifact Hub, pull the images each one deploys, and match their SBOMs against our OSV mirror; that is the chart index. On 23 October, two days after disclosure, the latest versions of 21 of 18,232 charts deploy an image with a vulnerable distro-packaged PHP, across 20 distinct images. All of them are Ubuntu php7.4, php8.1, php8.3 or php8.5, Debian php8.2, or Bitnami's php and libphp, and every one of those packages already has a fixed version; what is missing is a rebuilt image. The names are the self-hosted usual suspects:
- Zabbix, four charts (zabbix-community, cetic, and two single-author server charts), all through the
zabbix-web-nginx-*frontend images. - WordPress, two charts, one of them Bitnami's with its
bitnami/wordpress:latestimage; Moodle and Matomo on Bitnami-built images too. - Snipe-IT, two charts; Nominatim, two charts; ownCloud, Icinga 2, Checkmk, Tiny Tiny RSS, RSS-Bridge, phpPgAdmin and three small single-publisher charts.
When the two pages went up on 22 October the count was 26. Nextcloud, Drupal, Joomla, MediaWiki, Coolify and Adminer dropped off within a day because their image publishers rebuilt; the pages CVE-2026-109618 and CVE-2026-109619 list every remaining chart with the image digest, the package and the fixed version, and shrink as the rest follow. If you run one of those charts, the fix is a chart version that points at a rebuilt image, and until there is one, a values override to a newer tag or digest. Twenty-one is a floor, not a count of affected charts. It is the number our matcher can see, and the next section is about the ones it cannot.
The images an OSV-based scanner cannot see
Every image built FROM php:8.x-apache or FROM php:8.x-fpm, which includes the official Nextcloud and WordPress images and a large share of in-house PHP images, compiles PHP from source. There is no php8.2 package inside, no dpkg entry, no apk entry. Syft's binary cataloger still finds the interpreter and records it as pkg:generic/php@8.5.12 and pkg:generic/php-cli@8.5.12 in the SBOM, which is correct and useful. Then the match step has nothing to match it against.
OSV publishes PHP2Shell as distribution records: DEBIAN-CVE-2026-109618, UBUNTU-CVE-2026-109618, the Bitnami BIT-php and BIT-libphp records, and their 109619 twins. Each one is keyed on a distro package. The upstream record, CVE-2026-109618 itself, has an empty affected list as of 23 October. Nothing in OSV is keyed on pkg:generic/php, so a matcher that works from OSV, which is OSV-Scanner, the StackRadar CLI and dashboard and the chart index, produces no finding for a source-built PHP. Not "unknown": nothing.
nextcloud:35.0.2-apache and wordpress:latest. Both score zero critical findings in our index. Both tags were rebuilt on 21 October with fixed PHP, so today that zero happens to be right. The same tags pulled on 20 October, with vulnerable PHP inside, scored exactly the same zero. A number that does not change when the bug is fixed was never measuring the bug.This is the same shape as the false negatives we wrote about in October, and it is not specific to us or to PHP: any interpreter or server compiled into an image rather than installed from a package has it, and our matching docs say as much. PHP2Shell is just the first case in a while where the compiled binary sits in a huge fraction of self-hosted images and the fix is a rebuild nobody is notified about. Trivy and Grype both consume the same distro feeds for the packaged case; Grype can additionally match a binary through NVD's CPE data once NVD populates the configurations, which usually takes days. We have not run either against a source-built image for these ids and will not claim a result we did not measure. If you have, we would like to see the output.
What works today for source-built PHP is the check that does not need a database at all:
# For an image you do not build: is the digest you run the rebuilt one?
# Left: what the kubelet pulled. Right: what the tag resolves to today.
kubectl get pods -A -o jsonpath='{range .items[*]}{range .status.containerStatuses[*]}{.image}{" "}{.imageID}{"\n"}{end}{end}' \
| sed 's#docker-pullable://##' | grep -iE 'wordpress|nextcloud' | sort -u
crane digest docker.io/library/nextcloud:35.0.2-apache # go-containerregistry; or docker buildx imagetools inspectIf the two digests differ for a tag the publisher rebuilt on or after 21 October, you are running the old build; roll the Deployment so the kubelet pulls again, and if your pods pin by digest, update the pin. If they match, php -v inside the container is the confirmation.
Fix
For images you build, change the base image tag or digest and rebuild; php:8.4-apache has pointed at 8.4.27 since 21 October, so a rebuild with no other change picks it up, and a digest pin needs the new digest. For images you pull, move to the tag or chart version the publisher rebuilt, and verify with the digest check above because a rebuilt latest looks identical to a stale one in a manifest. Do not patch a running container with apt-get; the patching post explains why that creates a pod nobody can reproduce.
If you cannot roll yet, PHP publishes mitigations and is careful to say they reduce exposure to specific paths and do not make an installation safe:
; php.ini, or the FPM pool / Apache vhost as php_admin_value so no
; application, .htaccess or ini_set() can put the old values back.
memory_limit = -1
post_max_size = 8M ; 32M if the application needs uploads
upload_max_filesize = 4M ; below post_max_size
session.upload_progress.enabled = 0
; Then restart every worker. A graceful reload does not clear the stale
; state CVE-2026-109619 relies on.The unlimited memory_limit removes the controllable allocation failure that CVE-2026-109618 needs, but an underlying allocation failure can still reach the same path, which is why PHP also asks for a small post_max_size and request-size limits at the proxy, and warns against using RLIMIT_AS or RLIMIT_DATA as a substitute, since those cause exactly the failure the bug wants. Use a container memory limit as the availability boundary instead, which in Kubernetes you already have. On 23 October PHP added a note to keep session.upload_progress.enabled=0 in place until the next regular release, which will carry a full advisory for a further session issue, CVE-2026-115560.
What the scanners show
For distro-packaged PHP, every scanner that reads the Debian, Ubuntu and Bitnami feeds had the records within hours of the 21 October release; the distributions were given the patches in advance and published together. Trivy and Grype show them as critical findings on the package. StackRadar shows them within the hour of the OSV record appearing, on every stored SBOM, without a rescan, because the mirror is re-matched against every image when it changes; the dashboard orders them by Radar Score, which EPSS at the 93rd percentile pushes to the top of most clusters' lists, and the CLI gives the one-cluster version from a laptop:
# Every running image in the current kube-context, deduplicated by digest.
# Distro-packaged PHP lights up; source-built PHP does not (see below).
stackradar scan --fail-on critical --only-fixedFor source-built PHP, the previous section applies to all of them until OSV carries a record keyed on the binary or NVD's CPE data lands. A clean report for an image built FROM php: is not evidence this week. php -v is.
Frequently asked questions
Is the official wordpress or nextcloud image affected by PHP2Shell?
The tags were rebuilt on 21 October 2026 with fixed PHP, so a pull from 21 October onwards is patched. A pull of the same tag from before that date is not. Because these images compile PHP from source, no package manager and no OSV-based scanner can tell the two apart; compare the digest the kubelet is running with the digest the registry serves for the tag now, or run php -v inside the container.
Does the fix need a restart?
Yes, in both forms. A new image means new pods, which is a rollout. The mitigations for people who cannot update yet also need every PHP worker restarted, not reloaded: php.net is explicit that the session upload progress setting only protects after a restart, because CVE-2026-109619 is about state that survives in a worker between requests.
Is PHP 7.4, 8.0 or 8.1 fixed?
Not by php.net. Those branches no longer receive releases, so there are no 7.4.x, 8.0.x or 8.1.x tarballs with the fix. Distributions and LTS vendors backport it: Ubuntu 22.04 fixes php8.1 in 8.1.2-1ubuntu2.28 and Ubuntu 20.04 fixes php7.4 in 7.4.3-4ubuntu2.29+esm5 under Ubuntu Pro. The PHP security team also published minimal source patches for 7.4, 8.0 and 8.1 for people who build their own. An image on an unsupported branch with no vendor behind it has no fix at all.
Is PHP2Shell in the CISA KEV catalogue?
Not as of 23 October 2026. Amazon reported honeypot hits from 17 October, four days before disclosure, and several vendors report exploitation attempts since the release. KEV lists a CVE when CISA confirms exploitation, which can lag the vendors by days. EPSS scored both ids at about 0.05, the 93rd percentile, on 23 October. Treat it as exploited; do not wait for the KEV entry.
Why does my scanner show zero critical findings on an image that has PHP in it?
Because the image compiled PHP from source and the scanner matches against OSV. OSV carries PHP2Shell only as Debian, Ubuntu and Bitnami package records; there is no record keyed on a bare PHP binary, so a pkg:generic/php component matches nothing. The zero is the matcher having nothing to say, not a verdict. Run php -v in the container.
How do I find it across several clusters?
Run the kubectl listing above once per kube-context and merge the output, or run stackradar scan with --context for each cluster. For the distro-packaged cases the StackRadar dashboard shows every cluster in one list, ordered by Radar Score, within the hour of the OSV record appearing. For source-built PHP the per-container php -v check is the only honest answer today, whatever tool you use.
More from the blog
All postsHow to find which running Kubernetes images are affected by a new CVE
A CVE ID lands in Slack. Four steps answer “are we running it, and where?” from what the kubelets report: digests, one SBOM each, the ID and its aliases, then the pods.
11 min read
How to generate an SBOM from a running Kubernetes cluster
One CycloneDX SBOM per image digest the kubelets run, not per Dockerfile: list the digests, run Syft once each, keep the files by digest, and re-match them.
10 min read
How to prioritise container vulnerabilities with EPSS and KEV
Four tiers, the thresholds behind each, and a script that joins Grype or Trivy JSON with the EPSS and KEV feeds. On a real nginx image, 497 CVEs became 12 for this week.
11 min read
How to tell base-image vulnerabilities from your own, and what to do with each
Split a scan into base-image findings, packages your Dockerfile added, and images you do not build, with Syft, Grype and a short script. Then fix each the right way.
12 min read