Skip to content
Php2shell What We Observed And What To Do

Php2shell What We Observed And What To Do

ellio.tech • October 22, 2026

PHP2Shell is a pair of critical, unauthenticated RCE flaws in PHP core (CVE-2026-109618 and CVE-2026-109619), fixed on 21 October 2026. What the two bugs are, what to patch, and what the ELLIO Deception Network recorded on disclosure day, including what the attack tooling actually carried.

On 21 October 2026 at 14:00 UTC the PHP project released fixes for two remote code execution flaws in PHP itself, CVE-2026-109618 and CVE-2026-109619, nicknamed PHP2Shell after a leaked exploit tool. Both are rated CVSS 9.2, and both are reached while PHP parses a request, before any application code runs. An unpatched PHP server that accepts a POST is exposed with no login and no application bug needed. The first PHP2Shell request reached ELLIO sensors at 08:36 UTC, five and a half hours before the fix was public.

This post is being updated as the situation develops. New findings go in dated updates at the end; the text above stays as written.

CVE-2026-109618, RCE via OOM corruption . When PHP runs out of memory mid-request it jumps straight out of the allocator, which can leave request memory half initialized, and request shutdown then runs over that memory. An attacker controls when memory runs out through the multipart form parser: a body with no usable declared length, many headers inside a single part, empty file controls that do not count toward the upload limits, or deeply nested field names. No session and no particular field is involved. PHP confirms it as exploitable against Apache mod_php with default settings.

CVE-2026-109619, RCE via stale global state . A request that dies at the wrong moment, through a fatal error, a timeout or an out-of-memory condition, can leave state behind in the PHP worker. A later request handled by that same worker can then read that state after it has been freed. Session upload progress is one of the code paths that reads it, which is why the PHP_SESSION_UPLOAD_PROGRESS form field shows up in attempts. PHP validated this path against both mod_php and PHP-FPM.

Early coverage mostly attributed that field, and the advice to switch it off, to CVE-2026-109618. In PHP's advisories the upload progress path belongs to CVE-2026-109619 alone, and the CVE-2026-109618 advisory never mentions sessions. The distinction matters: the two bugs need different stopgaps. Both advisories are worth reading in full: GHSA-w8hw-w9fx-j79w and GHSA-6wmf-4rv5-p3rp .

Patch. The fixed releases are PHP 8.2.35 , 8.3.36 , 8.4.27 and 8.5.12 . PHP states that an update is the only complete fix for either bug. Patches exist only for the branches still supported, which means 8.2 and later. If you are running 8.1 or earlier, including every version of PHP 7 and PHP 5, there is no official fix for these two bugs and there will not be one. On those versions the remediation is a move to a supported branch. Plan that move this week and treat it as urgent.

For CVE-2026-109618, until you can patch, PHP's advisory puts memory_limit=-1 first, enforced so nothing can restore a finite limit, and every worker restarted afterwards. Do not substitute RLIMIT_AS or RLIMIT_DATA ; use container or cgroup memory limits instead.

For CVE-2026-109619, until you can patch, set session.upload_progress.enabled=0 and restart workers. On PHP 8.5 also set fatal_error_backtraces=0 . This closes the session path only and does nothing for CVE-2026-109618.

At the front end, block multipart/form-data to applications that accept no uploads. Where uploads are needed, cap the body size, the number of parts and the number of headers per part: a size cap on its own misses bodies that stay under post_max_size . Keep those caps after patching, because a patched PHP still terminates a worker that runs out of memory.

Observations from 21 October

The sequence of events, in UTC:

27 August: the reports reach PHP's security team.

25 September: the fix pull request opens, with the final commit on 18 October.

20 October: details and a working proof of concept leak, and PHP brings its release forward by a day.

21 October, 08:36: the first PHP2Shell request reaches ELLIO sensors, before the fix is public.

21 October, 14:00: PHP publishes the fixed releases and both advisories.

21 October, 16:05: the first requests shaped like the CVE-2026-109618 exploit arrive.

21 October, 20:40: a bot that sweeps for several other vulnerabilities adds PHP2Shell to its run.

The 08:36 request named CVE-2026-109618 in its user agent, php2shell-scan/109618 , while carrying the session upload progress field, which belongs to CVE-2026-109619: tooling built from the leaked material rather than from PHP's advisories. In nine minutes it sent 51 requests. The same family came back later under the user agents php2shell-109618 and php2shell-verify/1.0 , from addresses in three unrelated networks sharing one multipart boundary string.

