CTIPilot
← Back to the live brief
CRITICALCVE-2026-87902exploitedNATOA1vulnerabilitydeep dive · web-app-rce

CVE-2026-87902, WordPress Core: unauthenticated page-template path traversal to conditional remote code execution, weaponised within a day (CVSS4.0 9.2)

WordPress's fix for a pre-auth file-inclusion bug is already outrun by a public Nuclei template and confirmed file-write attempts

Defender actions

  • Patch every WordPress install (any version from 4.7.0 through 7.1.1) to its branch's fixed release now; where same-day patching is not possible, block pagename values containing percent-encoded traversal sequences at the WAF/reverse-proxy layer and disable register_argc_argv for web-facing PHP.
  • Check /tmp and /var/tmp on every WordPress host that was internet-reachable and unpatched since 2026-09-22 for unexpected .php files; their presence means a file-write attempt already succeeded and the host must be treated as compromised, not merely probed.

Analysis

CVE-2026-87902 is an unauthenticated path-traversal flaw in WordPress Core's page-template resolution that lets a remote attacker force get_page_template() to include a chosen, readable local PHP file from outside the active theme's directories (WordPress Security Team, 2026-09-22). The request pairs two public query variables, pagename and page_id, which WordPress accepts from an anonymous form POST (Robert Ressl, 2026-09-22); Patchstack's observed traffic shows the same fields also work over a GET request, with POST later overtaking GET as the more common method (Patchstack, 2026-09-23), with no account, cookie, session or nonce required (Robert Ressl, 2026-09-22). A valid page_id is what makes the request resolve to a real page at all: without one the query 404s and the vulnerable template-resolution code never runs, so the pairing is not padding but a load-bearing part of the chain (Patchstack, 2026-09-23). WordPress's own slug sanitiser preserves percent-encoded octets while rewriting literal dots and truncating at literal slashes (Patchstack, 2026-09-23), so a double-encoded traversal sequence reaches query processing with its percent-encoded octets still present, and the encoded separators are not treated as ordinary path separators at that point (Robert Ressl, 2026-09-22); get_page_template() then calls urldecode() on the surviving value, turning the encoded characters into a live ../ sequence at the moment the template candidate is built, and the loader that resolves the final path checks only that the target exists, is readable and carries the expected suffix; never that the resolved path stays inside an allowed theme directory (Robert Ressl, 2026-09-22). "The important distinction is between canonicalization and containment. Resolving a path with realpath() gives a normalized destination. It does not establish that the destination belongs to a directory the application intended to trust" (Robert Ressl, 2026-09-22).

Reaching code execution, not just file inclusion, needs two further conditions on top of the traversal itself: the active theme (parent or child) must contain a top-level directory whose name starts with page- (the advisory names the legacy Twenty Twelve and Twenty Fourteen themes and third-party themes Neve, Hestia and Sydney as examples) and a .php target file must exist on the server and be readable by the web server account (WordPress Security Team, 2026-09-22). The demonstrated route uses PHP's PEAR pearcmd.php entry point, present by default in the official PHP Docker image and in cPanel installs on PHP versions before 8.5 (WordPress Security Team, 2026-09-22), the discoverer's own lab used the wordpress:php8.3-apache tag specifically (Robert Ressl, 2026-09-22); with register_argc_argv enabled, the query string reaches the included script as $argv, letting an attacker issue pearcmd's config-create action to write an attacker-chosen PHP file to /tmp or /var/tmp (Patchstack, 2026-09-23), code execution with the privileges of the PHP or web-server account (Robert Ressl, 2026-09-22). WordPress shipped 7.1.2 for the 7.1 branch on 2026-09-22 and backported the fix to every other branch back to 4.7.37, so no affected site needs a forced major-version jump to patch (WordPress Security Team, 2026-09-22).

Exploitation moved fast. The first requests hit Patchstack's sensors at 11:49 UTC on 2026-09-22, the same day the patch shipped, using the exact encoding the fix addresses; evidence the payloads were built from the patch diff, not an independent rediscovery (Patchstack, 2026-09-23). What began as reconnaissance-only probing (pointing the inclusion at a harmless core file such as wp-links-opml.php to see if the host answers) escalated within a day into confirmed exploitation: "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" (Patchstack, 2026-09-23). Some observed payloads write a harmless marker string consistent with building a vulnerable-host list; others write a short tag that executes a shell command on access. Traffic is now spread across a few hundred source addresses and running at more than ten times the first evening's volume, and it has been commoditised: "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" (Patchstack, 2026-09-23).

Cited evidence

An unauthenticated attacker can make get_page_template() page-template resolution include a chosen readable local .php file outside the active theme directories. If relevant pre-conditions for both the server environment and the active theme are met, this can lead to RCE.

WordPress Security Team (GHSA-7hp8-65ch-5whp) 2026-09-22

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.

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.

Patchstack 2026-09-23

The important distinction is between canonicalization and containment. Resolving a path with realpath() gives a normalized destination. It does not establish that the destination belongs to a directory the application intended to trust.

Robert Ressl

Sources4

PROVENANCE

AI-generated · no human review · this permalink is the shareable record for the finding · verify operationally critical claims against the linked primary source.