News
PHP2Shell: Two Bugs in PHP Itself Let Anyone Run Code on 70% of the Web (2026)
Two unauthenticated RCE bugs in PHP's own engine, CVSS 9.2 each, were found by AI research tools, exploited in the wild before the embargo lifted, and patched on October 21, 2026. If you run WordPress, Laravel, a cheap host, or a Docker image with PHP in it, this one isn't your app's fault, but it's still your afternoon. SaaSCity runs its own small PHP-adjacent tooling, which is exactly why we read the advisory instead of the headline.

Contents (8)
- The quick version, before the details
- What the two bugs actually do
- Who's actually exposed
- How this got found, and the leak that moved the release up a day
- Exploitation: this was never theoretical
- The 45-minute action list
- Why this is a business story, not just a patching story
- The thought worth sitting with
Quick answer: On October 21, 2026, the PHP project shipped an out-of-band security release for two unauthenticated RCE bugs living in PHP's core engine, not in any app or framework: CVE-2026-109618 and CVE-2026-109619, both CVSS v4.0 9.2. Fixed versions are PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35, released 14:00 UTC that day. Amazon's threat intel caught exploitation attempts four days before disclosure, public proof-of-concept code showed up from 02:00 UTC on release day, and broad exploitation was underway within hours of the embargo lifting. If you run PHP anywhere — WordPress, Laravel, a plugin, a Docker base image, cheap shared hosting — this is not a "read later" advisory.
Here's the sentence that should ruin your morning: the hole was never in your code. It was in the thing every single PHP request runs through before your code gets a chance to do anything at all, and it's been sitting there for roughly a decade.
The quick version, before the details
You're reading this on a startup directory's blog, not a security vendor's, so the honest framing up front: we didn't find this bug and we're not the ones who patched it. What follows is built from the PHP project's own advisories, Wiz Research's write-up, AWS's threat intelligence post, and OpenAI's disclosure notes — read end to end, not skimmed for a headline. SaaSCity runs a modest stack of its own, and a flaw that sits underneath 70% of the web is exactly the kind of thing we stop and read carefully instead of summarizing in a tweet.

What the two bugs actually do
Forget the C internals for a second. Here's the plain version.
PHP has a memory limit per request — that's the memory_limit setting every host nags you about. When a request blows past it, PHP doesn't crash the whole worker. It bails out mid-execution using something called zend_bailout, basically an emergency exit that unwinds the current request and gets the worker ready for the next one. That's supposed to be clean. It isn't.
CVE-2026-109618 happens when that emergency exit fires while some of PHP's internal request state is only half set up. The cleanup code assumes a fully-initialized state that doesn't exist, so the worker finishes the aborted request with corrupted internal structures still sitting in memory. The next request handled by that same worker inherits the mess — and depending on what's in that mess, an attacker can turn it into code execution.
CVE-2026-109619 is the same root problem from a different angle: a global variable still points into memory that belonged to the request that just bailed out. The next request reuses that stale pointer without knowing it's stale, which means it can read — or write — memory it was never supposed to touch.
Both are triggered by the same basic move: force PHP to run out of memory at the wrong moment, then send a second, carefully shaped request to the same worker to collect the payoff. Wiz's demonstration didn't even need to reach code execution to be scary — they showed unauthenticated memory disclosure, pulling passwords, tokens and API keys straight out of a PHP worker's memory using nothing but an oversized value URL-encoded into the PHPSESSID response cookie. That trick works even on apps that never call session_start().
The fix, landed in a single upstream commit, 01a40e89fb ("Avoid state corruption via bailout") by Arnaud Le Blanc and co-authors, makes out-of-memory a properly recoverable condition, locks down the multipart/upload-progress code path so it can't run class code while it's mid-decode of a session, and resets the globals that startup logic depends on. One commit, two CVEs, a decade of exposure closed in one patch.
Who's actually exposed
This isn't an app-layer bug, so exposure comes down to which server API (SAPI) is handing requests to PHP. Here's how it breaks down per the available analysis:
| SAPI | Exposure |
|---|---|
| mod_php (Apache) | Confirmed unauthenticated RCE, reproduced with default settings and prefork — the easiest path, since it can trigger before any application code even runs |
| PHP-FPM | Susceptible per source-code analysis, with the session-disclosure path independently validated |
| FrankenPHP | Likely affected |
| LiteSpeed | Likely affected, not independently validated |
| php-cgi | Listed as at risk |
Wiz says every PHP release from the last decade carries at least one of the two bugs, back to PHP 5.4. There's no app-specific "are you vulnerable" question to ask here. A bare index.php with echo "Hello World"; and nothing else, on an unpatched build, is vulnerable. The CMS, the framework, the plugin ecosystem — none of it matters. The engine underneath all of it is the target.
Fixed versions, exact
| Branch | Fixed in | Released |
|---|---|---|
| PHP 8.5 | 8.5.12 | Oct 21, 2026, 14:00 UTC |
| PHP 8.4 | 8.4.27 | Oct 21, 2026, 14:00 UTC |
| PHP 8.3 | 8.3.36 | Oct 21, 2026, 14:00 UTC |
| PHP 8.2 | 8.2.35 | Oct 21, 2026, 14:00 UTC |
| PHP 8.1 and older | No official release — minimal source backports on branches PHP-7.4-CVE-2026-109618-109619, PHP-8.0-..., PHP-8.1-... | Oct 21, 2026 |
PHP 8.2 hits end of life on December 31, 2026. If that's the branch you're on, 8.2.35 buys you a little over two months, not a long-term plan.

