Earlier today 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, because the issue was already being probed in the wild before the day was over.
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.
| 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 probing observed |
| 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, 17:44 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 hostsWhat We’re Seeing
The first probing attempts reached our logs at 17:44 UTC on 22 September 2026, less than five hours after 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. That turnaround is the main reason we are writing this up separately.
These Are Probes, Not Payload Delivery
This is worth being precise about. Every request we have seen so far points the inclusion at an ordinary WordPress core file:
wp-links-opml.phpwp-includes/functions.phpwp-cron.php
None of those give an attacker anything on their own. They are already part of a normal WordPress install and including them does not run attacker-controlled code. What they do give is a clear yes or no answer. wp-links-opml.php emits a distinctive OPML document, so if that comes back from a normal page URL the inclusion worked and the host is vulnerable. It is a cheap and reliable oracle.
So what we are looking at is the reconnaissance stage: someone building a list of which hosts are exploitable, not yet trying to execute anything. The step that actually matters, pointing the same primitive at something like pearcmd.php on a host with register_argc_argv enabled, has not shown up in our data yet. We would expect it to follow once the scanning results come back, and the targets are the thing to watch for change.
That gap is also the useful part for defenders. If you find these requests in your logs, they are probably a survey rather than a compromise. It is still worth checking what your server returned.
Two Details in the Requests
Defanged, a typical 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 double 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 every request we have seen is double encoded at the HTTP layer, and it is what makes %252e%252e such a low noise thing to search your logs for.
The second detail is the page_id parameter, which appears in every single request alongside pagename. 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 this 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 so far:
- Traversal depth ranging from three to seven levels, presumably to cope with different theme and install layouts
- Uppercase hex (
%252F%252E%252E) as well as lowercase - POST as well as GET, which matters because WordPress reads
pagenamefrom the POST body in preference to the query string - Requests sent to
/index.phpdirectly as well as to the site root
All of them lead with templates%252f 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. A theme with a differently named page- directory would need a different prefix, so do not assume templates is the only one you will ever see.
Sources
The activity came from a small cluster of source addresses hitting multiple protected sites, with most of the volume concentrated in two neighbouring IPv4 addresses and some IPv6 traffic alongside it:
169.58.48.193
169.58.48.195
2001:df1:e8c0::106b
Most requests carried a Go-http-client/1.1 user agent, with a smaller share using spoofed browser strings. Activity peaked in the first hour and tapered off after that, which is the usual shape for an opportunistic sweep through a prepared host list rather than a targeted campaign.
Detection and Mitigation
Updating is still the answer. 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.
For hunting through existing logs, the highest signal indicators are:
- A
pagenameparameter containing%252e%252e, in the query string or the POST body - A
pagenamevalue beginning withtemplates%252for anotherpage-directory name pagenameandpage_idappearing together on the site root or/index.php- OPML output, or any other unexpected core file output, returned from a normal page URL
That last one is the one that tells you whether a probe succeeded. A 200 response carrying OPML where a page should have been means the inclusion worked 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, and check for unexpected or modified files while they are there.
We are continuing to monitor this, in particular for the shift from core file probes to inclusion targets that actually do something. If that changes we will update.
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

