Blog · Threat intel · · 10 min read · by huntback Research

PHP2Shell one week on: inside two operators' exploit labs

A week after PHP patched PHP2Shell, the most useful evidence is not another shell on a sensor. It is the operators' own notes, left open on their servers, saying exactly which PHP builds their chain breaks.

huntback runs decoys that record what attackers send, and it scans the infrastructure those attackers use, collecting what they leave exposed. For PHP2Shell that second source has been the richer one. Since 22 October we have recovered exploit kits, Docker labs, target lists and lab notebooks from seven hosts. This post covers the two most revealing workspaces, what they say about which systems are really at risk, and what our decoys saw at the same time.

We keep the evidence types apart. Decoy events show who sent what and when. Recovered files show what was exposed when we fetched it. Notes inside those files are the operators' own claims about their own labs, and we report them as claims.

21 Oct 08:41First PHP2Shell probes reach huntback decoys 21 Oct 14:00PHP security release (8.2.35, 8.3.36, 8.4.27, 8.5.12) 22 OctAgent lab notes: full RCE on ten builds, PHP 8.0 to 8.5 22 to 25 Octhuntback recovers PHP2Shell kits from five exposed hosts 22 to 23 OctA RedTail-style miner dropper arrives on huntback decoys 26 Oct 07:15huntback recovers the 8.4.26 workspace (1,563 files) 29 Oct 10:56huntback recovers the AI agent workspace (866 files)
Decoy captures (green), PHP's release (grey), operator lab notes (amber) and huntback loot (blue). Times are UTC.

Seven hosts in eight days

When a source probes our decoys, or when a file we recover names other infrastructure, huntback scans those hosts and collects what is exposed. For PHP2Shell that produced:

HostCollectedWhat was exposed
41.223.30.722 Oct 21:37A mirrored PHP2Shell proof-of-concept tree with its Dockerfile and notes
31.128.39.2423 Oct 21:34Check, exploit and race scripts for PHP2Shell
31.56.228.136:999924 Oct 23:45A 33-file research tree: Docker lab, race harness, root-cause notes, and an ASLR-bypass toolkit (CVE-2026-109618/tools/aslr_run.py, sup_poc.py)
217.160.132.82:900325 Oct 11:13An 89 KB exploit and a 66 KB scanner with usage and test documents
162.35.99.94:809025 Oct 21:48A Metabase command-execution poc.sql on one of the two addresses tied to confirmed shells
96.126.130.81:999926 Oct 07:151,563 files: an exploit tuned for PHP 8.4.26, a Docker driver and target triage (below)
209.99.184.246:809929 Oct 10:56866 files: an AI agent workspace with PHP2Shell lab notes and scripts (below)

The first five were in our 25 October report. The kits on the first two hosts predate the first confirmed shell in the wild, on 24 October, by one to two days. Several files repeat across hosts byte for byte, so these are shared kits passed between operators, not seven independent exploits.

The handoff file in the agent workspace also cites an evidence folder from a kit called 9bie-php2shell, on 22 October at 15:23 UTC. Ready-made exploit kits were changing hands within a day of the patch.

An AI agent's lab notebook: ten builds, full RCE

The workspace on 209.99.184.246:8099 is organised for coding agents: an AGENTS.md, per-round prompts, a turn plan, a watchdog and handoff files between agents. Its notes are written in Polish. Most of it concerns unrelated research against cryptocurrency and gaming platforms, which we do not describe here. Eight notes, a profile file and six scripts cover PHP2Shell.

The handoff to the agent called code2 sets the rules plainly: local Docker only, no external targets, and a strict definition of success. A first-stage heap leak or an "armed" upload does not count. Only a file containing the output of id does.

Our summary of the handoff rule: "full proof" means a file holding uid=... from running id; stage 1, "Armed" or a cookie leak is not proof.

The status file, timestamped 22 October 17:40 UTC, reports seven builds with full RCE. An update appended the same day, after another seventeen work blocks, raises that to ten of thirteen and adds working recipes for 8.4.10, 8.1 and 8.0. Combined and translated:

PHP buildResult in the agent's notes
8.5.10, 8.5.11Full RCE, all three stages (heap leak, libphp leak, fake reference to zif_system)
8.4.20, 8.4.25, 8.4.26Full RCE, all three stages
8.3.35Full RCE, all three stages
8.2.34Full RCE without the heap leak
8.4.10Full RCE in the later update
8.1, 8.0 (lab builds 8.1.34 and 8.0.30)Full RCE in the later update, without the heap leak, which was still listed as an open problem
8.4.4, 8.4.5Leaks work; the final stage never landed, described as a structural lock in the allocator
8.4.27 (fixed)Negative control: the chain failed at the first stage

Three details stand out.

  • Exact offsets per build. Each working row records the build-specific address of zif_system and the hash table offsets it needs. A chain this precise suits stock distribution packages; a custom or hardened build changes the numbers until someone recalibrates.
  • The agent separated real proof from false positives. Its lab file records that the "armed" monitor also fired on a fixed 8.4.27 build, and marks that as a false positive not to be trusted. Defenders should apply the same rule to their own detections: an upload-progress request is attempt evidence, a web worker starting a shell is execution.
  • The mitigation was tested. With session.upload_progress.enabled = Off the lab could not arm the bug at all.

The patch-analysis notes compare the PHP source between 8.2.34 and 8.2.35 and between 8.4.26 and 8.4.27. The agent's reading is that the fixed builds reset session state at the start of a multipart upload, which removes the state the chain depends on.

PHP 8.0 and 8.1: a working chain and no patch

PHP's fixed releases cover 8.2 to 8.5. The 8.0 and 8.1 branches are out of support and got no PHP2Shell fix. An early coverage note in the same workspace records the chain failing on 8.0.30 and 8.1.34; by the end of 22 October the agent had a working recipe for both.

That matters most for appliances, which embed their own PHP and update it on the vendor's schedule rather than PHP's. PwnDefend reported on 30 October that around 20 to 25 Palo Alto firewalls may have been compromised through PHP2Shell, with at least one confirmed. We cannot link the operators behind those intrusions to this workspace. What the notes do show is that the oldest PHP branches were already exploitable in an operator's lab on 22 October. If a device embeds PHP 8.0 or 8.1, no PHP release fixes it. Only the vendor's update does, and until then its management interface should not be reachable from the internet.

A kit tuned for one build, pointed at real sites

The 1,563 files (about 1.2 GB) on 96.126.130.81:9999 are commented and labelled in Chinese. The PHP2Shell material is a small, focused set:

FileWhat it does
php2shell_gd.pyA check mode (passive fingerprint, single URL or a target list) and an exploit mode for "PHP 8.4.26 + Apache mod_php prefork": rotate workers through out-of-memory crashes, leak the Zend heap chunk base, leak the libphp base, then point a fake destructor at zif_system.
pure_8426_run.shDrives the same three phases against a local container named php84target, restarting it between phases.
php2shell-8426/exploit.py and a second exploit.pyTwo exploit versions; the 33 KB one is close in size to the variant on 31.56.228.136.
chain_a_session_lfi.pyA route that needs no memory corruption: inject PHP through an upload filename into the session file, then include it through a local file inclusion bug.
target_tri.py, check_8426_targets.pyReachability and fingerprint checks against four real sites the operator believed ran PHP 8.4.26, labelled as a university, a membership system and a property-auction service. We are not naming them.

PHP 8.4.26 on single-worker Apache mod_php is also what the vulnerable targets ran in the 24 October confirmed-RCE report. Prefork gives the chain a small, predictable set of worker processes to cycle through, which is the likely reason the kit is built around it.

We searched all 221 log and text files in the workspace for an id result and found none. This is preparation against named targets, not evidence of compromise.

What our decoys saw

huntback's decoys recorded 227 PHP2Shell requests from six sources between 21 and 28 October. The first two arrived at 08:41 and 08:44 UTC on 21 October, before the release, as we reported at the time. The heaviest source, 77.90.185.20, sent 106 requests carrying a RedTail-style miner dropper. The most recent, on 28 October, introduced itself as php2shell-range/1.0 (authorized security testing).

That is a modest volume for a critical PHP bug, and it fits what the operators' notes say: the reliable chain needs a known build, a known server model and many requests per target. It suits targeted use more than internet-wide spraying.

Palo Alto fingerprinting

Requests for PAN-OS management and GlobalProtect paths reach the decoy network every week:

