Skip to content
php-cve-2026

php-cve-2026

Sploitus • October 22, 2026

A single Bash script that **finds every PHP on a Linux server, tells you whether it is

vulnerable to CVE-2026-109618 / CVE-2026-109619, and — on Debian and Ubuntu — fixes it

safely and proves the sites still work afterwards.**

It works for any PHP server: plain Apache or nginx sites, WordPress, Nextcloud, Laravel,

control panels (cPanel, Plesk, CloudLinux, DirectAdmin, aaPanel), LiteSpeed, FrankenPHP

and PHP running inside Docker/Podman containers.

> **Default is read-only.** Without `--fix` it only writes its own log and refreshes apt's

> package lists. With `--fix` it shows its plan and asks before changing anything.

| CVE-2026-109618 | Out-of-memory handling can interrupt PHP while request data is only partly initialised ([GHSA-w8hw-w9fx-j79w]( |

| CVE-2026-109619 | Request-owned state can survive after its memory was freed (use-after-free) ([GHSA-6wmf-4rv5-p3rp]( |

| Impact | Unauthenticated remote code execution as the web server user. CVSS v4 9.2 (critical). |

| Confirmed | Complete RCE over HTTP against **Apache mod_php**; validated against **PHP-FPM** for the session path; LSAPI and FrankenPHP named as likely candidates. |

| Alert | [NCSC (UK)]( · product inventory: [NCSC-NL php2shell]( |

| 8.1 and older | **no fix** (end of life) – upgrade the branch |

Distributions often *backport* the fix and keep the old version number (for example

Ubuntu's `8.3.6-0ubuntu0.24.04.13`). The script detects this from the package changelog,

so a backported build is reported as patched, and an old build is not hidden behind a

The advisories say updating is the **only complete fix**. As extra hardening they

recommend `session.upload_progress.enabled = 0`, which this script sets on Debian/Ubuntu

(skip it with `--no-harden` if an application uses upload progress).

# 1. download a release and check it (compare with SHA256SUMS on the release page)

curl -fsSLO

# 2. read it – it is one file, and it is going to run as root

# 4. fix (Debian/Ubuntu): shows a plan and asks before changing anything

Please do not pipe it straight into a root shell (`curl … | sudo bash`). Download it,

look at it, then run it. Try `--fix` on a staging copy first if you have one.

**1. Finds every PHP engine** — packaged ones (`dpkg`, `rpm`, `apk`) and everything

else in the usual places: `/usr/local`, `/opt/remi`, `/opt/cpanel/ea-php*`,

`/opt/plesk/php`, `/opt/alt/php*`, `/usr/local/lsws/lsphp*`, `/www/server/php`,

FrankenPHP and Apache `libphp*.so` modules. It also inspects running processes, so it

also catches PHP inside containers, and Apache workers that still have an old `mod_php`

**2. Decides for each engine**: patched, patched by a distribution backport, vulnerable,

end-of-life, or cannot verify. CLI-only binaries that are not running are reported as

warnings. Web-facing engines (FPM, mod_php, CGI, LSAPI, FrankenPHP) are reported as

- upgrades the PHP packages of every branch in use, including extensions and the

- repairs apt pins that silently block PHP updates from known vendors (`packages.sury.org`,

Ondřej's PPAs) — for example a `Package: *` pin at priority 10, or a `php8.3-*` pin

- restarts only the services whose PHP changed, or that still run a replaced binary,

- optionally (`--purge-unused`) removes PHP versions nothing uses.

Ubuntu PPAs configured on Debian are offered for disabling. With

`--disable-failing-repos`, broken repos that no installed package depends on are

- services are running and configuration tests pass;

- every site name in the web server config still answers on this machine, compared with

- Nextcloud: `occ` status, database, `status.php`, background jobs, setup checks and

**5. Looks for compromise indicators** (read-only):

- typical web-shell code in recently changed PHP files;

- PHP files in the Nextcloud data directory (users' own files are excluded);

- shells or network tools spawned by PHP workers;

These are hints, not proof. A clean result does not prove the server was not

On other systems (RHEL/Alma/Rocky, CloudLinux, cPanel, Plesk, Alpine, containers,

source builds) the script audits and prints the **exact update command** for each

vulnerable engine instead of changing anything.

| Debian 10–13, Ubuntu 20.04–24.04 (distribution PHP, Sury, Ondřej PPA) | ✅ | ✅ |

| RHEL / Alma / Rocky / CentOS / Oracle / CloudLinux / Amazon Linux (incl. Remi) | ✅ | prints commands |

| cPanel EA-PHP, Plesk PHP, CloudLinux alt-php, LiteSpeed lsphp, aaPanel | ✅ | prints commands |

| PHP built from source, FrankenPHP | ✅ | prints instructions |

| PHP inside Docker/Podman containers (seen from the host) | ✅ | prints instructions (rebuild image) |

This tool runs as root on production servers, so it is built to be boring:

- **Read-only by default.** `--fix` shows a plan and asks; non-interactive runs need

`--yes` as well. An audit also works without root (with less visibility).

- **Every package change is simulated first.** If apt would remove any non-PHP package,

the change is refused. Package configuration files you edited are kept

- **Backups before changes:** `/etc/apt`, `/etc/php`, `/etc/apache2`, `/etc/httpd` and

`/etc/nginx`, plus the list of PHP package versions, go to

`/var/backups/php-cve-2026-fix//` (root-only — they can contain secrets such

- **Configuration tests before every restart.** If PHP-FPM rejects the hardening file,

- **Never runs website code or untrusted binaries as root.**

- PHP versions are read from the binary file, not by executing it.

- Nextcloud's `occ` runs as the owner of `config.php` with a clean environment, and is

- System tools such as `php-fpm -t` are only executed when the binary and every parent

directory are root-owned and not writable by others.

- Nothing from inside a container is executed; its files are only read, with size

- **No downloads, no telemetry.** Network use is apt itself, HTTP checks against

`127.0.0.1` (proxies are bypassed so host names never leave the machine), and any

- refuses a `PATH` with relative or world-writable entries;

- uses a private temp directory, and writes logs and reports without following

- time limits on directory walks, size limits on files it reads.

What it **changes** with `--fix` (and nothing else):

| PHP packages via `apt-get install pkg=version` | the actual fix |

| `/etc/apt/preferences.d/00-php-cve-2026-fix-phpX.Y.pref` | only when a pin blocked a known PHP vendor |

| `/etc/php/X.Y/{fpm,apache2,cgi}/conf.d/99-zz-php-cve-2026-hardening.ini` | `session.upload_progress.enabled = 0` (skip with `--no-harden`) |

| apt source files | only for Ubuntu PPAs on Debian, or with `--disable-failing-repos`; a copy is kept |

| `phpX.Y-fpm` / `apache2` restarts | only for changed or stale PHP |

| purge of unused PHP | only with `--purge-unused` |

| `/var/log/php-cve-2026-fix/`, `/var/backups/php-cve-2026-fix/` | log and backup |

tar xzf "$B/etc-snapshot.tgz" -C / # configs as they were

cat "$B/php-packages-before.txt" # versions before; apt-get install pkg=version to go back

rm /etc/php/*/*/conf.d/99-zz-php-cve-2026-hardening.ini # just the hardening

--fix Apply fixes (Debian/Ubuntu). Shows a plan and asks first.

-y, --yes With --fix: do not ask (for automation).

--purge-unused Remove PHP versions nothing uses (no web config, no process,

not the CLI default, not referenced in cron/systemd).

--disable-failing-repos Also disable apt repos that fail to update, unless an

--no-harden Do not set session.upload_progress.enabled=0.

--nextcloud PATH Nextcloud directory (repeatable; auto-detected otherwise).

--url URL Extra URL that must not return 5xx after changes (repeatable).

--days N Look-back window for compromise indicators (default 7).

--no-ioc Skip the compromise-indicator scan.

--no-update Do not run 'apt-get update'.

--json FILE Write a machine-readable report.

--log-dir DIR Log directory (default /var/log/php-cve-2026-fix as root).

**Exit codes:** `0` all good · `1` warnings · `2` failures (for example still vulnerable)

sudo ./php-cve-2026-fix.sh # audit

sudo ./php-cve-2026-fix.sh --fix # plan, confirm, fix

sudo ./php-cve-2026-fix.sh --fix --yes --purge-unused # unattended

sudo ./php-cve-2026-fix.sh --fix --url

sudo ./php-cve-2026-fix.sh --json /root/php-cve.json # for dashboards

ssh "$h" "sudo /tmp/php-cve-2026-fix.sh --no-color --json /tmp/php-cve.json; echo exit=\$?"

- **Nextcloud:** found automatically under the web roots, `/var/www`, `/srv`, `/opt` and

`/usr/ `; pass `--nextcloud /path` otherwise. Users' own files in the data

directory are never scanned, so a website project someone stores in Nextcloud is not

reported as a web shell. The Talk "high-performance backend" setup notice is shown as

- **TurnKey Linux / appliances with Sury pins:** a `php-sury.pref` that pins

`packages.sury.org` to priority 10 keeps PHP on the old build forever. The script

detects this and adds a narrow pin for the PHP branch in use.

- **Apache mod_php:** this is the SAPI the advisories exploited. The script warns

- **Docker/Podman:** the host sees PHP processes inside containers and reports their

version, read from the binary. The fix is a new image: for example pull an updated

`php:8.3-fpm` tag, rebuild, and recreate the container.

- **End-of-life OS:** if the distribution is out of support (for example Debian 11 after

August 2026), the audit warns. Third-party PHP repos can stop building for it at any

shellcheck -S warning php-cve-2026-fix.sh tests/*.sh

# scenario tests (stubbed apt/dpkg) – they write to /etc and /usr, so only in a container:

docker run --rm -v "$PWD":/src -w /src debian:12 bash -c \

'apt-get update -qq && apt-get install -y -qq python3 curl procps >/dev/null && tests/run-tests.sh'

# integration test on a real system (installs apache2 + php-fpm) – CI or a throw-away VM:

CI runs shellcheck, the scenario tests on Debian 11/12 and Ubuntu 22.04/24.04, and the

integration test on a real Ubuntu runner (see `.github/workflows/ci.yml`).

If PHP publishes further fixed releases, update the `FIXED` table and `CVE_IDS` near the

Found a problem in the script itself? Please report it privately (see

[SECURITY.md](SECURITY.md)), not in a public issue.

Provided "as is", without warranty of any kind (see [LICENSE](LICENSE)). You are

responsible for what you run on your servers. The compromise checks look for common

traces only and cannot prove that a server is clean. If you suspect a compromise,

follow your incident-response process and your national CERT's guidance.

Extracted Entities

Attack Types (1)

Companies (2)

CWE Weaknesses (1)

Domains (1)

Platforms (4)

Tools (3)