The patch shipped at 14:00 UTC on October 21, 2026. By then the vulnerability had already been probed for four days, and within an hour of a container vendor publishing fixed images ahead of the embargo, someone was firing exploits at production sensors. php2shell is no longer a disclosure story. It is an incident response story with an unusually thin evidence trail.

This is what the public telemetry actually shows about exploitation, what the attempts look like on the wire, and why most organisations will not find the compromise in their application logs.

The four-day head start

Amazon Threat Intelligence published the detail that reframes the whole timeline: its MadPot honeypot network recorded a php2shell exploitation attempt on October 17, 2026, four days before disclosure. The source was 103.216.220.92, an address geolocated to Australia and associated with a commercial anonymization network.

Two things follow from that. First, someone had exploit knowledge while the bug was still under embargo, and the targeting of a honeypot from anonymized infrastructure looks like actor behaviour rather than a scanner's accident. Second, and more important for defenders: the disclosure date is not the start of the exposure window. Amazon's own guidance is to begin investigation from October 17, not October 21.

Amazon does not claim to know how the actor obtained the exploit, and nobody has published evidence that resolves it. What is on the record is that separate parties were working the same problem well before the advisory: watchTowr reproduced CVE-2026-109618 on October 20, and the PHP Foundation moved the release forward by a day after the identifiers leaked.

The Foundation's own timeline fills in the hours before the release-day PoCs. At around 15:00 UTC on October 20, the two CVE IDs began circulating on forums, attached to a wrong release time and a wrong CVSS score of 10.0. The project brought its pre-announcement forward to 19:30 UTC. Around 20:00 UTC, Calif published a video of a working proof of concept by mistake, and copies circulated. The Foundation's assessment is that this accelerated PoC development on October 21, which is the most plausible explanation for working exploits appearing at 02:00 UTC, ten hours before the patch existed.

The embargo broke at 07:41 UTC

The coordinated release was scheduled for October 21 at 14:00 UTC. It did not hold.

Chainguard, which had received the patch in advance so its users could update on disclosure, published built images containing the fix before the embargo lifted. The PHP Foundation's public statement is blunt about the consequence: any attacker could study the fix in binary form while every other PHP user was still waiting.

ELLIO's sensors put the first php2shell request at 08:36 UTC — under an hour after those images appeared. That is the shape of modern disclosure failure. A patch is not just a fix; it is a diff, and a diff against a memory-safety bug is a map. Publishing fixed artefacts early converts a coordinated release into an uncoordinated one for everyone else.

Amazon's telemetry shows the same compression from the other direction. It identified multiple proof-of-concept exploits on release day with the earliest around 02:00 UTC, then additional exploitation attempts against MadPot from further anonymized infrastructure between 02:00 and 08:00 UTC. The payloads and the anonymization choices varied, which Amazon reads as multiple actors rather than one campaign.

What the attempts look like

The observed traffic is not subtle in aggregate, even though it is subtle per request.

VulnCheck identified two hosts, 85.11.167.243 and 45.198.224.169, using the user-agent php2shell-scan/109618 against its honeypots, and a third, 109.71.252.97, exercising both CVEs. A user-agent that names the CVE is a scanner, not an intrusion, but it is a reliable marker that you were enumerated.

Amazon's indicators are harder to characterise because they are shared anonymization egress, and AWS says so explicitly: investigate observations involving them rather than blanket-blocking them.

IndicatorObservedNote
103.216.220.922026-10-17Pre-disclosure attempt, Australia-geolocated
31.223.184.1662026-10-21Release-day attempt
37.19.200.52026-10-21Release-day attempt
134.209.159.642026-10-21Release-day attempt
103.114.163.1882026-10-21Release-day attempt
95.217.98.372026-10-21Release-day attempt
85.11.167.2432026-10-21UA php2shell-scan/109618 (VulnCheck)
45.198.224.1692026-10-21UA php2shell-scan/109618 (VulnCheck)
109.71.252.972026-10-21Both CVEs (VulnCheck)

On the request side, the exploit has a recognisable shape because of how CVE-2026-109618 is reached. The bug lives in the multipart parser's handling of an allocation failure, so the trigger is a POST with Content-Type: multipart/form-data and a body large enough to exhaust the request heap. AWS's recommended hunt is exactly that: look for POST requests carrying the string PHP_SESSION_UPLOAD_PROGRESS in form uploads. The upload-progress mechanism is one of the paths that reaches the vulnerable code before any application script runs.

Cloudflare reports a second-order behaviour worth noting: it is seeing actors mutate payloads to evade WAF rules, using the volume of matches as a signal to work out what the rule keys on. Signature-based blocking of a specific body is therefore a temporary measure, not a boundary.

Why the logs are empty

CrowdStrike's write-up contains the sentence that should shape every detection plan for this bug:

