PHP2Shell: We Had Detections Running Before PHP Had a Patch

Two critical PHP RCE bugs leaked ahead of disclosure. Before the 14:00 UTC fix shipped, Soc0 had turned a news story into three behavioral detectors running on our clients' data. Here is how, and what attackers did next.

A stone server tower on a floating island, watched by three blue lanterns, while a red shell prompt escaping from it is caught in a detection ring

Details of two critical PHP bugs leaked on Tuesday, October 20, ahead of the planned disclosure, and PHP pulled its release forward to 14:00 UTC on Wednesday. A couple of hours before the fix shipped, our clients already had detections for it running in production.

Nobody on our side had seen the bug yet. We didn't need to.

9.2CVSS v4.0 score on both PHP2Shell CVEs
3Behavioral detectors live before the patch
0Lines of SPL, KQL or XQL written by hand

How the 48 hours played out

All times are UTC. The gap that matters is between the leak and the patch, because that is when every PHP server on the internet was exposed with nothing to install.

WhenWhat happened
Aug 2026Researchers at Calif, OpenAI and Wiz report the bugs to PHP. They stay under embargo while fixes are built.
Oct 17Amazon's MadPot honeypots log a PHP2Shell exploitation attempt, four days before disclosure.
Oct 20, morningDetails leak online ahead of the planned October 22 disclosure. watchTowr confirms the leak is genuine.
Oct 20, 19:33PHP's security team announces an emergency release for the next day at 14:00 UTC.
Oct 21, 07:43Cloudflare's CTO reports early attack attempts against PHP sites.
Oct 21, before 14:00The story lands in Soc0's Discover feed. Three detectors are generated, hunted and deployed for our clients.
Oct 21, 14:00PHP ships 8.5.12, 8.4.27, 8.3.36 and 8.2.35.
Oct 21 to 22Honeypots watch probes turn into web shells, download cradles and Mirai loaders.

The bug, briefly

Researchers are calling it PHP2Shell, but it's really two separate memory-corruption bugs in the PHP engine. Both are rated 9.2 on CVSS v4.0. Both can be exploited over HTTP without authentication, and on mod_php the attack lands before any application code runs.

CVEPHP's descriptionWhat goes wrong
CVE-2026-109618RCE via OOM corruptionA request hits memory_limit partway through an allocation. PHP bails out and leaves half-built data behind, which request cleanup then trusts.
CVE-2026-109619RCE via stale global stateA fatal error skips teardown, so the worker keeps a pointer to freed memory. The next request on the same worker uses it.

Both share a root cause. A fatal error interrupts PHP mid-operation, and PHP later treats the incomplete state as valid. Every runtime that serves HTTP is in scope, including PHP-FPM, Apache mod_php, php-cgi, FrankenPHP and LiteSpeed. Datadog Security Labs has the best technical breakdown so far.

These are old bugs. Calif says the out-of-memory flaw was first found in 2020 and rediscovered in a 2024 audit. Exploiting it took an exceptionally complex chain of steps, and Wiz says frontier AI models substantially reduced that barrier. Expect more of this: long-standing bugs that suddenly become cheap to weaponize.

Upstream fixes only cover PHP 8.2 through 8.5. Branches 7.4, 8.0 and 8.1 got minimal source backports meant for people who build PHP themselves. Mallory counted about 884,000 internet-facing hosts whose banners advertise PHP 8.1 or older.

Nothing to write a signature against

When the story broke, PHP's notice didn't say which component was affected, which versions, or what an attacker needed. You can't write an exploit rule for a bug nobody has described, and patching a big PHP estate takes days at best.

Even the basics were in flux. The leak claimed a CVSS score of 10.0, and PHP rated both bugs 9.2. Early coverage tied the session upload-progress path to the wrong CVE. Anyone building detection logic from the first wave of reporting was building on sand.

What you can watch is what happens after someone gets code execution on a PHP server. That part doesn't change much from one bug to the next.

You can't write an exploit rule for a bug nobody has described. You can write one for what attackers do the minute after it works.

What Soc0 did with a headline

The Hacker News story landed in Soc0's Discover feed soon after it went up, rated High severity with Medium source confidence. Soc0 read the report, picked out the post-exploitation activity it described, and turned that into three detector templates already mapped to each client's security products.

Soc0 Discover showing The Hacker News PHP2Shell story with an executive summary, five key takeaways, and three detector templates with Hunt and Adopt buttons
The PHP2Shell story in Soc0 Discover on October 21: summary, takeaways, and three detector templates ready to hunt or adopt.

The summary was honest about what wasn't known. It flagged that the CVE IDs came from a leaked post, that PHP's own notice named no workaround or technical detail, and that end-of-life branches would get no vendor patch.

After that it was a matter of running a hunt over historical data and deploying. Nobody had to write SPL, KQL or XQL. The whole path looks like this:

  1. Discover. The article arrives in the feed, scored for severity, popularity and source confidence.
  2. Extract. Soc0 pulls out the attacker behavior the report describes, not the indicators.
  3. Map. Each behavior becomes a detector template, pre-parsed into the right query for whichever EDR or SIEM the client has connected.
  4. Hunt. The detector runs back over historical telemetry first, so you learn whether you were already hit before you start waiting for new alerts.
  5. Adopt. One click puts it into production.

The three detectors