One more trap, specific to anyone on a distro package instead of a source build: distro version numbers don't follow upstream numbering. Ubuntu 24.04 fixed this in php8.3 version 8.3.6-0ubuntu0.24.04.13 — so php -v on a fully patched Ubuntu box still prints 8.3.6. If you're grep-ing logs or dashboards for the string "8.3.36," you'll conclude the opposite of the truth. Check the actual package version (dpkg -l 'php*' on Debian/Ubuntu) and its changelog, not PHP_VERSION. Red Hat hadn't shipped fixed packages by the time AlmaLinux posted its own advisory on October 21; Alma said its builds would land ahead of RHEL's.
How this got found, and the leak that moved the release up a day
Wiz Research found the first bug on August 19, 2026, using its Wiz Atlas platform paired with Gemini 4 Argon and GPT-5.6 Sol. Within a day they'd confirmed unauthenticated RCE on Drupal (August 20) and then WordPress (August 21). They reported to the PHP security team on August 24. A second team, the security testing company Calif, found the same class of bug independently, using GPT-5.6 Sol as part of OpenAI's own vulnerability-discovery coordination effort.
PHP acknowledged on September 21 and began a coordinated fix. The release was originally planned for October 22. It shipped a day early, on October 21, after details of the vulnerabilities leaked on October 20.

Wiz's own framing is worth repeating rather than paraphrasing: large language models can adapt an exploit to different PHP versions, configurations and memory layouts, turning work that used to take a strong team months into something one researcher did in about a week. That's not a hype line — it's the actual reason a 2016-vintage memory bug is this week's emergency instead of a historical curiosity.
Exploitation: this was never theoretical
Amazon's threat intelligence team found a PHP2Shell exploitation attempt in MadPot telemetry dated October 17, 2026 — four days before public disclosure, originating from an IP on a commercial anonymization network, while the bug was still under embargo. Someone had it before the fix shipped.

