Yesterday we wrote up CVE-2026-87902, the unauthenticated local file inclusion in WordPress page template resolution fixed in 7.1.2. That post covered the sink, the preconditions and the fix. This is the follow-up on what we are seeing hit our firewall.
Update, 23 September 2026: when this post first went up, every request we had seen was reconnaissance against harmless core files. That is no longer true. Attackers are now including pearcmd.php and using it to write PHP files to disk, and public scanning tooling for this CVE is in circulation. The Update section below covers what changed. The original first day analysis is kept underneath it as a record.
Patchstack customers are protected by a RapidMitigate rule. If you have not updated yet, WordPress 7.1.2 or the patched release on your branch remains the fix, and at this point it is urgent.
| CVE | CVE-2026-87902 |
| Vendor / Product | WordPress / WordPress Core |
| Vulnerability Type | Path Traversal leading to Local File Inclusion and conditional RCE |
| CVSS | 9.2 (Critical) |
| Exploitation Status | Active exploitation, including attempts to write files to disk |
| Authentication Required | No |
| Affected Versions | 4.7.0 to 7.1.1 |
| Fixed Version | 7.1.2, 7.0.6, 6.9.9, 6.8.10 and backports to 4.7.37 |
| First Observed Activity | 22 September 2026, 11:49 UTC |
✌️ Our users are protected from this vulnerability. Are yours?
Identify vulnerabilities in your plugins and get recommendations for fixes.
Request auditProtect your users, improve server health and earn additional revenue.
Patchstack for hostsUpdate, 23 September 2026: From Probing to Code Execution
The picture changed in under a day. Traffic against this CVE is now running at more than ten times the volume we saw on the first evening, it is reaching a far wider spread of sites, and the requests have moved through three clear stages.
Stage one: is the inclusion live
This is what the first day looked like and it is still the bulk of the traffic. The inclusion is pointed at an ordinary core file so the response tells the attacker whether the host is vulnerable. wp-links-opml.php is still the most common choice, with wp-includes/feed-rss2.php now appearing in volume alongside it, plus wp-cron.php, wp-includes/functions.php, wp-login.php and install.php.
Stage two: is pearcmd reachable
The second stage points the inclusion at PEAR’s pearcmd.php and appends an argument to the query string:
GET /?page_id=<valid page id>&pagename=templates%2f<traversal>usr%2flocal%2flib%2fphp%2fpearcmd&+config-show
This is the check we described yesterday as the step that had not shown up yet. It has now. When PHP runs with register_argc_argv enabled, the query string is exposed to the included script as $argv, so +config-show is handed to pearcmd as a command and the response confirms both that PEAR is present and that the argv trick works. It is the last thing an attacker needs to know before executing code.
Three install locations are being tried, which covers most distributions and container images:
/usr/local/lib/php/pearcmd.php/usr/share/php/pearcmd.php/usr/share/pear/pearcmd.php
Stage three: writing files to disk
The third stage swaps config-show for config-create, which pearcmd will happily use to write a file wherever it is told, with content the attacker controls:
GET /?page_id=<valid page id>&pagename=templates%2f<traversal>pearcmd&+config-create+<php payload>+/tmp/<filename>.php
That is arbitrary file write with attacker-controlled PHP content, which is code execution. The payloads we are seeing split roughly into two groups. Some write a harmless marker string, along the lines of CVE-2026-87902-POC-OK or an obfuscated LUCIFER-RCE-OK assembled from chr() calls, which is the behaviour of someone building a vulnerable host list. Others write a short tag that executes a shell command on access. The second group is not research.
The files are dropped into /tmp and /var/tmp. Filenames we have observed include:
wp-pear-rce-flag.phppoc87902.phpluci_<random>.phpzeta_<random>.php
Worth noting that a file in /tmp is not usually reachable over the web, so on its own this is a proof of execution rather than a persistent backdoor. It does mean the host is fully compromised from the attacker’s point of view, and the same primitive can write somewhere more useful.
It has been commoditised
Two user agents in the traffic tell their own story:
cve-2026-87902-poc/1.0nuclei-cve-2026-87902/1.0
A named Nuclei template means this is no longer a handful of operators working from the patch diff. It is in general circulation and anyone can point it at a host list. Most of the remaining traffic uses spoofed browser user agents, so the honest ones are the minority and you should not treat user agent as a filter.
Volume has also moved. The first evening was a slow trickle. Through the following day it climbed steadily and peaked around midday UTC on 23 September at by far the highest rate of the campaign so far, before easing off. It has not stopped.
The First Wave, 22 September
The analysis below was written on the first evening, when the traffic was still reconnaissance only. We are leaving it as it stands because the mechanics have not changed, only the targets.
The first attempts reached our firewall at 11:49 UTC on 22 September 2026, the same day 7.1.2 was published. The payloads match the exact encoding the patch addresses, so whoever built them was working from the diff rather than from an independent discovery.
Two details in the requests
Defanged, a first wave request has this shape. We are still not publishing a working one.
GET /?page_id=<valid page id>&pagename=templates%252f<traversal>wp-links-opml
The first detail is the encoding. WordPress runs the slug through its own sanitiser before the template candidate is built, and that sanitiser deliberately preserves escaped octets while rewriting literal dots and truncating at literal slashes. A plain ../../ payload does not survive it. A percent-encoded one does, and then get_page_template() decodes it. That is why the traversal always arrives encoded, and it is what makes %2e%2e such a low noise thing to search your logs for.
The second detail is the page_id parameter, which appears alongside pagename in almost every request. That is not padding. Without a page id that resolves to a real page, the query returns nothing, WordPress serves a 404, and the page template is never reached, so the vulnerable code never runs. Whoever wrote the first payloads understood the code path well enough to know the traversal alone is not sufficient. It also makes the pairing a good detection signal, since the two parameters rarely appear together in ordinary traffic.
Variations
The traffic is not a single fixed payload, so rules and log searches anchored too tightly on one string will miss things. What we have seen vary:
Traversal depth ranging from one to twelve levels, to cope with different theme and install layouts
Uppercase hex (%252F%252E%252E) as well as lowercase, and single as well as double encoding depending on where the payload sits
POST as well as GET, which matters because WordPress reads pagename from the POST body in preference to the query string. POST has since overtaken GET as the more common method
Requests sent to /index.php directly as well as to the site root
Nearly all of them lead with templates%2f rather than starting with a traversal sequence, because the payload needs to continue a real directory beginning with page- before it can climb out of the theme. page-templates is the obvious guess and it is the one being used. We have also seen template and a malformed page-templates prefix, so do not assume templates is the only one you will ever see.
Sources
The first evening came from a small cluster of addresses. It is now spread across a few hundred, so blocklisting individual sources is not a strategy. The heaviest talkers at the time of writing:
43.250.53.42
180.251.159.243
195.178.110.247
107.189.14.87
45.61.184.170
92.246.130.76
The file write attempts specifically come from a much smaller subset of those addresses, which is the usual pattern of a few operators acting on results that a much larger scanning population produced.
Detection and Mitigation
Updating is still the answer, and given stage three it is now urgent. WordPress backported the fix as far as 4.7.37, so every affected branch has a patched release and an older site can take it without a major version jump. If you want to work out how exposed you are before you get there, the preconditions are in our original write-up: whether your active theme has a top-level page- directory, and whether your PHP has register_argc_argv enabled.
If you cannot update immediately, rejecting traversal sequences in the pagename parameter is an effective stopgap. A real page slug never contains one, so this can be blocked without affecting normal traffic. Disabling register_argc_argv does not fix the inclusion but it does break the pearcmd chain, which is the difference between an information leak and code execution.
For hunting through existing logs, the highest signal indicators are:
A pagename parameter containing %2e%2e or %252e%252e, in the query string or the POST body
A pagename value beginning with templates%2f or another page- directory namepagename and page_id appearing together on the site root or /index.php
Any request containing pearcmd, +config-show or +config-create
The user agents cve-2026-87902-poc/1.0 and nuclei-cve-2026-87902/1.0
On the host itself, check /tmp and /var/tmp for unexpected .php files, including the names listed above. Their presence means a stage three attempt succeeded and the host should be treated as compromised, not merely as having been scanned.
In your access logs, OPML or RSS output returned from a normal page URL is the sign that a stage one probe worked. A 200 response carrying that content means the inclusion ran on your host, and you should treat it as a confirmed vulnerable window rather than a blocked attempt. Sites that were exposed before patching or mitigation should review historical logs on that basis.
We are continuing to monitor this and will update again if the payloads move on from /tmp to locations that survive a reboot or are reachable over the web.
Timeline
🤝 You can help us make the Internet a safer place
Streamline your disclosure process to fix vulnerabilities faster and comply with CRA.
Get started for freeProtect your users too! Improve server health and earn added revenue with proactive security.
Patchstack for hostsReport vulnerabilities to our gamified bug bounty program to earn monthly cash rewards.
Learn more