Week startingRequests to PAN-OS and GlobalProtect pathsSource IPs
14 Sep395
21 Sep197
28 Sep13132
5 Oct9423
12 Oct6413
19 Oct16539
26 Oct (to 30 Oct)6927

Since 21 October the most requested paths were the GlobalProtect login.esp and prelogin.esp pages, /php/login.php on the management interface, a source map under /php/ztp_gate.php, and /sslmgr. That is fingerprinting: finding which hosts are Palo Alto devices and which expose management. The busiest week was the one PHP2Shell went public. None of these requests carried a PHP2Shell upload, which suggests the exploit was kept for devices already confirmed by this kind of survey.

Limits of this evidence

  • The uid=33 results come from the operators' own lab, as they report them. We have not reproduced them.
  • No file we recovered shows PHP2Shell code execution on a real, third-party system, and our decoys do not execute PHP.
  • Exposed tooling proves the tooling existed on that host when we fetched it. It does not prove any particular target was hit.

What defenders should do

  1. Patch to PHP 8.2.35, 8.3.36, 8.4.27 or 8.5.12. The operators' own negative control on 8.4.27 shows the fix stops the chain.
  2. Until then, set session.upload_progress.enabled = Off. Few applications need upload progress, and the lab could not arm the bug with it off.
  3. Hunt down PHP 8.0 and 8.1. They have no fix and a working chain exists. Upgrade them, and treat firewalls and other appliances with embedded PHP as needing the vendor's update. Take management interfaces off the internet now.
  4. Alert on execution, not on requests. A web server process (apache2, php-fpm, httpd) starting /bin/sh is the signal that matters. Large multipart POSTs carrying PHP_SESSION_UPLOAD_PROGRESS are worth logging, but they are attempts.
  5. Look for worker churn. The chain rotates workers by forcing out-of-memory crashes. A burst of PHP memory-limit errors and worker restarts from one source is an early sign.

Indicators

Machine-readable: download these 13 indicators as CSV (type, value, context; values are not defanged).

Machine-readable: download these indicators as CSV (type, value, context; values are not defanged).

IndicatorTypeContext
209.99.184.246IPExposed AI agent workspace with PHP2Shell lab notes and scripts (port 8099), collected 29 Oct
96.126.130.81IPExposed PHP 8.4.26 exploit workspace and target triage (port 9999), collected 26 Oct
31.56.228.136IPExposed PHP2Shell research tree with an ASLR-bypass toolkit (port 9999), collected 24 Oct
162.35.99.94IPAddress tied to confirmed shells; hosted poc.sql on port 8090
5e99d6023dce9bb18d845a37fcdbc7ea574fdb20ab8d888ec54c695382d46036SHA-256aslr_run.py, ASLR-bypass toolkit (31.56.228.136)
c1e81cfbaf7e4cccad7d7f43c1a624b791ba1b7f1d176e224455138604837b4cSHA-256php2shell-8426/exploit.py (96.126.130.81)
4a1502affa36d106d5374bf02cf777956eef651176ec8964b26ff90f103dd506SHA-256poc.sql, Metabase command-execution gadget (162.35.99.94)
ebe9237143e437d411f831353a8472b303d458d99744bd9d96cf6d5fce6cafd0SHA-256php2shell_gd.py, 8.4.26 mod_php prefork exploit (96.126.130.81)
eeda79fb43f8c76f723a7d76a599b027b71e2bb7c930b7e4ed2e0bff143210baSHA-256chain_a_session_lfi.py, session-file LFI route (96.126.130.81)
dfde81213a29835b91b4a7bf04c9210305f519e093d76d4edc75f8e6bb842fe8SHA-256exploit.py, 33 KB variant (96.126.130.81)
ea92568319637e1913e20d9ceb1e6d89315c2393f8eac3fb6a2975bc1a1f6927SHA-256exploit.py, 33 KB variant (31.56.228.136)
ef5a98d9eca5de3be9f167f0a5cd9d0e83017032389a7df0b4f847e01f05ecbcSHA-256sup_poc.py (31.56.228.136)
494760e86cc41ff3d79ec2289f09320e86c86dd84f5e15899c316d9c2b54217cSHA-256PHP2SHELL_FINAL_STATUS.md, lab result matrix (209.99.184.246)

These hashes identify files, not behaviour. A renamed or edited copy will not match, so use them to find related material rather than as detection rules.

Sources