On release day itself: public proof-of-concept code appeared from roughly 02:00 UTC, more attempts followed through 08:00 UTC from different anonymized infrastructure, and once the embargo lifted at 14:00 UTC, PHP2Shell was being broadly exploited in the wild. The NSA posted guidance on X the same day with a blunt line: if you can't patch immediately, pull the site off the web until you can. Cloudflare, AWS, Akamai, Fastly, Imperva, Microsoft, Vercel, Google and Automattic all shipped WAF rules or guidance within hours. Automattic and the WordPress project were both explicit that this is not a WordPress bug — the fix lives at the server or hosting level.
The 45-minute action list
In order, because order matters here:
- Patch, then restart — not either/or. A running php-fpm or Apache worker keeps the old vulnerable code loaded in memory even after you update the package. Restart the service. If you run containers, rebuild from a patched base image (
php:8.5-apacheetc.) and redeploy;docker pullon a tag you've already pinned to a digest does nothing by itself. - Check every host that's actually answering requests, including the one you forgot about: the WordPress site on shared hosting, the Laravel queue worker, the phpMyAdmin install, the staging URL that's technically public. Run
php -v,php -i | grep memory_limit, and check your FPM pool config on each one. Shared hosts frequently hide a PHP version selector in the control panel — that's where this fix happens, not in your git repo. - If you genuinely can't patch today, PHP's own mitigation list is: set
memory_limitsomewhere the app can't override (php_admin_valuein the FPM pool or Apache config, not .user.ini); keeppost_max_sizelow (8M if you take no uploads, 32M if you do, withupload_max_filesizeset below that); enforce request-size and header limits at your reverse proxy; use a container or cgroup memory limit purely as an availability backstop — explicitly notRLIMIT_ASorRLIMIT_DATA, since those can trigger the exact allocation failure that causes the bug. For CVE-2026-109619 specifically: setsession.upload_progress.enabled=0, and on PHP 8.5,fatal_error_backtraces=0, then restart every single worker, sinceini_set()calls and.user.inifiles apply too late in the request lifecycle to matter. Say it plainly to yourself: there is no config-only fix. Updating is the only complete one. - Hunt for signs of compromise before you assume you're clean. Look for POST/multipart requests carrying
PHP_SESSION_UPLOAD_PROGRESS, memory-limit errors followed by repeated HTTP 500s, oversized or malformedPHPSESSIDvalues coming back in response cookies, unusual PHP worker churn, PHP processes spawning shells or download utilities, new files in the web root, unfamiliar cron entries or system users, and unexpected outbound connections from the web server. Triage by worker, not just by source IP — the exploit is a two-request sequence on the same worker, so one odd-looking 500 in isolation proves nothing either way. - Rotate credentials on anything that box could reach if it sat unpatched with internet exposure: database passwords, app secret keys,
.envcontents, payment and SMTP credentials, any API key living in the web root. Same instinct as the LiteLLM rotation checklist we walked through for a different kind of compromise. - Get off end-of-life PHP. 8.1 and older have no upstream fix at all. 8.2 dies December 31, 2026. If your hosting panel is still offering 7.4 as an option, that's not a version number, that's a liability sitting on your invoice.
Why this is a business story, not just a patching story
The uncomfortable part isn't that PHP had a bug. Every runtime has bugs. The uncomfortable part is that the cost of weaponizing a decade-old memory flaw just fell off a cliff. Discovery got cheaper because AI research tooling can chase a hunch across a codebase faster than a team of humans. Exploitation got cheaper because the same tooling can re-adapt a working exploit to a different PHP build, config and memory layout in hours instead of months. "Too complex to actually exploit" stopped being a load-bearing security control sometime in the last year, and nobody sent out a memo.
For an indie SaaS, the blast radius of a pre-auth RCE isn't "one endpoint." It's the whole box — marketing site, app, billing webhook, the database credentials sitting in .env, all behind the same vulnerable PHP-FPM pool. That's a company-ending event for a solo founder, not a Tuesday ticket. Moving a launch date by a day because you're patching the server is the cheapest decision you'll make all month, and it's a decision worth getting comfortable making before it's forced on you. This is also, bluntly, the reason review queues exist on directories like SaaSCity — a gamified startup directory with a live city map and human editorial review, a free listing page and a building on the map for anyone who wants one. Nobody wants to find out their server's still running 7.4 because a directory listing surfaced it. Adding the SaaSCity badge to your site earns a dofollow backlink and a Monday launch slot; Quick Pass ($19.99) goes live within 24 hours, and Premium ($99.99) adds a written launch post with three dofollow links — none of which has anything to do with whether your PHP is patched, which is the whole point. A good listing gets you found. It doesn't make you secure.
The AI angle cuts both ways here, and it's worth saying plainly rather than picking a side: frontier models found these bugs before attackers weaponized them at scale and bought defenders a window through coordinated disclosure. The same models made adapting the exploit across versions and configs cheap enough for opportunistic attackers to do it in days. That's not a contradiction. That's just the new baseline for every runtime you didn't write and every dependency you didn't audit — see also what happened with a compromised PyPI package, a supply chain attack through npm, and exposed databases from default configuration. None of those were your code either. All of them were your problem the moment they shipped.
The thought worth sitting with
For most of the last decade, "nobody's going to bother chaining a decade-old memory corruption bug against a random PHP box" was a real, if unspoken, part of everyone's threat model. That assumption is dead now, and it's not coming back. If you build or run anything on top of a runtime, a framework, or a dependency you didn't personally audit — which is to say, if you build software at all in 2026 — the question isn't whether something underneath you gets found next. It's whether you'll be the one restarting the worker in the first hour, or the one explaining to a customer why their API key showed up for sale a week later. Go check what's actually answering requests on your boxes right now. Then restart it.
Get your SaaS in front of founders
List your product on the SaaSCity live city map - a permanent listing, real discovery, and a backlink from a high-DR directory. Free to start; upgrade for a dofollow link and a building on the map.


