← Back to Feed CACHED · 2026-09-23 05:00:33 · CACHE_KEY CVE-2026-87902
CVE-2026-87902 · CWE-98 · Disclosed 2026-09-22

An unauthenticated attacker can make `get_page_template

ASSESSED — NOISGATE
Vendor
Reassessed
Verdict:
Do you agree?
01 · The Real Story

Like a hotel that checks your room key at the elevator but leaves the stairwell door propped open with a fire extinguisher

CVE-2026-87902 is a path-traversal-to-local-file-inclusion vulnerability in WordPress Core's page-template resolver (get_page_template() in wp-includes/template.php). When WordPress decides which template to render for a page, it builds a candidate filename from the pagename query parameter — page-{pagename}.php — and passes it to locate_template(). A neighboring code path applies validate_file() to block traversal, but the pagename branch does not. An attacker sends a double-URL-encoded traversal sequence (%252e%252e) that survives WordPress's slug sanitizer intact, then gets decoded by a late urldecode() call into ../, escaping the theme directory. Any readable .php file on the server can be included. If the server also has PEAR's pearcmd.php accessible and register_argc_argv=On in the PHP runtime, the attacker chains two requests to write and then execute a webshell — full unauthenticated RCE. Every WordPress version from 4.7.0 through 7.1.1 (a decade of releases across 23 branches) is affected. Fixed in 7.1.2 with backports to 4.7.37.

The GHSA advisory rates this CVSS 4.0: 9.2 (Critical) while a CVSS 3.1 evaluation produces 8.1 (High) — the gap is framework math, not disagreement. The CVSS 4.0 AT:P metric correctly signals that preconditions exist (the active theme must have a page-* directory; RCE further requires pearcmd.php and register_argc_argv), but the 9.2 top-line number overweights the worst-case RCE scenario that only materializes on a subset of deployments. The CVSS 3.1 AC:H captures this conditionality more faithfully. The LFI component alone — which needs only a qualifying theme — is reliably exploitable and serious (it can include core files, leak server-side behavior, and serve as a recon oracle), but it is not arbitrary code execution on every host. HIGH at 8.1 is the honest read: dangerous enough to demand immediate action, conditional enough that not every WordPress site is equally at risk.

"Unauth LFI in every WordPress before 7.1.2. Conditional RCE. Active probing 5 hours post-patch."
02 · The Attack Path

5 steps from start to impact.

STEP 01

Fingerprint WordPress version and theme

The attacker identifies a WordPress installation via standard fingerprinting (generator meta tag, wp-login.php, /wp-json/ REST endpoint). Version disclosure is on by default. The attacker also determines whether the active theme ships a page-* top-level directory — Twenty Twelve, Twenty Fourteen, Neve, Hestia, and Sydney all qualify. A simple request to /?page_id=2 confirms a published page exists.
Conditions required:
  • Internet access to the target
  • Target runs WordPress 4.7.0–7.1.1
Where this breaks in practice:
  • Version detection can be suppressed (rare in practice)
  • Theme identification may require additional probing
Detection/coverage: Standard web fingerprinting; no anomalous behavior at this stage.
STEP 02

Send double-encoded traversal payload

The attacker crafts a GET or POST request with page_id=<valid_id>&pagename=templates%252F%252E%252E%252F%252E%252E%252F%252E%252E%252F<target>. The %252e sequences survive WordPress's slug sanitizer (which strips literal dots but preserves percent-encoded octets). The late urldecode() in get_page_template() converts them to ../, producing page-templates/../../../<target>.php. Multiple PoCs are public: abraxas/CVE-2026-87902, pwnVader/CVE-2026-87902-PoC-pwnVader, ressl/cve-2026-87902-poc, and vulpecuna/CVE-2026-87902.
Conditions required:
  • Published page with known page_id
  • Active theme has a top-level page-* directory
Where this breaks in practice:
  • Theme must contain page-* directory — not universal across all themes
  • Some WAFs may detect double-encoded traversal sequences post-decode
Detection/coverage: WAF/IDS rules matching %252e%252e in the pagename parameter. Nuclei template from griisemine/cve-2026-87902-detection performs behavioral comparison across three requests to confirm inclusion.
STEP 03

Validate LFI with a benign oracle file

Before attempting RCE, attackers validate the inclusion by targeting core files that return distinctive output: wp-links-opml.php (returns OPML XML), wp-cron.php (returns empty 200), or wp-content/index.php (returns empty body — the stock 'Silence is golden' file). If the response differs from the normal page template output, the LFI is confirmed. This is exactly what Patchstack observed in the wild — the Sep 22 probes targeted benign validation oracles, not weaponized payloads.
Conditions required:
  • Target file exists and is readable by the PHP process
  • Step 2 payload successfully escapes the theme directory
Where this breaks in practice:
  • File must exist at the expected traversal depth (attackers try 3–7 levels)
Detection/coverage: Access logs showing OPML output or empty-body 200s on normal page URLs. Anomalous pagename parameter values containing encoded dots or slashes.
STEP 04

Include pearcmd.php to write a webshell

The attacker traverses to PEAR's pearcmd.php (commonly at /usr/share/php/pearcmd.php on Debian/Ubuntu or /usr/local/lib/php/pearcmd.php in Docker PHP images). When register_argc_argv=On, pearcmd.php reads $_SERVER['argv'] from the query string and interprets it as PEAR CLI commands. The attacker issues a config-create command that writes a PHP payload to a web-accessible directory (e.g., /var/www/html/shell.php). This is the well-known pearcmd-to-RCE technique documented by Orange Tsai and others.
Conditions required:
  • pearcmd.php exists and is readable
  • register_argc_argv=On in PHP runtime
  • Web-writable directory exists
Where this breaks in practice:
  • pearcmd.php is present by default in official PHP Docker images and cPanel but absent on many hardened/managed hosts
  • register_argc_argv is On by default in Docker PHP images and cPanel pre-8.5, but Off in most production php.ini configs
  • Managed WordPress hosts (WP Engine, Kinsta, Pantheon) typically strip PEAR entirely
Detection/coverage: File integrity monitoring on web-accessible directories. Write events to webroot by the PHP process. PEAR-specific command patterns in query strings.
STEP 05

Execute the webshell for arbitrary code execution

The attacker sends a second LFI request targeting the freshly written webshell, or accesses it directly via HTTP if it was written to a web-accessible path. The webshell executes as the www-data (or equivalent) service account. From here, the attacker can read wp-config.php for database credentials, pivot laterally, install persistent backdoors, or exfiltrate data. Note: this does NOT yield root — the blast radius is bounded by the web server's Unix permissions.
Conditions required:
  • Step 4 succeeded — webshell was written
  • Webshell path is accessible via LFI or direct HTTP
Where this breaks in practice:
  • SELinux/AppArmor policies may prevent PHP from writing outside designated directories
  • Read-only container filesystems block the write entirely
  • EDR/AV on the host may flag the webshell
Detection/coverage: Webshell detection via YARA rules, EDR behavioral analysis. Outbound connections from the web server process. Database credential usage from unexpected sources.
03 · Intelligence Metadata

The supporting signals.

In-the-Wild StatusActive reconnaissance probing confirmed. Patchstack observed the first probes at 17:44 UTC on Sep 22, 2026 — less than 5 hours after 7.1.2 was published. Payloads match the diff exactly (someone was working from the patch, not an independent discovery). Traffic from IPs 169.58.48.193, 169.58.48.195, and 2001:df1:e8c0::106b using Go-http-client/1.1. Currently reconnaissance/fingerprinting stage; full RCE delivery not yet observed in telemetry.
Proof-of-ConceptMultiple public PoCs available. abraxas/CVE-2026-87902, pwnVader/CVE-2026-87902-PoC, ressl/cve-2026-87902-poc (pinned vulnerable lab), vulpecuna/CVE-2026-87902. Working PoC confirmed by Hadrian. Sploitus indexes at least two weaponized exploits.
EPSS ScoreNot yet scored — CVE disclosed <24 hours ago (Sep 22, 2026). Given public PoCs, active probing, and WordPress's install base, expect rapid escalation to the 90th+ percentile once FIRST computes the initial score.
CISA KEV StatusNot listed as of Sep 23, 2026. Probing activity is reconnaissance, not confirmed exploitation with impact. KEV addition is plausible if exploitation escalates to webshell delivery.
CVSS VectorCVSS 4.0: CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N9.2 Critical (from GHSA). CVSS 3.1: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H8.1 High. The AT:P (CVSS 4.0) and AC:H (CVSS 3.1) both reflect the theme and server preconditions. Network-accessible, no auth, no user interaction.
Affected VersionsWordPress 4.7.0 through 7.1.1 — 23 version branches spanning a decade of releases. The LFI precondition (theme with page-* directory) is met by default themes Twenty Twelve and Twenty Fourteen, and popular third-party themes Neve, Hestia, and Sydney. The RCE precondition (pearcmd.php + register_argc_argv=On) is met by default in official PHP Docker images and cPanel environments on PHP below 8.5.
Fixed Versions7.1.2, 7.0.6, 6.9.9, 6.8.10, and backports through 4.7.37 (all 23 supported branches). The fix adds validate_file() to the pagename branch and _wp_is_template_path_allowed() using realpath() containment in locate_template().
Attack Surface / ExposureWordPress powers ~33–43% of websites (~37.3 million active sites per BuiltWith 2026). An estimated 88% of WordPress sites run outdated releases (WPScan). Even if only 30–40% of sites have a qualifying theme, the exploitable population is 10M+ hosts. Internet-facing by design — no Shodan/GreyNoise-specific data yet for this CVE, but WordPress exposure is essentially the public web.
Disclosure TimelineJul 20, 2026 — private report via HackerOne by Robert Ressl. Jul 21, 2026 — receipt acknowledged. Sep 15, 2026 — WordPress team requested attribution details. Sep 22, 2026 — public advisory and 7.1.2 release. Sep 22, 2026 17:44 UTC — first probing observed in the wild.
Reporting ResearcherRobert Ressl (ressl.ch). Responsible disclosure via HackerOne with 64-day coordinated timeline. All exploitation testing performed in isolated local labs using WordPress 7.0.2.
04 · The Call

Final Verdict
DOWNGRADED to HIGH (8.1/10)

Why this verdict

  • Unauthenticated, zero-click, network-reachable: No credentials, no user interaction — any internet-facing WordPress site in the affected version range is in the blast radius for the LFI component. This establishes a HIGH baseline before friction analysis.
  • Theme precondition narrows but does not eliminate the population: The page-* directory requirement is met by default themes (Twenty Twelve, Twenty Fourteen) and popular third-party themes (Neve, Hestia, Sydney), covering an estimated 30–40% of WordPress installations. This is a meaningful filter but still represents 10M+ exploitable hosts — not enough friction to pull below HIGH.
  • RCE is conditional, not universal: Full code execution requires pearcmd.php presence AND register_argc_argv=On — common in Docker-based and cPanel deployments but not the majority of production WordPress hosts. LFI alone is serious (includes arbitrary PHP files, serves as reconnaissance oracle, can chain with other server-side files) but is not equivalent to arbitrary command execution. This is the primary downward pressure from CRITICAL to HIGH.
  • Role multiplier: WordPress is a web CMS deployed on web/application servers. Compromise yields www-data shell at best — host-scoped, not domain/fleet/supply-chain scale. WordPress is *not* canonically in the high-value infrastructure catalog (not an IdP, hypervisor, PAM vault, backup system, CI/CD pipeline, or network edge appliance). The blast radius per-host is significant (database credentials via wp-config.php, persistent webshell, lateral movement springboard) but does not inherently cascade to domain takeover or fleet compromise. The high-value-role floor does not override: WordPress's canonical deployment role is line-of-business web presence, not identity or infrastructure control plane. No floor elevation applies.
  • Active probing and PoC availability compress the exploitation timeline: Multiple public PoCs and observed probing within 5 hours of patch release mean this is an imminent threat, not a theoretical one. This does not change the severity score (which measures impact and exploitability, not urgency) but triggers the noisgate exploitation override for mitigation deadlines.

Why not higher?

RCE requires multiple compounding preconditions beyond the base LFI — pearcmd.php must be present, register_argc_argv must be enabled, and a writable directory must exist. Each condition narrows the exploitable population. WordPress is a web CMS, not identity infrastructure, a hypervisor, or a PAM vault; compromise yields www-data privileges on a single host, not domain admin or fleet control. The CVSS 4.0 AT:P metric correctly reflects this conditionality — the 9.2 score overweights the RCE scenario that only materializes on a subset of deployments.

Why not lower?

The LFI component requires NO additional preconditions beyond a common theme layout — an attacker can reliably include arbitrary PHP files from any qualifying site without authentication. WordPress powers 37 million active sites; even a 30% exploitable fraction is 11 million hosts. Active probing within hours of disclosure, with multiple public weaponized PoCs, makes exploitation imminent rather than theoretical. Dropping below HIGH would be irresponsible given the unauthenticated attack surface and installed base.

05 · Compensating Control

What to do — in priority order.

  1. Deploy WAF rule blocking double-encoded traversal in pagename — Add a WAF rule (ModSecurity, Cloudflare, AWS WAF) that rejects requests where the pagename query parameter (GET or POST) contains %252e or %252f after URL decoding. This is the most targeted compensating control and does not affect legitimate traffic — no valid WordPress page slug contains percent-encoded dots. Given active probing and the noisgate exploitation override, deploy within hours, not 30 days. Test against your staging environment first to confirm no false positives on slug-based routing.
  2. Switch to a theme without a page-* directory — If patching is delayed, activate a theme that does not contain a top-level page-* directory (e.g., Twenty Twenty-One through Twenty Twenty-Six use different layouts). This eliminates the LFI precondition entirely. Verify by listing ls -d /path/to/themes/*/page-* — if nothing returns, the precondition is not met. Deploy within the noisgate exploitation override window (hours).
  3. Remove or restrict pearcmd.php — Delete /usr/share/php/pearcmd.php (or equivalent path) or make it unreadable by the web server user. This collapses the RCE chain entirely, even if the LFI succeeds. On Docker PHP images: rm /usr/local/lib/php/pearcmd.php. On cPanel: chmod 000 /usr/share/pear/pearcmd.php. Deploy within hours on internet-facing hosts.
  4. Disable register_argc_argv in php.ini — Set register_argc_argv = Off in the PHP configuration used by the web server (not just the CLI php.ini). Restart PHP-FPM or Apache. This prevents pearcmd.php from reading attacker-controlled arguments even if included. Verify with php -i | grep register_argc_argv from the web server context. Deploy within hours on internet-facing hosts.
  5. Monitor access logs for traversal indicators — Set up real-time log alerting for requests where the pagename parameter contains %252e, %252f, .., or references to pearcmd, wp-links-opml, or wp-cron in unexpected contexts. Patchstack documented specific IoC patterns: User-Agent Go-http-client/1.1, IPs 169.58.48.193 and 169.58.48.195. Alert on these immediately.
  6. Patch to WordPress 7.1.2 or branch backport — This is the definitive fix. Update via the WordPress dashboard (auto-update if enabled) or manually apply the branch-specific backport (7.0.6, 6.9.9, 6.8.10, down to 4.7.37). The patch adds validate_file() to the pagename branch and introduces _wp_is_template_path_allowed() with realpath() containment. Under the noisgate remediation SLA for HIGH, the deadline is 180 days, but given active probing, prioritize internet-facing instances this week.
What doesn't work
  • Rate limiting — the exploit is a single GET request per target. Rate limiting will not prevent a one-shot LFI/RCE chain. The attacker needs exactly two requests for the full RCE path (one to write the webshell, one to execute it).
  • Disabling XML-RPC or REST API — the attack vector is the standard front-end page request path (/?page_id=X&pagename=Y), not XML-RPC or the REST API. Disabling these endpoints provides no protection against this CVE.
  • WordPress security plugins that only inspect POST bodies — the primary attack works via GET query string parameters. Plugins like Wordfence or Sucuri may detect the traversal *if* they inspect URL parameters post-decode, but plugins that only hook into wp_loaded or later may fire after the template resolution has already occurred. Do not rely on plugin-level WAF alone — use a network-layer WAF upstream.
06 · Verification

Crowdsourced verification payload.

Run this script on the WordPress host (or any machine with read access to the webroot via mount/SSH). Invoke with bash cve-2026-87902-check.sh /var/www/html (adjust the path to your WordPress document root). No elevated privileges required — only read access to wp-includes/. The script also checks RCE preconditions (pearcmd.php, register_argc_argv) which may require broader read access.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# CVE-2026-87902 — WordPress Core LFI via page-template resolution
# Usage: bash cve-2026-87902-check.sh /var/www/html
# Exit: 0=PATCHED  1=VULNERABLE  2=UNKNOWN
set -uo pipefail

WP="${1:?Usage: $0 /path/to/wordpress}"
TMPL="$WP/wp-includes/template.php"
VERF="$WP/wp-includes/version.php"

if [ ! -f "$VERF" ]; then
  echo "UNKNOWN - $VERF not found. Is this a WordPress webroot?"
  exit 2
fi

wp_ver=$(awk -F"'" '/wp_version/{print $2;exit}' "$VERF")
echo "[*] WordPress version: $wp_ver"

if [ ! -f "$TMPL" ]; then
  echo "UNKNOWN - $TMPL not found."
  exit 2
fi

# Primary check: does template.php contain the fix?
if grep -q '_wp_is_template_path_allowed' "$TMPL" 2>/dev/null; then
  echo "PATCHED - template.php contains the CVE-2026-87902 fix (_wp_is_template_path_allowed)."
  exit 0
fi

echo "VULNERABLE - template.php lacks the CVE-2026-87902 fix (v$wp_ver)."
echo ""
echo "--- Precondition checks ---"

# Check 1: Theme has page-* directory (LFI precondition)
THEMES="$WP/wp-content/themes"
if [ -d "$THEMES" ]; then
  hits=$(find "$THEMES" -maxdepth 2 -type d -name 'page-*' 2>/dev/null)
  if [ -n "$hits" ]; then
    echo "  WARN: page-* directory found in themes - LFI precondition MET"
    echo "$hits" | sed 's/^/    /'
  else
    echo "  INFO: No page-* directory in themes - LFI precondition not met (lower risk)"
  fi
fi

# Check 2: pearcmd.php exists (RCE precondition)
pear=$(find / -name pearcmd.php -readable 2>/dev/null | head -1)
if [ -n "$pear" ]; then
  echo "  WARN: pearcmd.php found at $pear - RCE escalation path exists"
else
  echo "  INFO: pearcmd.php not found - RCE chain unlikely"
fi

# Check 3: register_argc_argv enabled (RCE precondition)
if command -v php >/dev/null 2>&1; then
  argc=$(php -r 'echo ini_get("register_argc_argv");' 2>/dev/null || true)
  if [ "$argc" = "1" ]; then
    echo "  WARN: register_argc_argv is ON - RCE precondition met"
  else
    echo "  INFO: register_argc_argv is OFF or unset"
  fi
else
  echo "  INFO: php binary not in PATH - cannot check register_argc_argv"
fi

exit 1
07 · Sources

Peer Review

What defenders are saying.

Submit a review attribution: handle + country only
0 flags selected · stored anonymously
Validation Results

Crowdsourced verification outputs.

Results submitted by users who ran the verification payload against their environment.