Author: @ Deepak Kandpal
PHP2Shell Explained: Inside CVE-2026-109618 & CVE-2026-109619 (And How To Check If You're Hit)
If you run anything on PHP — WordPress, Laravel, Drupal, a weird internal tool from 2014, doesn't matter — there's a good chance you spent part of last week patching it. On 21 October 2026, the PHP security team shipped emergency fixes for two critical, unauthenticated remote code execution bugs that the security community has nicknamed PHP2Shell.
I wanted to actually understand what's broken under the hood instead of just copy-pasting "upgrade now" into a Slack channel, so I went digging — and then I built a tool so you (and I) don't have to dig by hand on every host we're responsible for. This post covers both: the bug, and the tool.
:: Enjoy the content☕ ::
🧨 What Is PHP2Shell?
PHP2Shell isn't one bug — it's two, tracked separately but disclosed together:
| CVE-2026-109618 | CVE-2026-109619 | |
|---|---|---|
| Type | Out-of-memory memory corruption | Use-after-free |
| Severity | CVSS v4.0 9.2 | CVSS v4.0 9.2 |
| Auth required | None | None |
| Where it lives | PHP's request-parameter hash table | PHP's worker-wide request state |
| GHSA ID | GHSA-w8hw-w9fx-j79w |
GHSA-6wmf-4rv5-p3rp |
Why It Matters:
Both bugs live in PHP core, not in your application. That means your login page, your WAF rules, your
.htaccessdeny-lists — none of that matters. PHP processes the request before any of your code runs.
🕵️ How It Was Found
The short version, pieced together from the public advisories and vendor write-ups:

Security Note:
Amazon's threat intel team said it's unclear how an attacker had working exploit information before the public leak. That's the uncomfortable part of this story — some people apparently knew before the rest of us did.
🔬 The Two Bugs, Broken Down
1. CVE-2026-109618 — The Out-of-Memory Trap
PHP stores your submitted form fields in a hash table. The insertion code has a quiet ordering bug: it increments the table's bookkeeping counters before it allocates the new key's memory.

Key Insight:
The exploit doesn't need to crash the worker or even change the HTTP response you see. It can succeed silently with an HTTP 200 still coming back. Don't trust "nothing looks broken" as a sign you're safe.
2. CVE-2026-109619 — The Stale Pointer
This one's a classic use-after-free, with a very un-classic entry point: PHP's session upload-progress feature.
Some fields PHP uses are meant to live for the whole worker's lifetime, not just one request. Problem is, under certain conditions PHP frees the memory those fields point to — without clearing the pointer.

Critical:
This needs the attacker's request and the victim-triggering request to land on the same PHP worker. A bigger worker pool lowers the odds per attempt — it does not fix the bug. Confirmed trigger paths:
session.upload_progress(mod_php and PHP-FPM) and, on PHP 8.5 only, fatal-error backtrace generation under mod_php.
🌍 Is It Actually Being Exploited?
Yes. Multiple independent sources confirm active, broad exploitation, not just scanning:
- Amazon MadPot honeypots — first hit 17 October, four days pre-disclosure
- watchTowr's Attacker Eye honeypot network — attempts from 21 October onward
- Hadrian — exploitation at scale on 21 October, mostly via the
session.upload_progresspath - Cloudflare — confirms exploitation is underway on its network
Risk:
At the time PHP shipped the patch, PHP itself said it had not identified a working public exploit — but unverified proof-of-concept code started circulating within hours. Treat "is there a public exploit" as a moving target, not a reason to wait.
🩹 The Patch — What Actually Changed
PHP's security release on 21 October 2026 fixed four currently-supported branches:
| Branch | Fixed in | Status |
|---|---|---|
| 8.2 | 8.2.35 | ✅ Fixed |
| 8.3 | 8.3.36 | ✅ Fixed |
| 8.4 | 8.4.27 | ✅ Fixed |
| 8.5 | 8.5.12 | ✅ Fixed |
| 7.4 / 8.0 / 8.1 | (source backports only) | ⚠️ No official release |
| 5.4 – 7.3 | (none) | ❌ End-of-life, unfixed |

If you're on Ubuntu, the fix is already in your archives — check the exact package version, not just what php -v prints, since distro builds keep the upstream version number in some places:
| Ubuntu release | Fixed package version |
|---|---|
| 26.10 | 8.5.9-0ubuntu2.1 |
| 26.04 LTS | 8.5.4-0ubuntu1.5 |
| 24.04 LTS | 8.3.6-0ubuntu0.24.04.13 |
| 22.04 LTS | 8.1.2-1ubuntu2.28 |
| 20.04 / 18.04 / 16.04 / 14.04 | ESM only — needs Ubuntu Pro |
Interim mitigation (not a fix — buys you time only):
; Reduces exposure to CVE-2026-109618
memory_limit = -1
; Reduces exposure to CVE-2026-109619
session.upload_progress.enabled = 0
; PHP 8.5 only, also reduces 109619 exposure
fatal_error_backtraces = 0
Security Note:
PHP's own advisory is blunt about this: these settings reduce exposure, they do not make the server safe. Patch the binary. Restart every worker afterward — a config change alone, without a restart, won't touch workers already running.
🔎 How Do I Know If I'm Vulnerable?
Here's the annoying part: most production servers hide their PHP version (expose_php=Off), which is normally good hardening — except now it also hides whether you're exposed to this specific bug.

Option 1: Check it yourself, on the box
php -v
# or, on Debian/Ubuntu:
dpkg -l 'php*' | grep ^ii
# or, on RHEL-family:
rpm -q --changelog php
Compare the result against the table above. Done — this is always the most reliable answer, since it's not guessing from the outside.
Option 2: Check it from the outside — with my tool
This is where it got genuinely interesting for me to build. Most "PHP2Shell checkers" floating around right now just read the X-Powered-By header and call it a day — which tells you nothing if the version is hidden (the common, hardened configuration). So I built php2shell-scanner to actually try to settle the hidden-version case too, not just the easy one.
🛠️ Meet php2shell-scanner
github.com/kunshdeep2812/php2shell-scanner
A free, open-source, single-file Python scanner — no dependencies, GUI and CLI, built for bug bounty hunters, sysadmins, and anyone who wants a straight answer about their own infrastructure.

What it actually does
- Fingerprints the stack — web server, PHP, CMS, framework, CDN/WAF — from ~5,800 known technology signatures plus ~70 built-in checks, so you also get a map of what's running, not just a yes/no on PHP.
- Grades the PHP version against the real fixed-release table above, with backport awareness for Ubuntu, Debian, RHEL-family and
deb.sury.orgbuilds (so it doesn't cry wolf on a patched distro package just because the upstream version string looks old). - Settles the hidden-version case with an opt-in active patch oracle: one small, harmless multipart POST that reads how PHP itself reports a malformed session upload. Three warnings back = unpatched. Two = patched. It never triggers the actual memory-corruption bug — it reads behavior, it doesn't exploit anything.

That screenshot above is the whole point of building this: 127.0.0.1 there discloses zero PHP version information — a properly hardened host — and the tool still gets a confirmed answer.
- Writes real evidence. With
--evidence, it produces a Markdown report with the exact request sent, a trimmed response, and acurlcommand to reproduce it — handy for attaching to a bug bounty report or a ticket. - Scans your own LAN too — single IPs, CIDR ranges (
192.168.1.0/24), custom ports, the works. - One-click setup, whatever Python you have:
git clone https://github.com/kunshdeep2812/php2shell-scanner.git
cd php2shell-scanner
python3 setup.py
setup.py finds (or, with your permission, installs via pyenv) a Python 3.8+ interpreter, builds an isolated virtual environment, and gets out of your way.
Quick scan of your own site
python3 scanner.py --cli --authorized -t yourdomain.com --deep --oracle
Or just run python3 scanner.py with no arguments for the local web GUI.
Why It Matters:
I deliberately did not build this as an exploit-confirmation tool. It never triggers the memory-corruption bug, never crashes a worker on purpose, and never writes anything to the target. A known-vulnerable version is reason enough to patch — you don't need a working shell to justify that.
I also built a little docker-compose lab in the repo with real, deliberately-unpatched and patched PHP containers so you (or I) can verify the tool's claims against the real bug instead of trusting marketing copy. Transparency over vibes.
✅ TL;DR Checklist
- Confirm your PHP version — on the host if you can, with the tool if you can't
- Upgrade to 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12 or your distro's equivalent patched package
- Restart every PHP worker after patching — config changes alone don't help running processes
- If you can't patch right now, set the interim
.inimitigations above and treat it as a ticking clock, not a fix - Rebuild any container images that bake in an old PHP version
📚 Further Reading
- CERT-EU Security Advisory 2026-018
- Datadog Security Labs — PHP2Shell analysis
- Tenable — PHP2Shell FAQ
- watchTowr Intelligence
- Ubuntu USN-8948-1 · USN-8948-2
Patch your stuff, restart your workers, and go check that server you've been meaning to look at since last Tuesday. 😉
⭐ Star / fork php2shell-scanner on GitHub →
Stay tuned – for more exciting content !