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

§ 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 under post_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

GroupSource IPsTechniqueFirst seen (UTC)
Session upload-progress5PHP_SESSION_UPLOAD_PROGRESS + nested arrays / serialized data2026-10-21 16:21
Memory-pressure bodies41–2 MB single-field or batch bodies (OOM strategy)2026-10-20 21:57
Table 1Nine addresses across GB, FI, IR, EE, IE, US, and BE. The memory-pressure group is attributed with lower confidence: it matches the documented OOM strategy but lacks the upload-progress trigger field.

§ 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.

http

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=""
...
Listing 1Fields 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)PortBody bytesNested fieldsArray depth
16:21:09809922123
16:21:09801,4483210
16:21:09802,8967396
16:21:09806,0929491
16:21:098011,58410562
16:21:098016,384+10581
Table 2A slice of the size sweep. A burst of upload-progress POSTs whose body sizes climb monotonically is the signature of this calibration.

§ 07. Variants and tooling

Five addresses sent the upload-progress technique in five recognisable dialects.

ActorCountryDialect
94.154.43.7GBNested-array OOM sweep; boundary ----p2shell<hex>; chunked; no UA
38.180.45.91EESerialized fragment |x|i:1; in the progress field, UA B8RAT/1.0
80.66.66.66FIProgress value reused as the upload filename (uga67qBl6ky1 → .txt)
217.60.242.76IRSingle minimal probe, progress value X78b5ae69c545d49e, file x.txt
87.192.237.19IEManual 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:

http

------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--
Listing 2The |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 p2shell in a Content-Type header. No legitimate traffic contains it.
  • Trigger field. The field name PHP_SESSION_UPLOAD_PROGRESS in 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, and memory_limit errors or worker crashes correlated with multipart POSTs.

§ 10. Indicators

IndicatorTypeContext
94.154.43.7IPLead actor (Storm Industries LLC, AS219502, GB); OOM size sweep
38.180.45.91IPSerialized-data variant (WAVECOM-AS, AS34702, EE); UA B8RAT/1.0
80.66.66.66IPFile-write variant (SOLDATOV-AS, AS209702, FI)
217.60.242.76IPMinimal probe (PLYM-AS, AS197974, IR)
87.192.237.19IPManual curl check (BRM-AS, AS8193, IE)
34.19.34.88IPMemory-pressure 1 MB body (Google Cloud, US); earliest, ~16 h pre-patch
34.76.232.95IPMemory-pressure 1 MB body (Google Cloud, BE)
34.76.218.47IPMemory-pressure 1 MB body (Google Cloud, BE)
194.42.196.44IP2 MB WP REST-batch memory body (Virtual Systems LLC, US)
p2shellstringLiteral substring in the lead tool's multipart boundary
B8RAT/1.0stringUser-Agent of the serialized-data variant
PHP_SESSION_UPLOAD_PROGRESSstringMultipart field name; the technique's trigger
po10nn060000_efc8bc7e3e52_000000000000ja4hHTTP fingerprint of the lead actor
po10nn150000_94b423c72d17_000000000000ja4hHTTP fingerprint of the fasthttp cluster
Table 4Source addresses span VPS, cloud, and anonymisation networks; investigate rather than block outright, as several are shared infrastructure.

§ 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

  1. 2026-10-17Amazon MadPot records the earliest known PHP2Shell exploitation attempt, four days before disclosure
  2. 2026-10-2021:57 UTC – earliest memory-pressure body on the fleet (1 MB single field) from a Google Cloud host
  3. 2026-10-2114:00 UTC – PHP ships 8.5.12, 8.4.27, 8.3.36, 8.2.35; advisories published
  4. 2026-10-2116:21 UTC – first session upload-progress attempts; the lead actor runs a two-minute size sweep
  5. 2026-10-2118:16–21:55 UTC – three more actors, each with a distinct payload dialect; one tags itself B8RAT/1.0
  6. 2026-10-2202:30 UTC – the lead actor repeats the sweep; a manual curl check follows at 10:06 UTC

Have similar exposure in your environment?

[email protected]+371 2256 5353