There is no authentication to bypass, no session to steal, and no vulnerable endpoint to discover. The exposure is independent of the CMS, framework, or plug-ins in use.

And the consequence:

Worker processes remain stable, responses return HTTP 200, and application logs record no anomalies, because no application code ever ran. The only server-side trace is a single memory-limit fatal in the web server's error log.

That last clause is the detection. A normal PHP out-of-memory error is attributed to a script and a line. When the failure happens during the multipart decode that precedes script compilation, the error is logged with the file and line as:


PHP Fatal error:  Allowed memory size of 268435456 bytes exhausted (tried to allocate 20480 bytes) in Unknown on line 0

in Unknown on line 0 is the tell. A legitimate application hitting its memory limit names a file. A request that dies before PHP knows which file it is running does not. Any burst of in Unknown on line 0 memory-limit fatals on a public-facing PHP worker deserves a look, especially if the same client address produced a multipart/form-data POST immediately before.

The same reasoning explains why HTTP status codes are useless here. A successful exploit runs in the worker's memory and needs no application route, so the response can be a perfectly ordinary error page or a 200 from whatever handler happened to run next. Rate-based anomaly detection will not see it either.

Scale, for context

Exposure numbers from three independent scans, all taken around October 20:

  • VulnCheck: 2,608,039 internet-facing hosts running PHP. Over 700,000 run PHP 3.x through 7.3; roughly 400,000 run 7.4, 8.0 or 8.1; about 403,000 run a supported 8.2–8.5 branch.
  • Modat: 4.38 million internet-facing PHP services, of which 79% — about 3.47 million — are on a branch PHP no longer supports. PHP 7.4 alone accounts for 878,761, PHP 5.3 for 486,684, PHP 7.2 for 461,965.
  • Pantheon: more than 60,000 suspicious requests probing the vulnerability blocked at the platform edge.

The two scans disagree on absolute counts, which is normal for internet-wide fingerprinting, but they agree on the important ratio: the majority of exposed PHP is end-of-life and will never receive the fix. Every mainstream Linux distribution and every control panel that bundles PHP needs its own rebuild, which is why the tail will be long.

Detection that survives the patch

Because the exploit leaves no application trace, detection has to move down a layer. Four signals are worth deploying.

  1. Pre-script memory fatals. Alert on Allowed memory size of ... exhausted ... in Unknown on line 0 in PHP-FPM, Apache mod_php, php-cgi and lsphp logs. Correlate with the client address and the request method. This is the closest thing to a per-request signature the bug offers.
  2. Process lineage. A web worker spawning a shell is the post-exploitation event regardless of entry vector. php-fpm or httpd as the parent of /bin/sh, bash, curl, wget, python or perl should page someone. The FBI and NSA both recommended process monitoring for exactly this reason.
  3. File writes from the web user. A new .php file appearing under a document root, a temp directory, or an upload directory, owned by the web user, written by a process whose parent is the web server.
  4. Outbound connections from web workers. The worker has no legitimate reason to reach arbitrary internet addresses. Egress from php-fpm to a non-allowlisted destination is high signal.

For hosts you cannot instrument, AWS's fallback is to consider blocking multipart file uploads entirely. That is a heavy mitigation and it will break applications, but it is the one configuration change that closes the pre-script path for CVE-2026-109618, and it doubles as a stopgap for the upload-progress session gap discussed earlier in this series.

CrowdStrike's other configuration note is a ratio: keep memory_limit several times larger than post_max_size (its published guidance is 4:1). The OOM trigger needs a single accepted body to exhaust the request heap during parsing. If the heap is far larger than the largest body the server will accept, that arithmetic does not close.

The mitigation hierarchy

Ordered by how much they actually buy you:

  1. Upgrade to 8.5.12, 8.4.27, 8.3.36 or 8.2.35. Distro packages are not automatically current; check the build, not the version string in phpinfo(). CloudLinux has shipped ea-php52 through ea-php85 and its alt-php set, and other vendors have published their own rebuilds.
  2. Hunt from October 17, not October 21. The patch stops future exploitation. It does not remove an implant dropped on the 17th. Treat any pre-patch exposure as a potential compromise and look for persistence: modified PHP files, new cron entries, unexpected authorized_keys, and outbound beacons from the web tier.
  3. Instrument the pre-script layer. Application-level WAF rules and framework logging cannot see an exploit that never reaches the application.
  4. Assume the embargo leaked into your response timeline. If your runbook starts at "vendor advisory published", it started four days late.

The uncomfortable lesson from the AWS analysis is not about PHP. It is that the interval between patch availability and working exploit is now measured in hours, and that the earliest exploitation may precede the advisory entirely. For an internet-facing runtime in the path of every request, a 30-day remediation SLA is not a risk decision. It is an assumption that the attacker will wait.