1. Web server or PHP process spawning a shell

The first one looks for php-fpm, php-cgi, httpd or apache2 starting a shell or interpreter: sh, bash, cmd, PowerShell, Python or Perl. A PHP worker has almost no reason to do that, so when it happens, someone is usually running commands through it. Rated High.

If you want to build your own version, here is the logic as a Sigma-style sketch. Baseline the legitimate children in your environment first. PHP's exec() and system() run through sh -c, so apps that shell out to ImageMagick or Git will trip it.

yamlphp-worker-spawns-shell.yml
title: Web server or PHP process spawning a shell
status: experimental
logsource:
  category: process_creation
detection:
  php_or_web_parent:
    ParentImage|contains:
      - '/php-fpm'
      - '/php-cgi'
      - '/httpd'
      - '/apache2'
      - '\php-cgi.exe'
      - '\httpd.exe'
  shell_child:
    Image|endswith:
      - '/sh'
      - '/bash'
      - '/dash'
      - '/python3'
      - '/perl'
      - '\cmd.exe'
      - '\powershell.exe'
      - '\pwsh.exe'
  condition: php_or_web_parent and shell_child
level: high

2. Download or staging tool launched by PHP

The second catches PHP processes, or their children, running curl, wget, certutil, bitsadmin or Invoke-WebRequest. That's typically an attacker pulling down a second-stage payload. Also High.

3. New script written to the web root

The third watches for the web server or PHP worker writing .php, .phtml, .phar, .jsp or .aspx files into a web-accessible directory, which is how web shells get dropped. It's rated Medium because deploys and CMS updates do the same thing legitimately, so expect to tune it.

Then the attackers showed up and did exactly that

Within hours of the release, honeypot operators and researchers started publishing what exploitation looked like on the box. It lined up with the three detectors almost one to one.

Seen after exploitationReported byDetector that fires
The public PoC running uname -a; id through the workerELLIO1. Shell from PHP
apache2 (www-data) → sh -c → bashDatadog, lab reproduction1. Shell from PHP
curl calling out to attacker infrastructure, and a loader pulling a Mirai variant into /tmpELLIO2. Download tool
<?php system($_GET["cmd"]); ?> dropped as .php, .phtml, .php5 and .php.jpgELLIO3. Script in web root

ELLIO watched one address go from first probe to a working exploit in 48 minutes, and to an installed web shell 81 minutes after the probe. That is the window you get.

It's also why perimeter rules alone weren't enough. Cloudflare described its emergency WAF rules as temporary, and CrowdStrike found that after a successful hit, "the worker process does not crash, the server returns a standard HTTP 200 response, and application logs record nothing unusual." The dependable evidence was the process the worker started next.

Why behavior beats the CVE

None of these detections refer specifically to CVE-2026-109618. Behavior will catch a different adversary using a different vulnerability a different time.

The same three detectors would have fired on the follow-on activity from CVE-2024-4577, the PHP-CGI bug that drew exploit attempts within 24 hours of disclosure in 2024. They will fire on whatever PHP bug comes next. That is the core idea behind building detections that matter: aim at what the attacker has to do, not at the door they used.

Invest in detections that don't rely on a signature for a specific CVE.

Amazon Threat Intelligence, on PHP2Shell

The race is also getting shorter. When disclosure-to-exploit is measured in hours, and sometimes in negative days, a workflow that waits for a vendor signature loses by design. The workflow that wins already knows what post-exploitation looks like and only needs to be pointed at the new target.

If you run PHP

  1. Patch. Move to 8.5.12, 8.4.27, 8.3.36 or 8.2.35. On 8.1 or older, get a backport from your distribution or LTS vendor, or upgrade.
  2. Restart every worker. Running PHP-FPM pools and Apache children keep the old code in memory. Check the process actually serving requests, not just the package version, since backported builds can report the original version number.
  3. Hunt back to October 17. Amazon logged an exploitation attempt four days before disclosure. Run whatever detections you put in place back over your logs from then, not from the day the news broke.
  4. Watch the quiet tells. Allowed memory size ... exhausted fatals that end in Unknown on line 0, segfaulting Apache children, and POST bodies carrying PHP_SESSION_UPLOAD_PROGRESS.
  5. If you can't patch today, narrow the paths. PHP published temporary settings that block the demonstrated routes. They are below.
iniphp-fpm pool config (stopgap only)
; Temporary PHP2Shell mitigations. Not a fix: patch, then remove.
; php_admin_* stops applications from overriding these at runtime.

; CVE-2026-109619: close the session upload-progress path
php_admin_flag[session.upload_progress.enabled] = off

; CVE-2026-109619 on PHP 8.5: close the fatal-error backtrace path
php_admin_flag[fatal_error_backtraces] = off

; CVE-2026-109618: remove the memory_limit trigger
php_admin_value[memory_limit] = -1

Next time

The next PHP2Shell is already sitting in someone's audit notes. When it leaks, the useful question won't be whether a signature exists yet. It will be whether you're already watching what attackers do once they're in.

That's the loop we built Soc0 to run: from article to applied detector in minutes, on the security stack you already have.

See Port0 on your own data.

Bring your noisiest alert queue. Watch Soc0 investigate it live.

Book a Demo

Never Miss an Insight

Subscribe to get the latest posts delivered to your inbox.