On October 21, 2026, the PHP project shipped emergency releases for two critical bugs that let an unauthenticated attacker run code on a web server. The pair is tracked as CVE-2026-109618 and CVE-2026-109619 and goes by the name PHP2Shell. Both score 9.2 on CVSS v4.0, both hit every PHP setup that serves HTTP, and Amazon’s honeypots logged exploitation attempts on October 17, four days before the patch existed.
PHP runs on 69.3% of websites whose server-side language is known (W3Techs). If you run a PHP API, a Laravel or Symfony backend, a WordPress site, or a container image with PHP baked in, this one is yours.
This guide covers what the bugs do, which versions are affected, how to check your servers, what the mitigations really buy you, and how to prove your API still works after you patch.
TL;DR
| Question | Answer |
|---|---|
| What is it? | Two memory-safety bugs in PHP’s request handling, chained into remote code execution |
| CVEs | CVE-2026-109618 (out-of-memory corruption), CVE-2026-109619 (stale state, use-after-free) |
| Severity | CVSS v4.0 9.2 each (early leaked IDs said 10.0; PHP says that was wrong) |
| Auth needed? | No |
| Affected | Every SAPI that serves HTTP: PHP-FPM, Apache mod_php, php-cgi, FrankenPHP, LiteSpeed |
| Fixed versions | 8.5.12, 8.4.27, 8.3.36, 8.2.35 |
| PHP 8.1 and older | No official release; minimal source backports for 7.4, 8.0, 8.1; 5.4 to 7.3 get nothing |
| Exploited? | Attempts seen from October 17 (Amazon), early attacks reported by Cloudflare |
| Fix | Upgrade, restart every worker. Workarounds reduce risk but don’t close it |
What PHP2Shell actually is
Both bugs share one root cause: PHP can leave its memory in a broken state when a request ends abnormally, and the cleanup code then trusts that broken state.
CVE-2026-109618: out-of-memory corruption during request startup. When PHP parses form data into $_POST, the Zend engine’s hash table code bumps its entry counter before it allocates the new key. Datadog’s analysis traces it to _zend_hash_str_add_or_update_i() in zend_hash.c. If that allocation blows past memory_limit, PHP throws a fatal error and abandons the insert. The counter now includes a bucket that was never set up. At shutdown, PHP destroys $_POST and treats every counted bucket as real.
The part that should worry API teams: this happens while PHP is still parsing the request body, before a single line of your code runs. Your auth middleware, your rate limiter and your input validation never see the request. Datadog reproduced it with a plain application/x-www-form-urlencoded body, not a file upload.
CVE-2026-109619: stale global state across requests. PHP keeps some worker-wide state that points at per-request memory. If a fatal error interrupts the reset of that state, or a late shutdown diagnostic recreates it after cleanup, the next request on the same worker reads freed memory. PHP confirmed two paths:
- Fatal-error backtraces (PHP 8.5 only): a configuration diagnostic in a later request reads leftover backtrace state. Command execution confirmed on mod_php.
- Upload processing: after a fatal error, session upload progress can leave state behind for a later upload. Upload progress is on by default and runs even if your app never calls
session_start(). Command execution confirmed on mod_php and PHP-FPM.
The PHP security team rates mod_php as the highest risk, since the demonstrated exploit fires from one unauthenticated request before any application code loads.
Who found it (and why AI is in the headlines)
The bugs date back to code introduced in PHP 5.4.0, so they sat in the codebase for over a decade. The PHP Foundation credited Calif, OpenAI and Wiz.
Reporting from Decrypt and The Next Web says Calif researcher Ryan Austin found the flaws in August during a source audit assisted by OpenAI’s GPT-5.6 Sol. OpenAI says its own team found a fuller version in September as part of its “Patch the Planet” work. Wiz says it found both bugs on its own using Gemini 4 Argon, Wiz Atlas and GPT-5.6 Sol. The credit is disputed; the takeaway isn’t. AI-assisted audits are now finding decade-old memory bugs in core infrastructure, and attackers have the same models.
Timeline: why the patch came a day early
| Date (2026) | What happened |
|---|---|
| August to September | Calif, OpenAI and Wiz report the bugs privately |
| Oct 17 | Amazon’s MadPot honeypot records an exploitation attempt |
| Oct 20 | CVE IDs leak on Hacker News and social media; PHP posts an advance notice; CERT-EU issues advisory 2026-018 |
| Oct 21, 14:00 UTC | PHP ships fixes, moved up from Oct 22 after the leak; CISA publishes an alert; Cloudflare reports early attacks |
| Oct 22 | Neither CVE is in CISA’s KEV catalog yet |
Amazon advises investigating from October 17 (Tenable’s FAQ tracks the full timeline), not the disclosure date. At release, the PHP team said it inspected the proof-of-concept code that appeared after the leak and found no working exploit. watchTowr has since said it reproduced both bugs. Treat any “PoC” repo you find as suspect: unverified exploit code often ships malware.
Are you affected? Check in five minutes
1. Check the runtime that serves traffic, not your laptop. The CLI binary can differ from the FPM or Apache module in version and config.
# On the server or inside the running container
php -v
php-fpm -v
# Apache mod_php: check the loaded module version
apachectl -M 2>/dev/null | grep -i php
Or expose it once from a protected route and remove it after:
<?php echo PHP_VERSION, ' ', PHP_SAPI;
2. Compare against the fixed versions.
| Branch | Vulnerable | Fixed in |
|---|---|---|
| 8.5 | < 8.5.12 | 8.5.12 |
| 8.4 | < 8.4.27 | 8.4.27 |
| 8.3 | < 8.3.36 | 8.3.36 |
| 8.2 | < 8.2.35 | 8.2.35 |
| 7.4 to 8.1 | All | Source backports only, or vendor LTS |
| 5.4 to 7.3 | All | No fix |
Debian, Ubuntu, Red Hat and other distros often backport fixes without changing the upstream version string. If php -v shows an older number, read your distro’s advisory before panicking.
3. Inventory the hidden PHP. Containers built FROM php:8.3-fpm months ago, vendor appliances, self-compiled builds, admin panels on forgotten subdomains. Cisco has already opened an advisory to check its own product line. Shodan counts over 1.9 million internet-facing PHP hosts; one researcher estimates 1.3 million or more may be vulnerable.
How to patch
# Debian / Ubuntu (Ondřej Surý PPA or distro packages)
sudo apt update && sudo apt install --only-upgrade 'php8.*'
# RHEL / Rocky / Alma with Remi
sudo dnf upgrade 'php*'
# Official Docker image: rebuild on a fixed tag
docker pull php:8.4.27-fpm
docker build --pull -t my-api:patched .
Then do the step most teams forget: restart or replace every PHP worker. A running FPM pool keeps the old binary in memory. Restart php-fpm, reload Apache, and roll every container. CVE-2026-109619 is about state that lives across requests inside a worker, so an unrestarted worker is still vulnerable.
On 8.1 or older, you have three options: a vendor LTS package, building from PHP’s source backport, or upgrading to 8.2+. CISA recommends taking unpatchable PHP systems offline, and the UK NCSC and FBI said the same.
Mitigations if you can’t patch today
These buy time. Gina Peter Banyard of the PHP core team told BleepingComputer no mitigation fully blocks the attacks.
For CVE-2026-109618:
- Set
memory_limit=-1where apps can’t override it (php_admin_valuein the FPM pool or Apache config). This blocks the known path, but not every way an allocation can fail. - Lower
post_max_size(8M with no uploads, 32M with uploads). - Cap request body size at the reverse proxy (
client_max_body_sizein nginx), and set container or cgroup memory limits as an availability boundary. - Don’t use
RLIMIT_ASorRLIMIT_DATAprocess limits. They can cause the very allocation failures the exploit needs.
For CVE-2026-109619:
- Set
session.upload_progress.enabled=0in config that loads before request parsing. - On PHP 8.5, also set
fatal_error_backtraces=0. - Hiding errors with
display_errors=0or@does nothing. The stale state still exists.
At the edge: Cloudflare deployed WAF rules for known vectors on every plan, including free. Its CTO called them temporary.
After any php.ini change, restart every worker or it doesn’t take effect.
Signs you were already hit
Datadog’s detection guidance boils down to four signals:
- Log line:
PHP Fatal error: Allowed memory size of ... bytes exhaustedwithUnknown on line 0. That means PHP died during request startup, before any script ran. - Apache crashes:
AH00052: child pid ... exit signal Segmentation fault (11), a likely failed exploit guess. - Odd form posts: a field name several megabytes long, or binary and null bytes in a field with an empty name.
- The strong one: a web worker (
php-fpm,apache2,httpd) spawning a shell.
Don’t trust status codes. Datadog’s refined exploit returned HTTP 200 and the worker kept running. Search logs back to October 17.
Prove your API still works after the patch: let your agent run it
Patching PHP in production under pressure is how you find out a minor version bump changed something. Before and after you roll the fix, run your API test suite against staging, and let your coding agent do it with Apidog CLI.
Setup (once):
npm install -g apidog-cli
apidog auth login --with-token <your-access-token>
apidog test-suite list --project <projectId>
One prompt to Claude Code, Codex or Cursor:
We just upgraded PHP to 8.4.27 on staging for PHP2Shell. Use the Apidog CLI to run our regression test suite against the staging environment, write HTML and JUnit reports, and summarize any failing endpoints. Then confirm a POST with a body above our nginx client_max_body_size gets rejected with 413 at the proxy instead of reaching PHP.What the agent runs:
apidog test-suite run <testSuiteId> --project <projectId> -e <stagingEnvId>
apidog run --test-suite <testSuiteId> --project <projectId> -e <stagingEnvId> \
-r cli,html,junit --out-dir ./php2shell-reports --upload-report
-r picks the reporters, --out-dir sets where reports land, and --upload-report pushes a summary you can open in the Apidog app. Add --notification-failed-event <targetId> to ping your team only when something breaks. Drop the same apidog run line into CI so every rebuilt PHP image gets tested before it ships. The Claude Code setup guide and the CI/CD pipeline walkthrough cover both in detail.
Run the suite once on the old version to get a baseline, once after the upgrade, and diff. If a test you never expected to fail does, you’ll know before your users do. Download Apidog to build and manage the suites the CLI runs.
FAQ
Is PHP2Shell being exploited? Amazon saw attempts from October 17 and says it’s now broadly exploited; Cloudflare and watchTowr report attacks too. CISA hadn’t confirmed exploitation as of October 22.
Does my framework protect me? No. CVE-2026-109618 fires while PHP parses the body, before Laravel, Symfony or WordPress loads.
I’m on shared hosting. What do I do? Check your host’s status page. SiteGround and others say they’ve patched. If yours hasn’t said anything, ask.
Is session.upload_progress.enabled=0 enough? No. It covers one path of one CVE. Patch.
Do I need to rotate secrets? CVE-2026-109619 allows memory disclosure. If logs show signs of compromise since October 17, rotate database passwords, API keys and session secrets the PHP process could read.
The bottom line
Upgrade to 8.5.12, 8.4.27, 8.3.36 or 8.2.35. Restart every worker. Find the PHP you forgot about. Check logs back to October 17. Then run your API tests so the fix doesn’t become the next outage. If your PHP is on 8.1 or older, this is the week to finally move off it.



