Skip to content
php2shell

php2shell

Sploitus • October 22, 2026

A remediation script for the **critical unauthenticated RCE vulnerabilities in PHP core**

disclosed in the October 2026 PHP security release ("PHP2Shell"), exploited through

PHP's `session.upload_progress` handling.

- **`patch-php-cve-2026-109618-109619.sh`** — patches the local Debian/Ubuntu host it runs on.

- **`check.php`** — verifies a patch from outside by reading the target's PHP version banner.

> **Scope & safety:** neither script exploits anything. The patch script changes only the

> **local host** it runs on. `check.php` makes a single ordinary HTTP request to a target and

> reads its version banner — no attack payload is ever sent. Run both only on hosts you own

> or are authorized to administer and test.

| **CVE** | CVE-2026-109618, CVE-2026-109619 |

| **Type** | Unauthenticated remote code execution (PHP core) |

| **Severity** | ~9.2 (CVSS 4.0) / 9.8 (CVSS 3.1) |

| **Root cause** | State corruption via PHP's bailout (`longjmp`-based) error unwinding — use-after-free / uninitialized memory |

| **Delivery path** | `session.upload_progress` (enabled by default) |

| **Affected** | PHP 5.4.0 up to the fixed releases below, **in default configuration** |

| **Disclosed** | 2026-10-21 (PHP out-of-band security release) |

| ≤ 8.1 | **No fix — end of life.** You must migrate to a supported branch. |

Runs these steps in order. It is idempotent and safe to re-run.

1. **Detect** the installed PHP version and branch.

2. **Assess** the version against the fixed release for its branch (warns if the branch is EOL).

3. **Mitigate** immediately — writes `session.upload_progress.enabled = Off` as a

`99-cve-2026-109618-mitigation.ini` drop-in into **every** installed SAPI's `conf.d`

4. **Upgrade** the `php*` packages via `apt` — the complete fix. Details:

- On an **end-of-life branch** (PHP ≤ 8.1, which has no patched build) it does **not**

attempt an in-branch upgrade — that cannot make the host safe. It exits with migration

guidance after applying the interim mitigation.

- All apt calls use `-o DPkg::Lock::Timeout=60`, so the script **waits up to 60s** for a

busy apt/dpkg lock (e.g. `unattended-upgrades`) instead of failing immediately.

- Before upgrading it checks the apt *candidate* is already at or above the fixed version

and **refuses to proceed** if your repositories are not yet serving the patched build

(so it never pulls from an untrusted source just to appear successful).

5. **Restart** `php-fpm` / `apache2` / `nginx` if they are running.

6. **Verify** — confirms the running version now meets the fix, performs a

**mitigation self-check** (that `session.upload_progress.enabled` reads `Off`), then prints

> **Note on the mitigation self-check:** it reads the value from the **CLI** SAPI. The web

> SAPI (FPM/Apache) loads the same drop-in, so this is a reliable proxy — but it is not proof

> the running web worker re-read its config. Confirm externally against the restarted site

- Debian or Ubuntu (uses `apt-get` / `dpkg`).

- `root` privileges for applying changes (`sudo`). `--dry-run` works without root.

- The patched PHP build must be available from your configured repositories. On Ubuntu that

is typically the **Ondřej Surý PPA** (`ppa:ondrej/php`); on Debian, the **Sury** repo

(`packages.sury.org`). The script stops with guidance if the fixed version is not offered.

chmod +x patch-php-cve-2026-109618-109619.sh

# 1. Preview every action without changing anything (no root needed):

sudo ./patch-php-cve-2026-109618-109619.sh --dry-run

# 2. Apply mitigation + upgrade + verify:

sudo ./patch-php-cve-2026-109618-109619.sh

# Apply only the interim mitigation (e.g. before a scheduled maintenance window):

sudo ./patch-php-cve-2026-109618-109619.sh --mitigate-only

./patch-php-cve-2026-109618-109619.sh --help

| `--dry-run` | Print the exact actions without executing them. Works without root. |

| `--mitigate-only` | Apply only step 3 (disable `session.upload_progress`); skip the package upgrade. |

| `-h`, `--help` | Show the header documentation. |

| `0` | Already patched, dry run completed, or patch applied and verified. |

| `1` | Not fully patched — repo lacks the fixed build, branch is EOL, or verification failed. |

## Interim mitigation (what step 3 does, manually)

If you cannot run the script yet, you can close the confirmed attack path in seconds:

; in php.ini or a conf.d drop-in for the SAPI serving your site

Then reload the web PHP process (`systemctl restart php8.5-fpm` / `apache2`).

> This blocks the **confirmed** exploitation path but is **not** the complete fix.

> Upgrading the PHP packages is the only full remediation.

A companion script confirms the patch from the **outside**, the way a client sees the server.

php check.php --json

It sends a **single ordinary HTTP request** and reads the PHP version from the

`X-Powered-By` / `Server` response header, then compares it to the fixed releases.

| `PATCHED` | Advertised version ≥ the fixed release for its branch | `0` |

| `VULNERABLE` | Below the fixed release, or an EOL branch | `1` |

| `UNKNOWN` | No version exposed (banner hidden or stripped by a proxy/CDN) | `2` |

| *(error)* | Usage error or connection failure | `3` |

**It sends no exploit payload** — no `session.upload_progress` / attack traffic, nothing that

could crash or alter the target. It reads version banners only, so it verifies the *deployed

version* (which is what proves a patch landed) without attacking anything.

- `UNKNOWN` is common and usually **good** — it means the server hides its version

(`expose_php = Off`, or a CDN/proxy strips the header). That is correct hardening, not a

failure. Confirm on the host with `php -v`.

- A banner can be stale or spoofed. Treat `PATCHED` as "consistent with a completed patch"

and `VULNERABLE` / `UNKNOWN` as "verify on the server." The server-side `php -v` from the

patch script remains the authoritative source of truth.

> Run `check.php` only against hosts you own or are authorized to test.

## After patching: check for prior compromise

Mass exploitation has been reported in the wild. If your server ran a vulnerable,

default-configuration version while internet-exposed, **patching does not undo a breach that

may have already happened.** Review at minimum:

- Unexpected or recently-created files in web roots and in `session.save_path`

- Unknown cron jobs, `systemd` units, or startup items.

- Unexpected outbound network connections or listening ports.

- PHP files modified around or after your exposure window.

If you find evidence of compromise, treat the host as breached: isolate it, preserve

evidence, and rebuild from a known-good state rather than relying on the patch alone.

> Verify the fixed version numbers and advisory details against the official php.net

> security advisory before acting in production.

Provided for authorized system administration and defensive remediation only. Test with

`--dry-run` first, and ideally on a staging host, before running in production. You are

responsible for backups and for validating the result in your environment.

Extracted Entities

Attack Types (1)

Companies (2)

CWE Weaknesses (1)

Platforms (2)

Tools (1)