WordPress shipped its second security release in five days on September 22, and the flaw it closed was under attack before the day was out. Version 7.1.2 carries a single fix, described on the project’s version documentation as “an unauthenticated path traversal issue in page-template resolution leading to conditional remote code execution,” reported by Robert Ressl.
Patchstack recorded the first exploitation attempt at 11:49 UTC the same day and the first attempt to write a file to disk at 15:34 UTC. A public Nuclei scanning template was in circulation by September 23, and on September 25 CISA placed the flaw in its Known Exploited Vulnerabilities catalog with a federal deadline of September 28.
The word that matters in WordPress’s description is “conditional.” The bug lets an unauthenticated request make WordPress include a readable PHP file from outside the active theme, and the known route from there to attacker-controlled code execution needs two more things the site owner does not control and the hosting provider does: a PHP setting called register_argc_argv, and the presence of PEAR’s pearcmd.php. Patchstack’s assessment of how common that combination is: “That setting is on by default in the official PHP Docker images and in cPanel environments on PHP below 8.5, so ‘unusual configuration’ undersells how common it is.” Its conclusion: “Plenty of hosts line up.”
Key facts
- The preconditions: the traversal needs the active theme to carry a top-level directory beginning with
page-, and the known route to code execution adds two host-side conditions. - The origin: the payloads match the exact encoding the patch addresses, which Patchstack reads as attackers working from the published diff rather than from independent discovery.
- The spread: CrowdSec counted 30,813 source addresses by September 28 and 322,680 matching requests over five days, with distinct sources rising from 237 to 23,321.
- The deadline: CISA added the flaw on September 25 under Binding Operational Directive 26-04, giving federal civilian agencies until September 28.
| Identifier | CVE-2026-87902, advisory GHSA-7hp8-65ch-5whp |
|---|---|
| Class | CWE-98, improper control of filename for an include statement |
| Severity | CVSS 4.0 of 9.2 in the WordPress advisory, CVSS 3.1 of 8.1 in CISA’s addendum |
| Sink | get_page_template() in wp-includes/template.php, the only file the release revises |
| Affected | 4.7.0 up to and including 7.1.1, per Patchstack and CrowdSec |
| Fixed in | 7.1.2, plus 24 older branches from 7.0.6 down to 4.7.37 |
One Branch Was Validated. The Other Never Was.
The bug is a missing check, not a new class of attack. When WordPress resolves which theme file should render a page, it builds candidate filenames from two inputs. Patchstack’s write-up of the patch puts the asymmetry plainly: “The page template slug on the first line is passed through validate_file(), WordPress’s own traversal check, before it’s accepted. The candidate built from pagename never was.”
The candidate is assembled as page-{$pagename}.php, and because the value is URL-decoded on the way, an encoded ../ sequence becomes a real directory step. The request needs no account and no interaction from anyone who has one. It does need somewhere to start: the include path begins inside the active theme, so the page- prefix has to match a real directory before the .. steps can climb out. Patchstack says “legacy default themes and a number of popular third-party themes” carry one, and SecurityWeek reports WordPress naming Twenty Twelve, Twenty Fourteen, Neve, Hestia and Sydney.
What the traversal reaches is any readable PHP file on the server. Including a harmless one proves the bug, including a useful one runs code. Patchstack’s write-up continues: “the well-known candidate is PEAR’s pearcmd.php, which only becomes useful when PHP is running with register_argc_argv enabled.” With that setting on, query-string arguments become command-line arguments to the included script, and pearcmd’s config-createcommand writes a file whose contents and location the attacker chooses.
| What has to line up | Whose it is | Where to check |
|---|---|---|
A top-level theme directory beginning with page- | The site | Filesystem sweep of active themes |
register_argc_argv enabled | The host, per PHP handler and version | Per-version PHP configuration |
A readable pearcmd.php | The host | /usr/local/lib/php, /usr/share/php, /usr/share/pear |
WordPress shipped two changes, not one. The first applies the same validate_file() check the sibling branch already had. The second, _wp_is_template_path_allowed(), requires every resolved template path to sit inside the stylesheet directory, the template directory or theme-compat, whichever code path produced it. Patchstack reads that as deliberate: “A one-line validation fix would have been enough for the reported issue. Adding a check that every template path must resolve inside the stylesheet directory, the template directory, or theme-compat means the security team treated template resolution as a class of problem rather than a single bug.”
From Reconnaissance to File Writes in Under Four Hours
Patchstack’s telemetry, published on September 22 and updated the next day, describes three stages.
| Stage | First seen | What the request does |
|---|---|---|
| Reconnaissance | 11:49 UTC, September 22 | Includes an ordinary core file such as wp-links-opml.php or wp-includes/feed-rss2.php. A page URL returning OPML or an RSS feed is the tell. |
| Probing for PEAR | Same day | Points the include at pearcmd.php at three install paths with a +config-show argument, confirming both that PEAR is present and that the argv trick works. |
| Writing files | 15:34 UTC, September 22 | Swaps in +config-create to drop PHP files into /tmp and /var/tmp, named wp-pear-rce-flag.php, poc87902.php and randomized luci_ and zeta_ prefixes. |
The researchers split those payloads in two: some write a harmless marker, the behavior of someone building a list of vulnerable hosts, while others “write a short tag that executes a shell command on access. The second group is not research.”
What that amounts to is worth stating precisely, because the two firms tracking it describe it differently. CrowdSec writes that attackers “dropped web shell uploaders into /tmp/ after a successful chain.” The Patchstack write-up is more careful about what a file in that location is worth: “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.”
Patchstack infers from the encoding that the early payloads came from the published fix rather than from separate research. “The payloads match the exact encoding the patch addresses, so whoever built them was working from the diff rather than from an independent discovery,” it wrote. By September 23 traffic was “running at more than ten times the volume we saw on the first evening,” and a named Nuclei template meant, in its words, “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.”
| Sensor network | Window | Sources | Requests |
|---|---|---|---|
| CrowdSec | Requests September 23 to 27, address count as of September 28 | 30,813 addresses, rising from 237 on the first day to 23,321 on the 27th | 322,680, peaking at 124,154 on the 27th |
| Previdian | September 23 to 28 | 12 addresses in 10 countries | 1,274, peaking at 690 on the 27th |
The shape of the CrowdSec series matters more than its size. Requests per source fell from about 63 to about 5 while the number of sources grew a hundredfold, which is the signature of an exploit moving from a few operators into commodity scanners. By signal volume the United States accounts for 22 percent and Iran for 20 percent. Patchstack, counting a few hundred addresses when it published, drew the operational conclusion early: “blocklisting individual sources is not a strategy.”
Theme and Host Configuration Decide Whether It Reaches Code Execution
Patchstack’s guidance for anyone who cannot update immediately is two checks: “check whether your active theme has a top-level directory beginning with page-, and check whether your PHP has register_argc_argvenabled.” On a fleet those are filesystem and configuration sweeps a provider can run in bulk, and the third is looking for pearcmd.php at the three paths the attackers try. Removing PEAR where nothing needs it closes the known route without touching a single site.
For the access logs, the markers are:
%2e%2eor%252e%252ein thepagenameparameterpagenameandpage_idtogether, a pairing Patchstack notes is rare in ordinary traffic- Requests naming
pearcmd, and unexpected .php files in/tmpor/var/tmp - The self-identifying agents
cve-2026-87902-poc/1.0andnuclei-cve-2026-87902/1.0, though most traffic spoofs a browser, so the agent is not a filter
Traversal depth in the observed requests runs from one to twelve levels, and both cases of hex and both single and double encoding appear, so a rule anchored on a single string will miss traffic.
The update itself is the smaller problem, since security releases install automatically by default and the fix reaches every branch back to 4.7, so a site pinned to an old major line can take it without a migration. What stays exposed is the tail where the automatic update is disabled or cannot complete. CrowdSec’s examples are agency-managed sites pinned to a version, staging copies left online, containers built from an image that never updates itself, and hosts where the filesystem is read-only. A host that pushed 7.1.1 by hand on September 17 has to push again.
Pantheon’s release note is the template for the platform response: “Pantheon has pre-deployed platform-wide mitigations (virtual patching via our routing network) against external abuse of this vulnerability (CVE-2026-87902), and is actively monitoring those rules,” followed immediately by “However, customers need to update their sites as soon as possible.”
A WAF rule that blocks encoded traversal in pagename stops the request at the edge. It does not remove pearcmd, does not change the PHP setting, and does not help a site reached by a route the rule does not cover. For a provider that found /tmp PHP files this week, the order of work is to preserve the evidence, then patch, then read the access logs for the first two stages to see how far the attacker got.
The CNA portion of the CVE record carries the CWE classification and the affected range but no severity score. The 9.2 comes from WordPress’s advisory under CVSS 4.0 and the 8.1 from CISA’s addendum under CVSS 3.1, which rates attack complexity high where the WordPress figure rates it low. That is the same set of preconditions expressed differently under two versions of the scale, which CVSS 4.0 records as attack requirements while 3.1 has to fold them into complexity. CISA’s own decision record marks the flaw actively exploited but not automatable.
CrowdSec’s counts are from its September 28 report and Previdian’s from its public CVE page, both read on September 30, and neither is independently verified. A widely repeated figure of more than 350,000 exposed sites, attributed to Shadowserver, could not be traced to a Shadowserver publication and is not used here.
Sources
- WordPress 7.1.2 Release - WordPress.org
- Version 7.1.2 - WordPress.org Documentation
- WordPress 7.1.2 Security Release: Unauthenticated LFI to RCE - Patchstack
- CVE-2026-87902: Attackers Started Probing WordPress Sites Hours After the Patch - Patchstack
- Known Exploited Vulnerabilities Catalog - CISA
- GHSA-7hp8-65ch-5whp, unauthenticated path traversal in page-template resolution - WordPress security advisory
- CVE-2026-87902 record - CVE Program
- Critical WordPress Vulnerability Exploited Immediately After Disclosure - SecurityWeek
- CVE-2026-87902: 30,000 IPs Target WordPress in Five Days - CrowdSec
- CVE-2026-87902 Exploitation Observed - Previdian
- WordPress 7.1.2 Security Release now available - Pantheon