At 16:05, two hours after the release, different tooling appeared, and it matches the out-of-memory bug rather than the session path. Its requests carry the amplification shapes PHP's advisory names: hundreds of empty file fields and field names nested dozens of levels deep, packed into bodies of a few hundred kilobytes, with the upload progress field in the same request. One address sent 164 of them in twenty minutes, the heaviest burst before the evening. It marks its own multipart boundaries with the string p2shell . That address has been in our data since December 2025 and was already classified malicious for earlier exploitation attempts.

Each body is padding plus 149 identical 288-byte parts, and every part is a hand-built array of twelve PHP value structures at a 16-byte stride, carrying PHP's own type and flag bytes: unused slots undefined, the object slot collectable, the string slot not. Getting those flags right means working from the PHP internals headers. Each part ends with a command for the target to run.

Every pointer in those structures is zero, no address appears anywhere in the 288 bytes, and nothing in them reaches the command text at the end. The tool has no stage that could learn an address, so if the bug handed that memory back as a live value, the first dereference would read address zero and the worker would crash. Every part still carries a command to run, so the intent is in the body; the structure that would reach it is unfinished.

One address sent two versions of the same tool six hours apart. The first carries a loader: fetch a binary from a hard-coded address into /tmp , make it executable and run it with the name of the vector as its argument, the convention Mirai-like loaders use. The second version carries no address at all: it is the proof of concept's default left in place, uname -a; id and a fixed canary string the operator can look for in the response. That is the ordinary way to confirm blind code execution before spending a payload on a host.

The second-stage binary is 179,632 bytes, ELF x86-64, statically linked and stripped, with almost nothing in its strings beyond C library text and no configuration in the clear. It did not appear in the public malware sources we could query, and the staging host it comes from has never reached our sensors, so a feed built from observed scanning alone would carry neither the host nor the file. Hashes are below.

The same form field also carries an older technique unrelated to these two CVEs: write PHP into the session file through session upload progress, then include it from a later request. We tag that separately.

Each wave of tooling matched whatever material was public at the time. The tool that arrived before the patch names CVE-2026-109618 in its user agent while sending the field that belongs to CVE-2026-109619, because the leaked proof of concept had folded the two together. Requests matching what the advisory actually describes for the out-of-memory bug, hundreds of empty file controls and field names nested dozens of levels deep, did not appear until two hours after PHP published. Nobody had to reverse engineer the patch: the mitigation list names the amplification vectors.

The heaviest run of the day came last. From 20:40 one address sent 140 requests in eighteen minutes, and the 20:00 and 21:00 hours carried 180 and 188 requests, the two busiest of the day. Earlier this month the same address exploited React2Shell and swept for exposed Docker and Spring Boot endpoints. That is the part worth planning for: a new unauthenticated RCE gets picked up by the bots that were already sweeping for everything else, and those run continuously.

user agents php2shell-scan/109618 , php2shell-109618 , php2shell-verify/1.0

multipart boundary strings beginning ----p2s , including ----p2shell

a POST whose multipart body contains a part named PHP_SESSION_UPLOAD_PROGRESS . Sites that use PHP's upload progress feature send this field in ordinary uploads too, so treat it as a signal, not a verdict

multipart POSTs built from hundreds of empty file fields, or with field names nested many levels deep

second-stage download , written to /tmp and run with the argument php

