Two remote code execution vulnerabilities were just emergency-patched in the PHP core: CVE-2026-109618 and CVE-2026-109619, CVSS v4.0 9.2/10 each, nicknamed php2shell. They potentially affect every PHP release since 5.4 — the one from 2012. Details leaked ahead of the embargo, PHP moved its security release up, and we're showing everything in a lab: the attack surface, the demonstration, and above all how to fix.
⚠️ The full lab (Docker, race harness, AddressSanitizer A/B proof — now with a stock-WordPress target on the frozen vulnerable PHP) lives under. Local target only, never aimed at third-party systems — and the PoC is built from the official patch diff at release time, not from the leaked tooling.
slabbdev
/
php2shell-cve-2026-109618-109619
Local-only PoC lab for CVE-2026-109618 / CVE-2026-109619 (php2shell) — two critical RCEs in PHP core, session.upload_progress, on by default since 5.4. Docker A/B (vulnerable vs patched builds), race harness, ASAN proof.
php2shell-lab — CVE-2026-109618 / CVE-2026-109619 lab
Layout
php2shell-lab/
├── docker-compose.yml # fpm+nginx (realistic target), vuln-stock (php -S), ASAN A/B
├── target/www/index.php # target: session + peek/destroy/regenerate/gc actions
├── fpm/zz-lab.ini # lab INI (cleanup=Off, freq=1, min_freq=0)
├── fpm/nginx.conf # fastcgi_request_buffering off (streamed body)
├── asan/Dockerfile # AddressSanitizer PHP build of an arbitrary commit (ARG PHP_COMMIT)
├── harness/race.py # throttled multipart upload + concurrent disturbances
Vulnerable images, frozen (pulled BEFORE the Oct 21, 14:00 UTC release)
-
php:8.4-cli@sha256:e2432ac8…(pulled 11:15 CEST) -
php:8.4-fpm@sha256:f5431b57…(pulled 11:25 CEST)
Docker Hub overwrites the tags with patched builds at release time — the digests pinned in docker-compose.yml keep a reproducible vulnerable reference.
Start
docker compose up -d fpm nginx # realistic target on 127.0.0.1:8898
curl 'http://127.0.0.1:8898/index.php?a=ping'
Harness (composable disturbances)
python3 harness/race.py --port 8898 --modes destroy --rounds 20
# modes:…Timeline of a leak
- Oct 20, morning: a "Tell HN" post (since deleted) reveals two PHP RCEs rated CVSS 10.0. Screenshots spread on X and Mastodon, the reserved CVE IDs get quoted.
- Oct 20: watchTowr publicly confirms the bugs are real. PHP publishes an advance notice: CVSS v4.0 9.2 each, security release on Oct 21 at 14:00 UTC — moved up a day precisely because details leaked.
- Oct 21: an exploit video "php2shell" (credited to Calif.IO) and a PoC briefly circulate before being pulled. No confirmed in-the-wild exploitation at the time of writing (Huntress), but Sansec expects mass exploitation within minutes of the patch going public.
The attack surface
The technical details that surfaced in the leak coverage describe "two heap use-after-free primitives in PHP's session.upload_progress handler".
What makes this serious: session.upload_progress.enabled is on by default on nearly every PHP install since 5.4. Which matches the leaked tool's claim exactly: "works against every PHP release from 5.4.0 on".
We verified that surface in the lab. A plain multipart POST carrying a PHP_SESSION_UPLOAD_PROGRESS=<key> field makes the server write an entry fully controlled by the attacker into its own session store:
array (
'upload_progress_fastkey' =>
array (
'start_time' => 1792574947,
'content_length' => 7284,
'bytes_processed' => 7284,
'done' => true,
'files' => array ( ... ),
),
)
That's the official upload-progress-bar feature — nobody enables it, nobody disables it, nobody watches it. And it's a permanent race between the streaming upload and the session lifecycle. Exactly where use-after-free bugs live.
The lab demo
Setup: nginx + php-fpm 8.4 (Docker images digest-pinned pre-patch, pulled before the release), and a Python harness sending a throttled raw-socket multipart upload while a second flow disturbs the session lifecycle (destroy, regenerate, GC, double upload, client abort).
Two details make the race winnable in practice:
- The harness pads the multipart body with small form fields so the RFC 1867 parse loop dominates the race window instead of the network — bodies stay under
post_max_size(8 MB by default), and the request streams chunk by chunk throughfastcgi_request_buffering off. - The slow upload is aimable at any endpoint (
--path), not just a script the lab controls.
That second point is why the lab now ships a second target: stock WordPress on the same frozen vulnerable PHP — default config, no vulnerable plugin, no misconfiguration. The "your code never runs before the bug" claim, made concrete on the most-deployed PHP application on the web. Run it yourself (local only, 127.0.0.1):
git clone https://github.com/slabbdev/php2shell-cve-2026-109618-109619
cd php2shell-cve-2026-109618-109619/wordpress-demo
sh setup.sh # fetches WP + wp-cli, boots, installs, verifies
python3 ../harness/race.py --port 8080 --path /wp-login.php --modes "" --rounds 1
docker compose exec fpm cat /tmp/sess/sess_* # → an attacker-controlled upload_progress_<key> entry
The full chain on WordPress — race to RCE on wp-login.php, then the same harness landing clean on the patched build — is the next lab update, once the official diff is out.
Who is exposed
- Any unpatched PHP web server: php-fpm, mod_php, cgi. No account needed, no vulnerable application endpoint needed — the bug is in the core, and the exploit completes before your application code even runs. Application-level WAFs see nothing.
- End-of-life branches (≤ 8.2) get no official fix: a large chunk of the web has to migrate or rely on distro backports.
- [T0: exact patched versions per branch 8.3.x / 8.4.x / 8.5.x]
The fix, step by step
-
Inventory everything running PHP, including the embedded ones (apps shipping their own
php.exe, cPanel/Plesk, containers):
php -v
php -i | grep -i 'upload_progress.enabled' # On = surface active (default since 5.4)
- Update to the patched release of your branch: <!-- [T0: distro commands — apt/dnf/alpine/brew, exact target versions] -->
- EOL branches: migrating to a supported branch is the only real answer. Until then: network isolation plus detection (below).
- Managed or shared hosting? Open the ticket now — ask your provider exactly when they deploy the patched packages and for which branches. "Soon" is not an answer.
- ⚠️
session.upload_progress.enabled=Offis NOT a fix. It's a partial mitigation the public coverage says is insufficient — the PHP release managers were explicit that patching is mandatory.
Detecting an attempt
Watch for abnormal children of PHP workers: a php-fpm spawning sh, bash, nc, curl or python means it's already too late — but it's the signal. A quick triage on any box:
pstree -p | grep -A2 php-fpm # anything hanging off a worker that isn't a socket = look closer
Reverse-shell patterns in command lines: /dev/tcp/, mkfifo, fsockopen. On unmonitored Linux servers, a free process-watcher in observe mode is better than nothing until real EDR lands.
TL;DR
Patch today, not tomorrow. The full diff analysis, PoC and proofs are in the lab repo: [T0: link].
Sources: official php.net notice · watchTowr · Huntress · Sansec · CERT-EU 2026-018 · [T0: official release notes & upgrade pages for 8.3 / 8.4 / 8.5]
Top comments (1)
⚡ Live story: PHP's security release lands today at 14:00 UTC — the markers in this repo get filled within the hour: exact patched versions, official diff analysis, ASAN A/B proof, and hunt rules + IOCs.
The lab is already runnable in the meantime — frozen pre-patch images, race harness, and a stock-WordPress target on default config: github.com/slabbdev/php2shell-cve-...