Check, then apply, the **php2shell** fix on your PHP runtimes: system packages, standalone binaries, Docker containers (FrankenPHP included) and remote servers over SSH.
 and exit codes are language-neutral.
**php2shell** is the name given to two remote code execution flaws in PHP core: **CVE-2026-109618** and **CVE-2026-109619** (CVSS v4.0 9.2). The fix ships in the PHP security release of **October 21, 2026, 14:00 UTC** (16:00 in Paris, CEST).
No configuration workaround is confirmed: **only upgrading PHP fixes it**. These scripts answer three questions: what is still vulnerable, how to fix it, and what to watch in the meantime.
Nothing is hardcoded for a given environment:
- **Fixed version**: read live from php.net, per branch (8.2 to 8.5). Until the release is out, the scripts answer `[??]`, never `[OK]`.
- **End-of-life branches** (7.x, 8.0, 8.1): detected through php.net's `supported_versions` list and reported `[VULN]`, since they will get no fix.
- **Distro packages**: Debian/Ubuntu and RHEL backport fixes without bumping the version number. A package is therefore judged on its changelog (CVEs cited) **or** on its upstream version (Alpine, Sury, Remi).
- **Docker**: `php` and FrankenPHP containers, plus frozen tags spotted from their last push date on Docker Hub.
- Linux, with GNU `date`, bash 4+ and `curl`.
- `docker`: optional, only needed for containers and images.
- Tested on Ubuntu, Debian 13, Alpine and Rocky Linux 9. **Not tested on macOS.**
git clone
Nothing to install: these are standalone Bash scripts (`lib.sh` holds the shared functions).
| `[OK]` | Fixed version (or a changelog citing the CVEs). |
| `[VULN]` | Vulnerable version, pre-release, or end-of-life branch. |
| `[??]` | Unknown: release not published yet, Docker socket not reachable, PHP not found... |
sudo ./check.sh # if the Docker socket requires privileges
Checks deb, rpm and apk packages, binaries outside any package (source builds, `/opt`, asdf, Homebrew...) and running containers. Exit code `0` if everything is patched, `1` if at least one runtime is vulnerable or unknown.
Upgrades PHP packages (apt, dnf/yum, apk), restarts PHP and web server services, lists the containers to rebuild, then runs `check.sh` (and returns its exit code). Uses `sudo` when not run as root. Docker images are not rebuilt here.
cp servers.txt.example servers.txt # one SSH target per line
./servers-check.sh # every server in servers.txt
./servers-check.sh web-01 ops@203.0.113.10 # or explicit hosts
Pipes `probe.sh` (read-only, POSIX sh) to each target over SSH and judges what it finds: binaries, packages, containers. Nothing is installed remotely. For a distro package, the upstream version may hide a backport: the script then prints `[??]` and asks you to run `check.sh` on that host.
Exit codes: `0` everything is patched, `1` at least one server is unreachable, vulnerable or unknown, `2` if `servers.txt` is missing (and no host is passed as an argument).
The SSH account must be able to run `sudo -n docker` to see containers.
### docker-scan.sh: Dockerfiles and compose files
NO_PULL=1 ./docker-scan.sh ~/code # without pulling images
Lists the PHP base images referenced by the Dockerfiles and compose files of a folder (current directory by default), resolves `ARG` defaults, classifies each reference (`pin`, `exact`, `rolling`, `frozen(Nj)`, where `j` stands for days) and reads the PHP version inside the image. Exit code `0` or `1`, like `check.sh`.
DRY_RUN=1 ./docker-bump.sh path/to/Dockerfile # print only
Replaces each `image@sha256:` with the current digest of its tag, **only if** that image ships the fixed version. Otherwise the old pin is kept and the exit code is `1`.
### frankenphp-selfbuild.sh: FrankenPHP without waiting
./frankenphp-selfbuild.sh 8.4.27 trixie v1.13.1
SHIP_TO=user@node ./frankenphp-selfbuild.sh 8.4.27 # docker load on a remote node
Stopgap: builds a FrankenPHP image on the fixed `php:X.Y.Z-zts` image, without waiting for the upstream daily rebuild (04:00 UTC, 06:00 in Paris). Arguments: `PHP_VERSION [OS] [FRANKENPHP_TAG]`. Runs a real multi-stage build (clones `php/frankenphp` into `build/`, then `docker buildx bake`). Exit code `1` if the requested version is not the fixed one, or if the official image is not on Docker Hub yet.
Loops until interrupted (Ctrl+C) and prints an `ALERT` line whenever a shell or a network tool (`sh`, `bash`, `python`, `perl`, `curl`, `wget`, `nc`, `socat`...) is spawned by a PHP or web server process.
| `RELEASE_DATE` | all (through `lib.sh`) | Date of the security release carrying the fix. Default: `2026-10-21`. |
| `CVES` | all (through `lib.sh`) | CVEs a distro changelog must cite. Default: `CVE-2026-109618 CVE-2026-109619`. Change it if the official advisory differs. |
| `NO_COLOR` | all (through `lib.sh`) | When set, disables colors. |
| `NO_PULL` | `docker-scan.sh` | When set, does not pull images nor read their PHP version. |
| `STALE_DAYS` | `docker-scan.sh` | Age in days after which a Docker Hub tag counts as frozen. Default: `30`. |
| `DRY_RUN` | `docker-bump.sh` | When set, prints the changes without editing files. |
| `SHIP_TO` | `frankenphp-selfbuild.sh` | SSH target (`user@node`) where the built image is loaded with `docker load`. |
| `INTERVAL` | `watch.sh` | Seconds between two scans. Default: `2`. |
- **A `docker restart` patches nothing**: rebuild with `--pull`, then redeploy.
- **FrankenPHP embeds PHP** in its image and in its static binary. Upgrading the host's PHP changes nothing for it.
- **FrankenPHP minor tags freeze**: `1.9-php8.4` stayed on PHP 8.4.15. Prefer `1-phpX.Y`.
- **A pinned digest nobody owns drifts**: hand it to Renovate or Dependabot.
- Linux only (GNU `date`, bash 4+, `curl`). Tested on Ubuntu, Debian 13, Alpine and Rocky Linux 9. Not tested on macOS.
- `frankenphp-selfbuild.sh` runs a real multi-stage build: read it before use.
- The CVE identifiers come from researchers and CERT-EU, not from the PHP announcement. Check the official advisory on php.net once it is published.
- Provided as is, without warranty. Test in staging first.
- The scripts are **read-only**, except `apply.sh` (upgrades packages and restarts services), `docker-bump.sh` (rewrites your files) and `frankenphp-selfbuild.sh` (builds an image and can load it on a remote node).
- `probe.sh` installs nothing on servers: it only lists the PHP runtimes it can see.
- Read a script before running it as root.
- `servers.txt` (your real inventory) is ignored by Git: never commit it.
Issues and pull requests are welcome. Before opening a PR, check the syntax of every script:
-
-
The full story
This article is one source in a clustered incident — the cluster page carries the summary, timeline and every other outlet covering it.