the progress field carrying system('curl , a call with no second stage behind it

the public proof of concept's default command uname -a; id; echo JACKALPOC , whose marker JACKALPOC means it was run unmodified

second-stage binary SHA-256 0c5486f06620e26e316f4b33f318b0d646d5e1133789331aa2fed012360d1486 , MD5 b61a098fc62a2c2696e196dca78da71a

request body SHA-256 43898a0a53bbd25d31ad9f5018ed3182be98a66109b22c9cab545a9fc447992a (session path tool) and 7bc41ed79a9ae909927a9e40701736460a8cb70fca16ce1510e58677aca764b6 (out-of-memory tool)

PHP workers exiting on memory errors in bursts

a PHP worker process starting a shell, an interpreter or a network tool

Find it in the ELLIO Platform

tag_id: "php2shell_exploit" returns the addresses observed sending PHP2Shell requests.

cve: "CVE-2026-109618" OR cve: "CVE-2026-109619" returns every address carrying a tag mapped to either bug.

http.user_agent: "*php2shell*" returns the tools that name themselves.

Results cover a rolling 90 days, and you can push matching addresses straight to a dynamic block list from platform.ellio.tech .

Update, 21 October 2026, 20:50 UTC: the second stage, and where it calls

The file that loader wanted to install is a Mirai variant compiled for 64-bit servers; Mirai itself was written for embedded devices. Four things identify it: its command parser uses Mirai's wire format, its attack option numbers match the leaked Mirai source value for value down to the gaps and oddities in that numbering, its dispatch table has the same shape, and it takes a tag from the command line and stores it the way Mirai does, which is why the request that plants it runs it as ./nfs php .

This build encrypts its entire configuration, 473 entries, with ChaCha20. Mirai and nearly every fork of it hide their strings behind a repeating single-byte scheme that a brute force breaks in seconds, so the usual shortcut turns up nothing here but C library error text. The key sits at a fixed address in the binary and the same keystream is reused for every entry, so recovering one entry recovers all of them: the key is 5a3f2e7b8c9d0a6e3b4f7c8a1d5c2b0d6f1a0c3e2d8e4a091c7e5a4d3c2b6a08 , the nonce is 4a1b00ff2d7c3a1200010305 , and the block counter starts at 1 for every string.

On a host that runs it, the bot ignores the usual terminal signals, moves to the filesystem root, overwrites its own command line and process name, then forks and detaches from the terminal. It takes its new name from a short list of ordinary service names, so a process listing tells you nothing, while the executable path still resolves and gives it away. It opens no listening port, only one long-lived outbound TCP connection to its command and control domain on one of fourteen high ports, picked at random per connection. The live channel does an X25519 key exchange and then carries ChaCha20-Poly1305 sealed records with sequence numbers, falling back to a single-byte XOR in both directions. Its first message profiles the machine: processor count, memory, architecture, hostname, and the tag naming the vulnerability used to plant it.

Then it waits for orders. What it can be told to do:

flood a target, with 22 working methods: raw-socket floods giving the operator control of individual IP and TCP header fields, UDP floods both raw and connected, an ICMP flood, a GRE over Ethernet flood, and connection-exhaustion attacks

run a layer 7 HTTP flood that rotates five browser user agents and appends cache-busting parameters

attack game servers specifically, with one method for Minecraft Java on its default port 25565 and another for FiveM, the multiplayer modification for Grand Theft Auto

spoof its source addresses to look like a chosen country, using 41 built-in per-country address tables plus a much larger dedicated table for China

open an interactive shell on a pseudo-terminal

hunt and kill other processes, picked by generic heuristics: binaries deleted from disk, binaries running out of temporary directories, suspicious memory mappings

replace itself on command, detecting the host architecture, checking that the target directory is writable and executable, and preserving the infection tag through the upgrade

The game-server flood shows itself in its own HTTP. It sends a duplicated header name, Accept: Accept:*/* , which no legitimate client produces, puts a third carriage return and line feed before the body, and sets a Content-Length two bytes short of what follows. Those are mistakes in the bot's code rather than its configuration, so they survive a rebuild, and the duplicated Accept header alone is a clean signature.

The shell makes every compromised host a full backdoor, directly available to the operator. The process killer targets the shape of malware, so legitimate software running from a temporary directory can be killed along with the competition.

Two results favor defenders. The bot cannot spread on its own: no telnet or SSH password guessing, no credential list anywhere in the decrypted configuration, no port scanning, no exploit scanner. It has no persistence either, writing no startup entry, no cron job and no service unit, so a reboot removes it. This is disposable infrastructure: it gets in through someone else's exploit tool, joins the pool, and is replaced on the push.

Minecraft and FiveM servers are the stock targets of the rent-an-attack market, so the game-server methods and the country-by-country source spoofing point at a bot built to sell capacity.

Indicators from the binary, with the operator's own domain names and strings reproduced exactly, crude wording included, because detection depends on matching them character for character.

command and control domain thunder.godofthunder.st , filling all eight configured slots

command and control address 134.199.154.64 , which the bot derives from the published 134.154.64.199

connects to one of fourteen high ports, chosen at random: 50217, 50341, 50488, 50613, 50792, 50904, 51127, 51289, 51456, 51603, 51871, 52134, 52398, 52645

second-stage and self-update host hackattackkaboom.botlesscucks.st at 117.55.202.115 , used as published

staging host that served the file 92.118.112.100 on AS215540

sample SHA-256 0c5486f06620e26e316f4b33f318b0d646d5e1133789331aa2fed012360d1486 , MD5 b61a098fc62a2c2696e196dca78da71a , 179,632 bytes, statically linked and stripped

renames its process to one of telnetd, udhcpc, upnpd, watchdog, syslogd, klogd, uhttpd, uchttpd

prints one line to standard output before detaching, from a fixed set that includes im in the matrix and big brother is watching

writes a one-byte probe to /dev/shm/.cache.so and removes it, when it cannot create an anonymous in-memory file

falls back to eighteen hard-coded public DNS resolvers when normal resolution fails

The binary numbers its flood methods and does not name them; the names above describe what each one does, read from socket setup and protocol numbers.

thunder.godofthunder.st answers 134.154.64.199 with a 300 second time to live, identically from every public resolver we asked. That address belongs to California State University East Bay, and the bot never connects to it.

Every address the bot resolves goes through a transform first: a.b.c.d becomes a.d.b.c , first octet fixed, the other three rotated. 134.154.64.199 becomes 134.199.154.64 , a DigitalOcean host, and that is the command and control server this build talks to. Fixing the first octet is a deliberate touch, leaving the published address looking plausibly related to the live one.

We connected to 134.199.154.64 on all fourteen ports the binary carries, and all fourteen accepted: connect, observe, close, with no data sent in either direction. Ten ports outside the set refuse on that host, 50216 and 50218 either side of the open 50217, and all twenty-three other permutations of the published address return zero of fourteen, the published address and the halves swap result 64.199.134.154 among them.

The transform applies to the command and control connection alone. The self-update path takes the address DNS returns: hackattackkaboom.botlesscucks.st publishes 117.55.202.115 , and the bot connects there as published, so blocking that address works. Blocking the published address of the C2 domain stops none of this bot's traffic. That address belongs to a university, so the rule only blocks their legitimate traffic.

45.198.224.169 , the address that sent the Mirai loader to our sensors at 14:38 today, sits in a /24 that has sent our sensors over 1.4 million scanning sessions: the operator's delivery and scanning infrastructure.

Nokia Deepfield documented this family in May 2026 under the name potassium, and their report is what let us recognize it. The cryptography matches theirs exactly, which makes this the same codebase and the same crew:

the ChaCha20 configuration key and nonce, byte for byte

the single-byte traffic key 0xED

the bot seed 14861879

domains registered through Istanco on the same nameserver pair

Four things have changed since May:

Decoy recovery. Their report has the bot swapping the two 16-bit halves of the resolved address before connecting, so A.B.C.D becomes C.D.A.B . This build holds the first octet and rotates the other three.

The command and control channel. Their report describes C2 traffic obfuscated with the single static XOR byte. Here that XOR is the fallback: the live channel does an X25519 key exchange and then carries ChaCha20-Poly1305 sealed records with sequence numbers.

The Ethereum fallback, which appears nowhere in their research.

The campaign. Their report covers vitacoco , iambig , havanagila and botlesscucks ; godofthunder.st is absent from it.

The seed 14861879 is the same in both readings, with ten random digits appended. Their report has that result identifying the bot to its C2; in this build it makes a spoofed FiveM game-client identifier inside attack traffic over UDP, and never appears in a command and control message.

godofthunder.st was registered on 12 October, nine days before the attacks, and neither it nor the binary we pulled appears in any public source we checked.

Two entries in the configuration are dead, and no code in this build reads either. One is hik.gettingheadfromduckysmom.st . The other is an Ethereum Name Service block alongside the domains: two blockchain names, a set of public Ethereum endpoints, and the shape of a lookup that would read command and control addresses out of a name's text records. C2 addresses published on a public blockchain would be awkward to take down. Both names are newer than the domain, registered on 13 October, around 21 hours after godofthunder.st . One carries a live text record holding an address, rewritten four times during a two-hour session on 14 October, and the address it holds today sits in a range that is not announced to the internet at all, so the fallback would not work yet. One of the addresses in those records is in a United States Department of Defense range. The same name carries a second record, which says: woah woah. there is nothing to see here! please go away or else i will call the police.

What this means for blocking, the opposite of the usual advice:

Block by address. The bot carries eighteen hard-coded public resolvers, so a DNS block, an RPZ or a local sinkhole never sees the query.

Restrict outbound port 53 to your own resolvers, which brings the bot's lookups back into view. The two controls cover each other's gap: it ignores your resolver, and its transform hides the address that resolving the domain would give you.

Block 134.199.154.64 and the update host at 117.55.202.115 . Resolving the C2 domain and blocking the answer adds a university's address to your blocklist and stops none of this bot's traffic. Traffic to 134.199.154.64 on the fourteen configured ports is a high-confidence infection signal.

Update, 21 October 2026, 23:00 UTC: inside the command channel

The single static byte that published research describes as this family's C2 encryption is only the fallback here. The live channel runs an X25519 key exchange and then carries ChaCha20-Poly1305 sealed records, each with a sequence number.

The bot opens with 96 bytes: a fixed 19-byte preamble, a tag byte, its own public key, twelve zero bytes, and the registration sealed inside. The server answers with exactly 33 bytes.

The bot never checks who it is talking to: its whole test on that is that it runs longer than 32 bytes and begins with the right tag byte. Anything that answers that way completes the handshake and the bot takes orders from it, which makes a defensive sinkhole in front of this channel practical.

The preamble and the tag are built by code rather than read from the encrypted configuration, so they survive domain, port and key changes and are the durable thing to detect on. The shell marker inside the channel comes from the configuration and will differ in the build.

The registration is the whole of what the operator learns when a machine joins.

Victims are told apart by source address alone. The bot builds a fuller profile of the host, hostname and user included, and sends it only when the operator opens an interactive shell.

After the handshake every message is a record with a five-byte header, and the keepalive payload is a fixed length, so an idle bot emits exactly 39 bytes every 90 seconds.

The self-update fetches a replacement binary over plain HTTP with no signature and no integrity check, then makes it executable and runs it. Whoever controls that path controls every bot that takes the update.

Update, 21 October 2026, 23:50 UTC: how fast the tooling moved

One address went from a three-mode acceptance probe to a working exploit in 48 minutes, and to an installed foothold 81 minutes after that first probe. The same object-injection toolkit turned up on a second rented host twenty minutes after the first, and another address swept variants for two hours and forty minutes across 115 requests and five boundary strings.

The payloads are one published proof of concept, brought to our attention by the user Reelix. Its default command, uname -a; id; echo JACKALPOC , appears 149 times in our 20:33 capture, once per part. The forged-structure body is byte-identical at 14:38, 20:33 and 22:43 across at least four hosts, and the 14:38 copy is that proof of concept with the default command swapped for a Mirai loader fetch. That swap is the whole distance between published code and a weaponized attack, and counting payload shapes overcounts the actors behind them.

Eight requests, between 23:08 and 23:16:

two probes that fix a session identifier and test whether the server takes it from a cookie or from the form

four requests asking one question each the host and its web directory

one request writing a small PHP shell into that directory

one listing of three likely paths, to confirm it arrived

The pacing reads as a script: four commands in seventy-three seconds, and the verification twenty-six seconds after the write. All of this went through the session route, PHP's upload progress path. The same address had taken the memory bug earlier in the same run, so one operator used both halves of PHP2Shell.

Some of the traffic counted here is that older session-file technique using the same feature rather than an attempt at either new bug. A single request does not always separate the two, because both carry the same field.

The commands are base64 encoded and the wrapper is not, so a filter matching the shell body or the command text sees nothing while system(base64_decode( stays visible on the wire. The shell it writes takes its orders from a request parameter and fetches nothing.

Bodies ran a few hundred bytes early in the day; through the out-of-memory wave the median request body was 10 megabytes for two hours, with 287 bodies landing exactly on 10 MiB or one byte over, a chosen trigger value. The largest was 37.4 megabytes at 22:45, and after 23:00 nothing topped 77 kilobytes. The two paths pull in opposite directions: the memory bug needs volume, the session path needs a few hundred bytes.

Update, 22 October 2026, 03:10 UTC: a second way to exhaust PHP's multipart parser

At 02:47 UTC a single address, 152.233.51.47 , sent large bodies to four sensors over two minutes. They reach the same parser as the out-of-memory wave by a different route: the number of header lines it has to walk.

Each body carries 1,005 parts, and 1,001 of them hold one byte each. One part holds the rest, and its content is the header line X:a repeated 1,663,221 times in the smaller body and 1,872,936 times in the larger, 99 percent of the body in both. Every repeat is a line the parser reads as a part header.

The two bodies are 8,388,608 and 9,437,183 bytes, two to the twenty-third exactly and nine mebibytes less one byte. PHP's documented default for post_max_size is 8M, which is why those two sizes stand out. A third captured copy is the 8 MiB body cut short in transit.

The session stub rides along: PHPSESSID is a single null byte, the upload progress field holds the string pwn , and a one-byte file part sits beside them. Nothing in either body reaches outward: no URL, no shell command, no code.

The checklist above caps the number of parts and the number of headers per part alongside the body size. These two bodies are the clearest reason to set all three.

Update, 22 October 2026, 05:00 UTC: the requests now carry working malware

Between 03:46 and 04:22 UTC two addresses attached working malware to upload progress requests: a DDoS booter and an IIS web shell from the first, a Go remote access agent from the second.

At 03:46 UTC, 146.70.202.189 began posting to /upload.php on a sensor. Forty-two seconds later it asked for /1.aspx back, and its last upload followed two seconds after that.

Two payloads went to that endpoint. One is a Linux executable named bot.x86_64 , 182,739 bytes and 99.8 percent of the body that carried it. The other is the ASP.NET page 1.aspx , sent twice. The pair reads as a spray across platforms.

The executable is a DDoS booter, built for x86-64 against uClibc and statically linked, with its symbols intact: attacks.c and hmac.c are named inside it. Its flood methods cover OVH TCP and UDP, the Valve Source Engine, Discord and FiveM alongside plain UDP, TCP, SYN and HTTP GET, and it tracks a maximum user count, attacks allowed per user and a thread count, the accounting of a rented service. /bin/sh is present, so it can run what the C2 sends.

The only dotted quad in the file is its C2, 150.251.112.208 , written in the clear twenty-three bytes before the format string Connecting to C2: %s:%d , with the port beside it as a compiled-in integer. The bot authenticates with HMAC-SHA256 over a secret shipped in the same file, kZPUbHYdNYRDXBjbR6SeWvUfvD5Sc80wMrC8XGa2Z65= , and its game flood template carries a Steam account id, 76561198804806015 . The file is sha256 7133612558a5d0f257ca6bdb4f765453af9465bc5298acdcf33b5761b8c59aac .

The web shell is an IIS file manager written in Chinese, signed Code by Bin with author sites rootkit.net.cn and z-cg.com . Its password is the md5 of admin . It is sha256 17758cf05e3376f8448dff926564e038b763b4a46668455e2ccb5ac0839e629c .

The loader earlier in this post hides its configuration under ChaCha20 and stands a decoy address in front of its real C2. This file encrypts nothing, names its C2 in the clear and carries none of the godofthunder names, which reads as a different operator.

At 04:19 UTC a second address, 185.5.70.233 , started on the same endpoint and worked it until 04:22. The same thirty bytes of PHP, , went up four times as shell.php , shell.phtml , shell.php5 and shell.php.jpg , declared in turn as text/plain , image/jpeg and application/octet-stream .

Each upload named its own test in the upload progress field: shell_upload , phtml_test , php5_test and dbl_test .

Then came a 6,951,072 byte file named test_beacon , a Go program built with go1.27.1 for linux amd64, statically linked and sent twice a minute apart.

Its symbols are intact and describe a remote access agent. main.TaskProcess dispatches the tasks, msgpack carries them, and main.CreateInfo builds a host profile through gopsutil. The tasks are a shell in taskShell , an interactive terminal in jobTerminal over creack/pty , screen capture in taskScreenshot through kbinani/screenshot , file transfer in taskUpload , traffic forwarding in jobTunnel and TunnelController , and taskExecBof , which loads beacon object files in memory, the technique Cobalt Strike and Sliver use.

It shares nothing with the booter or with the loader earlier in this post: no godofthunder or botlesscucks name, no 150.251.112.208 , and nothing of the booter's HMAC secret.

No address is written into it. The C2 is supplied when the program runs, so the indicator is the file: sha256 dd98bf48899aa385035352688357742c73ddc5e47ec4ecc7d7b74c53c2e3ca3d .

Update, 22 October 2026, 06:00 UTC: commodity scanners arrive within a day of disclosure

Eight addresses sent upload progress requests during the 05:00 UTC hour, more separate senders than any other hour of the day. Six of them, on different networks, moved a few hundred bytes each, and their bodies follow a handful of fixed templates.

At 05:18 UTC, 122.97.242.167 on China Unicom posted a body with the boundary --nucleiBoundary , the progress value nuclei-probe , and a one byte file named a.txt . At 05:56, 192.140.154.1 on Excitel in India sent a second template of the same family: the boundary, the progress value and the file name all carry one random token, the session id carries a second one behind nuclei/ , and the file holds a single byte. That address rotated a different user agent through every request and tried / , /app/upload.php and /api/endpoint.php .

At 05:32, 47.238.232.123 in Alibaba's Hong Kong range sent twelve requests across six sensors under the user agent Mozilla/5.0 (php-session-startup-rce-poc) . The body carries the boundary ------atk , the progress value POC_79fe5e4f and a five byte file named a.txt .

A fourth tool has been running since 15:51 UTC yesterday, recognizable by a boundary of ------p2s and eight hex digits. Today it writes a canary into the progress field, P2SMARK or P2SMK followed by random letters, from 31.182.51.55 at 04:42, 98.80.11.91 at 04:43 and 200.119.44.50 at 05:16. At 05:03, 45.136.71.89 sent that same shape with a command in the field instead: system(base64_decode( wrapping echo PHP2SHELL_OK; id; whoami .

Each of these bodies asks one question, whether the server honors the field, and carries a token its sender can look for in the answer. That canary is what marks a scanner. The other two senders in the hour ran the other way. 34.136.122.211 sent ten bodies over a megabyte in under three minutes, six of them 15,657,436 bytes and four of them 12,655,437 . The constant across all of it is the field in a multipart body.

Update, 22 October 2026, 08:00 UTC: one sender rotates five bodies across three overload techniques

Between 04:28 and 05:32 UTC, 77.237.246.73 , an address at Contabo, sent 56 upload progress requests to two sensors, cycling through five fixed bodies. The five cover all three ways to overload the parser: one enormous field, a thousand parts, and a flood of header lines. The largest, at 25,281,929 bytes, is the biggest body of the day.

That body carries its bulk in one field. The boundary is SSSSSSSSSSSSSSSSS , and it declares 1,005 parts, 1,001 of them decoy fields of 39 bytes each. The part named big holds one contiguous run of 25,165,824 B characters, exactly 24 mebibytes and 99.54 percent of the body. Beside it sit a PHPSESSID of one null byte, a progress value of ppp , and a file part named a.txt holding 64 A characters.

The 68,456 byte body works by part count instead. The boundary is BBBBBBBBBB , there are 1,000 parts, and 998 of them are array fields named a0000[0] through a0997[0] carrying one byte each. Its PHPSESSID is leaksessionid01 .

Two more bodies make the parser walk header lines. The 1,416,690 byte body repeats X: a 230,000 times, 97.4 percent of the body. The 2,583,393 byte body repeats the same line 427,458 times inside the MIME headers of a file part named AAAA.txt . The 03:10 update covered that technique on its own, and here it shares a rotation with the other two.

The fifth body is 282 bytes, and it carries the link to the rest: a PHPSESSID of attacksessionid0001 beside a progress value of pwn , the same pair that 146.70.184.216 , 34.136.122.211 and 38.207.136.217 send behind three different boundaries. Four addresses on four networks, one generator.

None of the five bodies carries an executable, a URL or a command. The sender names itself in 59 of its sessions: Mozilla/5.0 (php2shell-test/2.0.0) .

Track PHP2Shell as it emerges

Every address in this post is tagged in the ELLIO Platform, and the tags keep updating as new sightings arrive.

Track PHP2Shell activity in the ELLIO Threat Intelligence Platform

No credit card required.

A group of researchers at ELLIO, transforming insights from mass exploitation and network reconnaissance into real-world cybersecurity defenses.

wp2shell in the Wild: From Patch to Mass Exploitation in Under 48 hours

The ELLIO Deception Network recorded more than 11,500 sessions across 700 sensors as traffic moved from probing to attempted database extraction, administrator creation, and a web-shell write. The first probe arrived the morning after WordPress published its fix.

React2Shell in the Wild: Payload Analysis, Active Campaigns, and IoCs

The ELLIO sensor network has been tracking active exploitation of CVE-2025-55182 (React2Shell) in the wild. Here’s what we’re seeing.

React2Shell Update: Custom Go L7 DDoS Botnet

A single delivery IP has been exploiting React2Shell to distribute malware from an open directory. 31 binaries including a custom Go L7 DDoS botnet with Cloudflare token forgery, two Mirai variants across 13 CPU architectures, and a C2 server.