01 / 08Mirage field report – 005
PHP2Shell in the wild: the first 48 hours on a honeypot fleet
Internet-facing PHP (mod_php, PHP-FPM, php-cgi)
PHP2Shell (CVE-2026-109618 and CVE-2026-109619) is unauthenticated remote code execution in the PHP runtime, leaked before its patch on 21 October 2026. OffSeq's Mirage honeypot fleet captured the first forty-eight hours of exploitation: nine source addresses, two attack shapes, and a tool that names itself in its own HTTP boundary.
CVE-2026-10961810 min read
Contents
§ 01. Summary
PHP2Shell is two critical flaws in the PHP runtime, disclosed on 21 October 2026 after leaking ahead of schedule. CVE-2026-109618 is remote code execution via out-of-memory corruption; CVE-2026-109619 is a use-after-free from stale global state. Both are unauthenticated, reach the web worker before application code runs, and are rated CVSS 9.2. PHP 5.4.0 through 8.5.11 are affected; fixes shipped in 8.2.35, 8.3.36, 8.4.27, and 8.5.12.
OffSeq's Mirage fleet recorded the first forty-eight hours of exploitation. Nine source addresses in seven countries split into two groups: five sent the session upload-progress technique that both CVEs depend on, and four sent oversized memory-pressure bodies consistent with the documented out-of-memory strategy. The lead address, 94.154.43.7, sent 269 requests across a two-minute size sweep.
§ 02. The vulnerabilities
Both flaws abuse the same unauthenticated entry point: PHP's session.upload_progress feature, which is enabled by default and needs no call to session_start(). When a multipart POST contains a field named PHP_SESSION_UPLOAD_PROGRESS, PHP writes progress into the session during input parsing, before application code runs.
CVE-2026-109618 (OOM corruption). PHP uses longjmp() to abort a request on an out-of-memory error. Request memory is briefly reachable before it is fully initialised, so an allocation failure at that moment leaves it in an invalid state that the shutdown procedure then reads. Apache mod_php is most exposed because it runs shutdown unconditionally, even when startup failed. The advisory lists the ways a multipart request drives the OOM condition: a body with no usable declared Content-Length (bypassing the post_max_size check), a part with an unrestricted number of headers, empty file controls that evade max_file_uploads, and nested field names that multiply the generated array work.
CVE-2026-109619 (stale global state). PHP keeps per-request state (error, session, object) in the worker, which must be reset before request memory is freed. A first request can leave that state behind, and a second request on the same worker reads it before initialisation resets it, producing a use-after-free. Confirmed triggers include fatal errors, OOM, and session upload progress reaching session handling before normal init. On PHP 8.5 the fatal_error_backtraces path is the strongest demonstrated case. Setting error_reporting, using @, or disabling display_errors does not mitigate it.
§ 03. What the payloads do, and why there is no shellcode
PHP2Shell is not a buffer overflow, and the AAAA… and nested arrays carry no code. The CWE classes are use-after-free and uninitialised-pointer access, not out-of-bounds write. The attacker's bytes do one job: manipulate PHP's heap allocator.
- Exhaust memory. The 1 MB single field and the nested field names push the request toward
memory_limit. Nested names are efficient at it because their in-memory representation is far larger than the wire bytes, multiplying allocation work while staying underpost_max_size. - Force the failure. That makes an internal PHP allocation fail at a precise moment, while request memory is half-initialised (109618), or it leaves session state live after its memory is freed (109619).
- Corrupt and hijack. PHP then reads or branches on that memory during shutdown. By controlling the size, count, and timing of allocations, the attacker grooms the heap so the freed or uninitialised slot is reoccupied with data that redirects a function pointer, giving code execution.
The attacker controls heap layout, not the bytes written to a target address, so there is no return-address overwrite, no shellcode, and no command in the request. Turning the corruption into a shell takes heap grooming and follow-up requests a low-interaction decoy never completes; the fleet sees the trigger, not the takeover.
§ 04. What reached the fleet
| Group | Source IPs | Technique | First seen (UTC) |
|---|---|---|---|
| Session upload-progress | 5 | PHP_SESSION_UPLOAD_PROGRESS + nested arrays / serialized data | 2026-10-21 16:21 |
| Memory-pressure bodies | 4 | 1–2 MB single-field or batch bodies (OOM strategy) | 2026-10-20 21:57 |
§ 05. Session upload-progress attempts
The core technique is a multipart POST combining three elements: the PHP_SESSION_UPLOAD_PROGRESS trigger, a PHPSESSID value, and form fields whose names are deeply nested arrays. The nested names are one of the advisory's named OOM strategies; the lead actor also sends Transfer-Encoding: chunked with no usable Content-Length, a second documented strategy.
POST / HTTP/1.1
Host: <target>
Content-Type: multipart/form-data; boundary=----p2shell<24-hex>
Transfer-Encoding: chunked
------p2shell<24-hex>
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"
x
------p2shell<24-hex>
Content-Disposition: form-data; name="PHPSESSID"
<26-char session id>
------p2shell<24-hex>
Content-Disposition: form-data; name="f0[k0][k1][k2]...[k59]"; filename=""
Content-Type: application/octet-stream
------p2shell<24-hex>
Content-Disposition: form-data; name="f1[k0][k1][k2]...[k59]"; filename=""
...f0…f9 each carry about sixty nested array keys with empty values. The trigger field forces session handling mid-parse; the nested arrays and chunked encoding drive the out-of-memory condition.§ 06. The size sweep
In its 16:21 UTC run, 94.154.43.7 sent the same structure at growing sizes: from under one kilobyte to past sixteen, with nested fields rising from two to ten and array depth from roughly 120 to 590 bracket pairs. Each size is a distinct request, producing over two hundred unique bodies in two minutes. Successful OOM corruption depends on a memory threshold that varies per target, so the escalation is the exploit searching for it.
| Time (UTC) | Port | Body bytes | Nested fields | Array depth |
|---|---|---|---|---|
16:21:09 | 80 | 992 | 2 | 123 |
16:21:09 | 80 | 1,448 | 3 | 210 |
16:21:09 | 80 | 2,896 | 7 | 396 |
16:21:09 | 80 | 6,092 | 9 | 491 |
16:21:09 | 80 | 11,584 | 10 | 562 |
16:21:09 | 80 | 16,384+ | 10 | 581 |
§ 07. Variants and tooling
Five addresses sent the upload-progress technique in five recognisable dialects.
| Actor | Country | Dialect |
|---|---|---|
94.154.43.7 | GB | Nested-array OOM sweep; boundary ----p2shell<hex>; chunked; no UA |
38.180.45.91 | EE | Serialized fragment |x|i:1; in the progress field, UA B8RAT/1.0 |
80.66.66.66 | FI | Progress value reused as the upload filename (uga67qBl6ky1 → .txt) |
217.60.242.76 | IR | Single minimal probe, progress value X78b5ae69c545d49e, file x.txt |
87.192.237.19 | IE | Manual curl/8.20.0, boundary CHKBND, progress test, file t.txt |
The smaller requests are complete in capture. Each is only the trigger field and a one-byte dummy upload (Z), confirming no command rides in the request:
------b8p2s7Qx9
Content-Disposition: form-data; name="PHPSESSID"
96fb6c1c8cdf6f2b9bf28d616557383a
------b8p2s7Qx9
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"
|x|i:1;
------b8p2s7Qx9
Content-Disposition: form-data; name="f"; filename="a.txt"
Z
------b8p2s7Qx9--|x|i:1; value is a PHP serialized fragment pushed through the progress key, the session path of CVE-2026-109619, distinct from the lead actor's OOM approach.§ 08. The memory-pressure group
Three Google Cloud hosts (34.19.34.88, 34.76.232.95, 34.76.218.47) using the fasthttp client sent 1 MB multipart bodies, a single field named wafjunk filled with the letter A. A single oversized field is the simplest way to push a worker toward memory_limit, the precondition for OOM corruption; the field name suggests the padding also pads out a WAF. 34.19.34.88 fired at 2026-10-20 21:57 UTC, matching the pre-disclosure exploitation Amazon and Cloudflare reported. A fourth host (194.42.196.44) sent a 2 MB WordPress REST-batch body on 18 October. This group lacks the upload-progress trigger, so it is attributed to PHP2Shell only by shape.
§ 09. Detection
PHP2Shell lives in the request body and Content-Type; no decryption beyond what the server already parses is required. PHP's multipart parser is lenient (folded headers, missing colons, junk after quotes), so match on several signals, not one exact line.
- Boundary. The substring
p2shellin aContent-Typeheader. No legitimate traffic contains it. - Trigger field. The field name
PHP_SESSION_UPLOAD_PROGRESSin a multipart body. The name is configurable, so treat it as a strong but not sole indicator. - Nested-array names. Form field names with deeply nested array keys such as
f0[k0][k1][k2]…. - Size sweep. Bursts of upload-progress POSTs from one source whose body sizes climb monotonically.
- Oversized single field. Multipart bodies over a few hundred kilobytes carrying one field of repeated bytes.
- Host side. A web worker (
php-fpm,apache2,httpd) spawning a shell or downloader, andmemory_limiterrors or worker crashes correlated with multipart POSTs.
§ 10. Indicators
| Indicator | Type | Context |
|---|---|---|
94.154.43.7 | IP | Lead actor (Storm Industries LLC, AS219502, GB); OOM size sweep |
38.180.45.91 | IP | Serialized-data variant (WAVECOM-AS, AS34702, EE); UA B8RAT/1.0 |
80.66.66.66 | IP | File-write variant (SOLDATOV-AS, AS209702, FI) |
217.60.242.76 | IP | Minimal probe (PLYM-AS, AS197974, IR) |
87.192.237.19 | IP | Manual curl check (BRM-AS, AS8193, IE) |
34.19.34.88 | IP | Memory-pressure 1 MB body (Google Cloud, US); earliest, ~16 h pre-patch |
34.76.232.95 | IP | Memory-pressure 1 MB body (Google Cloud, BE) |
34.76.218.47 | IP | Memory-pressure 1 MB body (Google Cloud, BE) |
194.42.196.44 | IP | 2 MB WP REST-batch memory body (Virtual Systems LLC, US) |
p2shell | string | Literal substring in the lead tool's multipart boundary |
B8RAT/1.0 | string | User-Agent of the serialized-data variant |
PHP_SESSION_UPLOAD_PROGRESS | string | Multipart field name; the technique's trigger |
po10nn060000_efc8bc7e3e52_000000000000 | ja4h | HTTP fingerprint of the lead actor |
po10nn150000_94b423c72d17_000000000000 | ja4h | HTTP fingerprint of the fasthttp cluster |
§ 11. Remediation
Patch the PHP runtime: 8.5.12, 8.4.27, 8.3.36, or 8.2.35, with source backports for 7.4, 8.0, and 8.1. Code, plugin, and CMS updates do not address a flaw in the interpreter. Interim, per the PHP team: set session.upload_progress.enabled=0 and restart workers; on 8.5 set fatal_error_backtraces=0; set memory_limit=-1 through non-overridable config such as php_admin_value, lower post_max_size, and enforce a container or cgroup memory bound. Error-suppression settings do not help.
§ 12. Method and limits
Findings are from OffSeq's Mirage fleet over 20–22 October 2026. Requests are matched by content: the trigger field name, nested array keys, the p2shell boundary, and oversized single-field bodies. Payloads were decoded from captured request data; previews are capped (4 KB in the index, 16–64 KB raw), so the largest bodies were larger than shown, while the complete smaller requests carry the decisive evidence. The memory-pressure group matches the OOM strategy but lacks the upload-progress trigger, so its attribution is by shape only. Decoys are low-interaction and cannot be exploited, so this characterises tooling and sequence, not exploitation success.
§ 13. Prior research
PHP2Shell was found by Ryan Austin of Calif and, independently, by Wiz, both using AI-assisted auditing, and disclosed on 21 October 2026. PHP's two advisories (GHSA-w8hw-w9fx-j79w, GHSA-6wmf-4rv5-p3rp) document the mechanisms used above. Amazon's MadPot reported the earliest known exploitation on 17 October; Cloudflare observed pre-release exploitation and shipped WAF rules; Huntress and Imperva published mechanism explainers and host-side hunting guidance. Those cover the vulnerability and victim-side scanning. This report adds the attacker-side view from an independent sensor network: the enumerated infrastructure, the per-actor dialects, the live size sweep, and the self-identifying p2shell boundary.
§ 14. Observation timeline
- 2026-10-17Amazon MadPot records the earliest known PHP2Shell exploitation attempt, four days before disclosure
- 2026-10-2021:57 UTC – earliest memory-pressure body on the fleet (1 MB single field) from a Google Cloud host
- 2026-10-2114:00 UTC – PHP ships 8.5.12, 8.4.27, 8.3.36, 8.2.35; advisories published
- 2026-10-2116:21 UTC – first session upload-progress attempts; the lead actor runs a two-minute size sweep
- 2026-10-2118:16–21:55 UTC – three more actors, each with a distinct payload dialect; one tags itself B8RAT/1.0
- 2026-10-2202:30 UTC – the lead actor repeats the sweep; a manual curl check follows at 10:06 UTC