Your Code Is Clean. Your Server Isn’t.
On October 21, PHP shipped emergency fixes for two flaws, CVE-2026-109618 and CVE-2026-109619, that researchers named PHP2Shell. Both are rated 9.2 on CVSS v4.0. Both let an attacker with no account run code on a web server.
The uncomfortable part is where they live. The bugs are in the PHP engine, not in WordPress, Laravel or anything your team wrote. A one-line “Hello World” page on a vulnerable runtime can be attacked. On Apache mod_php, a single unauthenticated request triggers the exploit before any of your application code runs.
That breaks a habit most application security programs rely on: scan the code, scan the dependencies, ship. This post is about the part of the stack that habit skips, and how Cycode covers it.
Why Your Lockfile Won’t Warn You
Open any composer.lock and look for PHP itself. You won’t find it as a package. At most, composer.json has a line like "php": ">=8.1". That is the minimum version the app will run on, but it doesn’t tell you the actual version your server runs.
So a dependency scan of a Laravel repo can come back green while the container it ships in runs PHP 8.3.30. The scanner did its job. It just looked at the wrong layer.
The fixed releases are 8.5.12, 8.4.27, 8.3.36 and 8.2.35. Anything below those on a supported branch is exposed. PHP 8.1 and older get no new official releases, but they aren’t a dead end. The PHP security team published source backports for 7.4, 8.0 and 8.1, and distro and LTS vendors may ship patched packages under old version numbers. To know where you stand, you have to find every place the runtime version is actually decided.
Where PHP Actually Gets Picked
In most teams, nobody chooses the PHP version on purpose. It gets inherited from one of a few places, and each one needs a different check.
| Where it’s set | Example | What to look for |
|---|---|---|
Dockerfile FROM line | FROM php:8.3-apache | Floating tags rebuild fine but may still pull a stale cached image. Pinned tags like php:8.3.30-fpm never move. |
| OS packages in a Dockerfile | apt-get install php8.2-fpm | Distro packages can carry the fix under an old version string. Check the changelog for the CVE. |
| Built and pushed images | Images in ECR, ACR, Docker Hub | An image built last month still runs last month’s PHP, whatever the Dockerfile says today. |
| IaC and platform config | Helm values, Terraform, Elastic Beanstalk or App Service runtime settings | A runtime version string set once and forgotten. |
| CI pipeline images | image: php:8.2 in a workflow file | Build and test jobs that hold secrets and also run PHP. |
Look closely at the first row. On the official PHP image, tags ending in -apache, like php:8.3-apache, run Apache with mod_php. That is the highest-risk setup in the advisory, so those images should be fixed first.
How Cycode Closes the Gap
Cycode’s Context Intelligence Graph makes the connection between your code, images, Dockerfiles, repos, and teams. During an incident, that context turns “we have 140 vulnerable images” into “these 12 teams have a fix to merge.”
It scans the images, not just the code. Cycode’s container scanning pulls images from registries such as Docker Hub, Azure Container Registry and Amazon ECR and reports vulnerable packages inside them. That is where a PHP runtime should show up, since it never appears in composer.lock. One caveat: the official php images compile PHP from source into /usr/local, so there is no OS package record to read. Whether these CVEs get flagged depends on how the scanner fingerprints that binary and on the CVEs reaching its advisory feed.
Check coverage before you count on it. Push two test images to a connected registry: php:8.3.35-apache, which is vulnerable, and php:8.3.36-apache, which is fixed. The first should flag CVE-2026-109618 and CVE-2026-109619, and the second should not. Repeat with an -fpm tag and with a Debian image that installs php8.2 from apt. Any gap means the inventory approach below carries the load until the feed catches up.
It traces each image back to a Dockerfile and a commit. Cycode matches registry images against Dockerfiles in your connected repos. It also reads CI build logs for docker build and docker push, which ties a pushed image to the repo and commit that produced it. For PHP2Shell, that answers the first question responders ask: who owns this?
It tells you what kind of fix you need. Cycode’s remediation agent works out whether a finding comes from the base image, from a package installed in the Dockerfile, or from app dependencies. Here that split matters. A FROM php:8.3-apache finding needs a base image bump. An apt-get install php8.2 finding needs a distro update and a changelog check.
It finds PHP before there’s a finding. A CVE search only works once a scanner has flagged something. The graph doesn’t have to wait for that. It already links images to the Dockerfiles that built them, the repos and commits behind them, and the pipelines that push them. So you can start from where PHP runs instead of from the CVE:
- Images built on any
php:*base, with-apache(mod_php) variants first - Dockerfiles that use
FROM php:or installphp,php8.*,php-fpmorlibapache2-mod-phpthrough apt, apk or dnf - Deployment config that sets a runtime: Helm values, Kubernetes manifests, Terraform, and platform settings such as an App Service or Elastic Beanstalk PHP version
- CI workflows that run jobs in a
php:image
Each result already has an owner attached. When CVE findings arrive, they confirm the list instead of building it. If your graph doesn’t index one of these patterns, a code search across connected repos for the same strings covers it.
It keeps your other tools in the picture. If some teams use a different container or cloud scanner, Cycode integrations pull those findings into the same graph, so you’re not reconciling spreadsheets mid-incident.
A First-24-Hours Plan
- Hour 0 to 2: build the list. Start from the PHP inventory above, then add CVE findings as they appear. Export image, digest, repo, owner, base tag and where each one runs.
- Hour 2 to 4: sort by real risk. Internet-facing mod_php first, then PHP-FPM, then internal and CI images. For 8.1 and older, choose a path per service: move to a supported branch, build with the PHP team’s source backport, or install a patched package from your distro or LTS vendor.
- Hour 4 to 12: rebuild and redeploy. Bump
FROMto the fixed release, or update the distro package and confirm the CVE in its changelog. Rebuild with--pull --no-cacheso a cached layer can’t bring the old runtime back. Push, point deployments at the new image digest, and redeploy. - Restart everything that runs PHP. That means Apache, PHP-FPM pools, queue workers and long-running servers like FrankenPHP or RoadRunner. A patched image does nothing for a process that started yesterday.
- Verify what’s actually running. Run
php -vandphp-fpm -vinside live containers and on hosts, and compare against the fixed versions. For distro packages, check the installed package against the vendor advisory, not the version string. Rescan registries to catch old images that are still deployed. - Hour 12 to 24: stop it coming back. Add a policy that fails builds on PHP below 8.5.12, 8.4.27, 8.3.36 or 8.2.35, and swap floating tags for pinned digests.
What Still Has to Happen on the Server
Cycode shows you where the problem is and who should fix it. A few things only happen on the host.
- Restart after you patch. Apache, PHP-FPM and any long-running PHP workers keep running the old code until they restart.
- Bare-metal and VM servers. PHP installed directly on a host, with no image or IaC behind it, needs an endpoint or asset inventory to find. Pair Cycode with that.
- Stopgap configs are not fixes. Turning off
session.upload_progress.enabledblocks some paths for CVE-2026-109619 and does nothing for CVE-2026-109618. PHP’s advisory is clear that only updating fully protects a server. - Check memory settings after the update. The fix adds a hard memory limit. Review
memory_limitandpost_max_sizeagainst what your app needs. - Look back, not only forward. If you patched late, check logs for odd multipart requests followed by memory errors or bursts of HTTP 500s, oversized
PHPSESSIDcookies, and unexplained worker restarts.
The Lesson Outlasts This Bug
PHP2Shell came out of code that had been in PHP for over a decade, and researchers say AI helped them build working exploits in about a week. That speed will show up again in other runtimes, base images and language engines.
The teams that handle the next one well will be the ones with the playbook to answer “what runs this, and who owns it” in minutes. That answer is what Cycode is built to give you.
Sources
- The Hacker News: PHP Issues Emergency Patches for Two Critical PHP2Shell RCE Flaws
- GHSA-w8hw-w9fx-j79w (CVE-2026-109618)
- GHSA-6wmf-4rv5-p3rp (CVE-2026-109619)
- Cycode: AI Remediation for Containers
- Cycode: Context Intelligence Graph with AI
- Cycode: ConnectorX and complete ASPM