PHP Docker Vulnerability: Rebuild Your Images Post-PHP2Shell
docker pull doesn't correct PHP2Shell. See how to rebuild php:8.x and FrankenPHP images and what to change in CI/CD for the patch to reach production.
by Cleverson Gouvêa

A vulnerable PHP Docker image doesn't fix itself when PHP releases a patch. The container in production continues running the binary it was created with, even after Docker Hub has published the new version. In this guide, I show how to find every image affected by PHP2Shell, how to correctly rebuild php:8.x and FrankenPHP images, what to change in your CI/CD pipeline, and why the container's memory limit doesn't close the vulnerability.
TL;DR
- PHP2Shell refers to two critical vulnerabilities (CVE-2026-109618 and CVE-2026-109619, CVSS 4.0 = 9.2) that allow remote code execution without authentication. The fix was released on 21/10/2026 in versions 8.5.12, 8.4.27, 8.3.36 and 8.2.35.
- Official
php:8.ximages with the patch have been on Docker Hub since 21/10. FrankenPHP patched it in v1.13.2, on the same day.docker pulldoesn't fix anything on its own. You need to rebuild with a new base, push, and recreate each container.- Old fixed tags, pinned digests, and build cache can keep the old version without warning.
- Container memory limits (cgroup) protect availability; they don't close the vulnerability. The other CVE doesn't even depend on memory.
Why a vulnerable PHP Docker image remains vulnerable after the patch
On a traditional server, apt upgrade replaces the package, and restarting PHP-FPM resolves the issue. In a container, the logic is different: the image is immutable. The PHP running inside it was compiled and frozen at build time.
This creates three layers that need to be updated, in this order:
- The base image (
php:8.4-fpm,php:8.3-apache,dunglas/frankenphp) needs to have the corrected version. - Your application image, which inherits from the base via
FROM, needs to be rebuilt and pushed to the registry. - Running containers need to be recreated from the new image.
Skipping any layer leaves a vulnerable PHP Docker image in production, appearing updated. The most common scenario I see is a team running docker pull on the server, seeing "Downloaded newer image", and closing the ticket. The new base is on disk, but the old container remains active, and the application image hasn't even been rebuilt.
I've worked with PHP since 2008 and use containers daily, from development environments with DDEV to the services we deliver in bespoke system development. The difference between an applied patch and a "downloaded" patch is always this: the process that actually handles requests.
PHP2Shell in Two Minutes: What Changed Between 17 and 23 October
PHP2Shell refers to two vulnerabilities in the PHP core. The official php.net advisory states that "all PHP installations serving web requests" are at risk, citing PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP, and LiteSpeed. The greatest risk, according to PHP itself, is mod_php: there, the attack is triggered before any application code.
- CVE-2026-109618 (memory exhaustion corruption): PHP runs out of memory during request assembly, and cleanup treats half-processed data as valid.
- CVE-2026-109619 (stale global state, a use-after-free): an internal state survives after memory has been freed, and the next request in the same worker uses this remnant.
The timeline explains the urgency. According to the Tenable FAQ, Amazon observed an attempted exploit on 17/10. Details leaked on 20/10, and the patch was released on 21/10. Palo Alto Networks identified over 22.6 million exposed instances, as reported by TechTarget. On 23/10, PHP published an additional alert (CVE-2026-115560) regarding an unlikely vector related to session upload progress, which will be addressed in the next regular release.
| PHP Branch | PHP Status | Official php Image on Docker Hub |
|---|---|---|
| 8.5 | Fixed in 8.5.12 | Updated on 21/10/2026 |
| 8.4 | Fixed in 8.4.27 | Updated on 21/10/2026 |
| 8.3 | Fixed in 8.3.36 | Updated on 21/10/2026 |
| 8.2 | Fixed in 8.2.35 (end of life on 31/12/2026) | Updated on 21/10/2026 |
| 7.4, 8.0 and 8.1 | Only minimal source code backports | No new image: the tag 8.1-fpm hasn't received a push since 31/12/2025 |
| 5.4 to 7.3 | No fix | Abandoned (the 7.4-fpm stopped on 15/11/2022) |
I verified the dates in the last column directly with the Docker Hub API and the official image repository. The tag 8.4.27-fpm received its first push on 21/10, at 16:43 (UTC).
How to Discover Which Images Are Vulnerable
Before rebuilding, take an inventory of every vulnerable PHP Docker image you have. In companies with multiple projects, a forgotten container always appears: an old dashboard, a queue worker, or a staging environment that remained exposed.
Ask the Container, Not the Host
The host's PHP says nothing about the container's PHP, and a vulnerable PHP Docker image might be running on a server whose php -v shows 8.4.27. Also, don't rely solely on the CLI's php -v: Datadog warns that the command-line installation can differ from the runtime serving the web. Run the check inside each service:
# PHP version in each running container
for c in $(docker ps -q); do
echo "$(docker inspect --format '{{.Name}}' $c): $(docker exec $c php -r 'echo PHP_VERSION;' 2>/dev/null || echo 'no php')"
done
# image used by the container vs. newest image on disk
docker inspect --format '{{.Image}}' my-container
docker image ls --digests php
Check the Registry, Not Just the Servers
Official images compile PHP from source code and record the version in the PHP_VERSION variable. You can audit without spinning anything up:
docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' registry.example.com/app:prod | grep PHP_VERSION
A detail that catches many people out: since the PHP in the official image doesn't come from a Debian or Alpine package, scanners that only read the system's package database might not detect it. Datadog itself recommends a separate inventory for compiled runtimes. If your vulnerable PHP Docker image doesn't appear in the scanner's report, that doesn't prove it's secure.
What to Include in the List
- Web containers (FPM, Apache, FrankenPHP).
- Queue workers, cron jobs, and consumers using the same image.
- Staging and preview images generated by CI.
- Third-party images (dashboards, internal tools) that embed PHP. This is often the vulnerable PHP Docker image no one remembers to rebuild.
Rebuilding php:8.x Images: The Step-by-Step Guide That Works
With the inventory complete, fixing a vulnerable PHP Docker image follows a short sequence. What changes from project to project is how the FROM instruction is written.
Floating Tag, Fixed Tag, or Digest
How FROM is written |
Example | Receives patch automatically? | What to do |
|---|---|---|---|
| Branch tag | php:8.4-fpm |
Yes, on the next build with --pull |
Rebuild with --pull |
| Exact tag | php:8.4.26-fpm |
No | Edit to 8.4.27-fpm and rebuild |
| Digest | php:8.4-fpm@sha256:... |
No | Update the digest and rebuild |
| Unsupported branch | php:8.1-fpm |
Never | Migrate the application to 8.2+ |
Exact tags and digests are good practices for reproducibility. The cost is precisely this: in an urgent fix, someone needs to edit the file. If your project uses a digest, the vulnerable PHP Docker image remains locked until a human (or a dependency bot) changes the line.
The Commands
# 1. rebuild with a new base (and without cache, if the cache holds the old version)
docker build --pull --no-cache -t registry.example.com/app:2026-10-24 .
# 2. check the version BEFORE publishing
docker run --rm registry.example.com/app:2026-10-24 php -r 'echo PHP_VERSION, PHP_EOL;'
# 3. publish and recreate the containers
docker push registry.example.com/app:2026-10-24
docker compose pull && docker compose up -d --force-recreate
# 4. validate what's running
docker compose exec web php -r 'echo PHP_VERSION;'
The --no-cache option is important. Without it, a cached layer might reuse the old base, and the build finishes "green" with the same vulnerable PHP Docker image as before. Recreating the container restarts all PHP workers. Mounted volumes remain intact.
Variants: Update All
Each variant is a separate image: -fpm, -apache, -cli, -zts, Debian (bookworm, trixie), and -alpine. Start with the -apache variants, which use mod_php, the scenario PHP considers highest risk. And do not expose the FPM port (9000) outside the internal network: FastCGI was not designed to be exposed.
FrankenPHP: Docker Image and Static Binary
FrankenPHP deserves a separate chapter because it comes in two formats, and each updates differently.
Docker Image
The v1.13.2 release, on 21/10/2026, brought images with PHP 8.2.35, 8.3.36, 8.4.27, and 8.5.12, explicitly citing PHP2Shell. Two days later, v1.13.3 was released, which fixes crashes (SIGILL) of musl static binaries on older CPUs. A vulnerable PHP Docker image based on FrankenPHP is corrected in the same way as official images. If your Dockerfile uses dunglas/frankenphp:1-php8.4 or 1.13-php8.4, a rebuild with --pull resolves the issue. If you use a previous fixed tag, such as 1.13.1-php8.4, edit it to 1.13.3 or higher.
Static Binary
Here lies the trap. The FrankenPHP static binary embeds PHP within the executable. Updating the operating system changes nothing. Updating the distro's php package also doesn't. The only fix is to replace the binary with a version that includes PHP 8.5.12 (v1.13.2 or later) and restart the service.
Anyone packaging the entire application into a single executable (FrankenPHP's "embed" feature) also needs to redo that build. This is precisely the case Datadog describes as "applications that bring their own PHP interpreter".
Worker Mode
In worker mode, FrankenPHP keeps the application loaded in memory between requests. CVE-2026-109619 specifically exploits state that survives from one request to the next in the same worker. There's no shortcut: update and restart all workers.
CI/CD Pipelines: How to Avoid Being Caught Out Again
PHP2Shell demonstrated the size of the window between "patch published" and "patch running". In a poorly designed pipeline, a vulnerable PHP Docker image can remain in production for weeks. In a good pipeline, it's a matter of hours.
What to Configure
- Scheduled rebuild. A weekly job (and a manual trigger) that rebuilds the image with
--pull, even without a new commit. An updated base doesn't generate a commit, so a pipeline that only runs on push will never pick up the patch. - Dependency bot for
FROM. Renovate and Dependabot can open PRs updating image tags and digests. This maintains reproducibility and gains speed. - Minimum version gate. A step that fails the build if PHP is below the minimum patched version:
docker run --rm "$IMAGE" php -r 'exit(version_compare(PHP_VERSION, "8.4.27", ">=") ? 0 : 1);' \
|| { echo "PHP below patched version"; exit 1; }
- Tag by date or commit, never just
latest. This way, rollback points to a known image, and the registry shows what's in production. - Final image scanning, with the aforementioned caveat: check the PHP version via the
PHP_VERSIONvariable, not just the package database.
What it Costs
Keeping every vulnerable PHP Docker image under control costs little. The direct cost is small: minutes of CI per week and some extra registry space. The real cost is engineering time, from a few hours to a few days to standardise Dockerfiles, create the job, and test the rollback. Compare this with the cost of an unauthenticated intrusion on an exposed server.
For those running Moodle in a container, the logic is the same, with an extra precaution: check the PHP version matrix for each Moodle release before switching branches. The server requirements for Moodle can help with this assessment.
Why Container Memory Limits (cgroup) Are Not a Complete Mitigation
This is the point I see most often confused. Since one of the vulnerabilities involves memory exhaustion, it seems logical that a --memory=512m on the container would resolve it. It doesn't. A vulnerable PHP Docker image with a memory limit remains vulnerable.
What PHP Actually Recommends
The official mitigation for CVE-2026-109618 is memory_limit=-1, applied in a way that the application cannot override it (php_admin_value in the FPM pool). This removes PHP's controllable ceiling. However, the advisory itself admits that "an underlying allocation failure can still reach the vulnerable path".
Since memory_limit=-1 allows a script to consume all RAM, PHP suggests using a container or cgroup limit "as an availability barrier", preferably with a policy that kills only the affected worker. The keyword is availability. It is not security.
Four Reasons Not to Trust cgroup
- CVE-2026-109619 has nothing to do with memory. It exploits stale state between requests. No RAM limit interferes with it.
- The limit protects the host, not PHP. When the container exceeds the cgroup, the kernel kills a process. This prevents the entire server from crashing, but it doesn't fix the corruption the attacker is seeking.
RLIMIT_ASandRLIMIT_DATAmake it worse. PHP explicitly asks not to use these process limits instead of cgroup, because they can specifically trigger the vulnerable allocation failure. Be careful withulimit -vin entrypoint scripts: it applies exactly anRLIMIT_AS.- Killing the wrong process brings down the service. If the OOM killer targets the FPM master process, the entire container restarts. Repeated in a loop, this becomes a denial-of-service attack provoked by the attacker themselves.
How to Build Layered Defence While Updating
| Layer | Configuration | Role |
|---|---|---|
| PHP | memory_limit=-1 via php_admin_value |
Removes the predictable trigger for 109618 |
| PHP | session.upload_progress.enabled=0 |
Closes confirmed paths for 109619 |
| PHP 8.5 | fatal_error_backtraces=0 |
Closes the fatal backtrace path |
| Container | --memory / limits.memory in Kubernetes |
Availability barrier |
| Proxy/WAF | size limit, fields and multipart parts | Reduces known payloads |
Datadog summarises it correctly: these changes "reduce exposure while you update, but none fix the vulnerabilities". If you want to reinforce the edge, the guide on how to protect web servers against attacks covers WAF, fail2ban, and rate limits.
When NOT to Do It: Costly Pitfalls
In emergency fixes, the most common mistake is rushing in the wrong areas. Avoid these:
- Do not use release candidate tags. Tags like
8.5.12RC1are not the final version. Use only stable tags. - Do not run
apt upgradeinside a running container. Besides not replacing the compiled PHP in the official image, the change disappears on the next restart. - Do not rely on
ini_set()or.user.inifor mitigation. PHP warns that they are applied too late, after the request has been read. - Do not rebuild an 8.1 image expecting a fix. It won't come. For 7.4, 8.0, and 8.1, PHP only published minimal source code backports; the realistic solution is to migrate to 8.2+, preferably 8.3 or 8.4, as 8.2 goes out of support on 31/12/2026.
- Do not do everything directly in production. Swapping a vulnerable PHP Docker image for one from another branch without testing simply exchanges one problem for another. A branch change (e.g., 8.1 to 8.4) can break extensions and legacy code. Patching within the same branch (8.4.26 to 8.4.27) is low risk. A branch change requires staging/UAT.
- Do not forget host hardening. The container runs on a Linux host that also needs to be up-to-date; the Linux server hardening checklist covers this layer.
And after updating, look for signs that an attack occurred before the patch. Datadog lists indicators such as out-of-memory errors with "Unknown on line 0" in the log, segfaults (signal 11), and, most directly, a web worker opening a shell. If any of these appear, treat it as an incident, not maintenance.
How Agathas Web Can Help
If you have one or more PHP applications in containers and aren't sure that no vulnerable PHP Docker images remain in production, this is precisely the kind of work we deliver through our system development and support service.
What's Included
- Complete inventory: running containers, registry, workers, cron jobs, and staging environments, with the actual PHP version of each.
- Rebuild and publication:
FROMupdate, cache-free build, push, container recreation, and validation of the running version. - FrankenPHP and static binaries: binary or image replacement, including in applications packaged with embedded PHP.
- Well-applied temporary mitigations while branch migration is ongoing, at the correct level (FPM pool, not
.user.ini). - Future-proof pipeline: scheduled rebuild, dependency bot, minimum version gate, and tested rollback.
- Legacy PHP migration (7.4, 8.0, 8.1) to a supported branch, with staging/UAT before production.
How it Works in Practice
We start with a brief diagnostic: you provide us with read access to your registry and servers, and we return a list of what's vulnerable and the recommended order of correction. We then execute the rebuild within an agreed window, with rollback readiness. The same team handles Moodle, Linux servers, managed hosting, and integrations with the official WhatsApp API, so your entire application benefits from a unified perspective, not just the container.
How to Engage Our Services
Contact us via the Agathas Web system development page, describe your stack (how many projects, which images, where they run), and we'll respond with a scope and timeline. For urgent PHP2Shell-related matters, please state this in your initial message.
Conclusion: A Patch Only Counts When It Reaches the Process
The lesson from PHP2Shell for Docker users is simple. The patch was released on 21/10, and official images and FrankenPHP were updated on the same day, but none of this protects a container that hasn't been rebuilt and recreated. Inventory, rebuild with --pull --no-cache, push, recreation, and version validation: that's the sequence. Container memory limits help keep the service running, but they don't close the vulnerability.
If you want to end this week with the certainty that no vulnerable PHP Docker images remain, and with a pipeline that catches the next vulnerability within hours, contact Agathas Web.
Related posts

mod_php vs. PHP-FPM: Securing UK Servers Post-PHP2Shell
PHP identified mod_php as the worst-case scenario in PHP2Shell. Discover architectural changes and how to migrate to FPM without downtime.

PHP2Shell Vulnerability: Essential Mitigation for UK Servers and Why It's Not Enough
The official three adjustments buy time, not security. Discover where to apply them, how to restart each PHP worker, and what to do until the full patch arrives.

Server Patch Management: Your 24/7 Post-PHP2Shell Strategy
From the 17/10 attack to the 21/10 patch: understanding the PHP2Shell exposure window and the part that's entirely within your control.