← Back to Feed CACHED · 2026-10-24 03:56:59 · CACHE_KEY CVE-2026-87902
CVE-2026-87902 · CWE-98 · Disclosed 2026-09-22

WordPress Core get_page_template() unauthenticated path traversal to local PHP file inclusion

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

Someone left the theme folder's name on the office door, and now every lockpick on earth knows which key opens the server room

CVE-2026-87902 is a Local File Inclusion flaw in WordPress Core's get_page_template() function affecting every release from 4.7.0 through 7.1.1 — nearly a decade of code. An unauthenticated attacker sends a double-URL-encoded path-traversal payload in the pagename query parameter. WordPress's first sanitisation pass sees harmless percent-encoded text; a subsequent urldecode() call converts it into live ../ sequences, and locate_template() happily includes any readable .php file on the server — outside the theme directory, no login required. When the active theme ships a top-level directory whose name starts with page- (Twenty Twelve, Twenty Fourteen, Neve, Hestia, Sydney, and others) and PHP's register_argc_argv is On and PEAR's pearcmd.php is present, the attacker chains a second request to write an arbitrary PHP webshell and achieve full remote code execution.

The vendor tagged this HIGH / 8.1 with AC:H to reflect the RCE preconditions. That rating under-counts the real-world risk. The LFI itself is unconditional on any unpatched WordPress — no theme check, no PHP flag, no special config. More importantly, the RCE conditions are met *by default* in official PHP Docker images and cPanel environments running PHP < 8.5, which together cover a large fraction of containerised and shared-hosting WordPress installs. Within hours of the September 22 patch, automated exploitation began; by day five, CrowdSec counted 30,813 unique attacking IPs and 322,680 exploitation signals, and TechTimes reported 350,000+ compromised sites. CISA added the CVE to KEV on September 25 with a three-day remediation deadline for FCEB agencies. The vendor's 8.1 is defensible in a vacuum, but the explosive real-world exploitation, commodity tooling, and WordPress's 43 % web-market share make this functionally CRITICAL for any organisation running unpatched WordPress.

"Pre-auth WordPress LFI mass-exploited in hours; patch or lose the box."
02 · The Attack Path

5 steps from start to impact.

STEP 01

Reconnaissance — identify WordPress version

The attacker fetches /feed/, /readme.html, or the <meta name="generator"> tag to confirm WordPress and its version. Shodan, FOFA, and Censys dorks surface millions of WordPress hosts. No authentication or interaction is required.
Conditions required:
  • Target runs WordPress 4.7.0–7.1.1
  • WordPress is reachable over HTTP/S
Where this breaks in practice:
  • Sites behind CDNs may strip version metadata, but the attack still works without fingerprinting — attackers spray blindly
Detection/coverage: Nuclei template CVE-2026-87902.yaml (projectdiscovery/nuclei-templates#17302) detects the vulnerable code path non-invasively.
STEP 02

Craft double-encoded pagename traversal

The attacker constructs a request with pagename set to a double-URL-encoded traversal sequence (e.g., %252e%252e%252f repeated 1–12 levels) targeting a known .php file outside the theme directory. A valid page_id of a published Page is included so WordPress enters page-template resolution. Tools: exploit_locate_template_rce.py, CVE-2026-87902-Toolkit, or a raw curl one-liner. Uppercase hex and varying depths evade signature WAFs.
Conditions required:
  • Unpatched WordPress 4.7.0–7.1.1
  • At least one published Page exists (default on fresh installs)
Where this breaks in practice:
  • WAFs with deep-decode inspection (Cloudflare managed rule shipped Oct 1) block the most common encodings, but multi-encoding and multipart/form-data wrapping bypass simpler rulesets
Detection/coverage: WAF logs showing pagename values with %2e or %252e sequences; Patchstack virtual-patch rule shipped Sep 22.
STEP 03

LFI triggers — arbitrary .php included

WordPress's get_page_template() calls urldecode() on the payload, producing real ../ components. locate_template() checks file existence and readability but never validates the resolved path stays inside the theme directory. The attacker-chosen .php file is loaded via include. This step succeeds on any unpatched WordPress regardless of theme or PHP config. By itself it yields information disclosure (e.g., including files that echo configuration) or execution of existing server-side PHP code.
Conditions required:
  • Steps 1–2 succeeded
Where this breaks in practice:
  • The included file must be a .php file (not arbitrary read of non-PHP files)
  • Useful LFI targets beyond pearcmd vary by hosting stack
Detection/coverage: Application-layer IDS rules matching page- followed by path traversal in the URI; unusual include() paths in PHP open_basedir violation logs.
STEP 04

(Conditional) Include pearcmd.php with config-create

If /usr/local/lib/php/pearcmd.php (or distro equivalent) exists, is readable, and PHP's register_argc_argv=On, the attacker appends +config-create+/&/<php payload>/+/tmp/shell.php to the query string. Because register_argc_argv passes the query string as $argv to included scripts, pearcmd interprets this as a config-create command and writes attacker-controlled PHP to disk. Observed webshell filenames: wp-pear-rce-flag.php, poc87902.php, luci_*, zeta_*.
Conditions required:
  • Active theme has a page- prefixed top-level directory
  • pearcmd.php exists and is readable
  • register_argc_argv=On (default in official PHP Docker images, cPanel < PHP 8.5)
Where this breaks in practice:
  • Production php.ini commonly sets register_argc_argv=Off
  • Managed WordPress hosts (WP Engine, Kinsta, Flywheel) strip PEAR entirely
  • open_basedir restrictions may block traversal to pearcmd path
Detection/coverage: File-integrity monitoring for new .php files in /tmp, /var/tmp, wp-content/uploads; Sysdig Falco or auditd rules for pearcmd.php being opened by httpd/php-fpm.
STEP 05

(Conditional) Execute webshell — full RCE

A second request re-uses the same LFI to include the freshly written webshell from /tmp/shell.php. The attacker now has arbitrary PHP execution as the web-server user (typically www-data). From here the standard post-exploitation playbook applies: database credential theft from wp-config.php, lateral movement, crypto-mining, or ransomware staging.
Conditions required:
  • Step 4 succeeded
  • Web server user can read the written file
Where this breaks in practice:
  • /tmp may be mounted noexec on hardened hosts (but include bypass noexec)
  • SELinux/AppArmor httpd_t confinement may block reads from /tmp on RHEL/derivatives
Detection/coverage: Endpoint agent alerting on PHP process spawning shell commands; webshell scanners (YARA rules for system(), passthru(), eval(base64_decode( patterns).
03 · Compensating Control

1
CRITICAL 9.0→IGNORE 0.0
SEVERITY REDUCED
Patch WordPress immediately to the fixed version for your branch — This is the only complete fix. WordPress 7.1.2 (or the corresponding backport for your branch) closes the path-traversal at the source. Auto-update is enabled by default for minor releases; verify it fired. Per the noisgate mitigation SLA for CRITICAL + KEV-listed vulnerabilities: deploy within hours, not days. Run wp core update via WP-CLI or apply the one-click update from the dashboard. Confirm with wp core version.
2
CRITICAL 9.0→HIGH 7.5
SEVERITY REDUCED
Disable register_argc_argv in PHP configuration — Set register_argc_argv=Off in php.ini (or PHP-FPM pool config) and restart PHP. This breaks the demonstrated pearcmd RCE chain completely — pearcmd can no longer receive query-string arguments as $argv. The LFI still works, but the most dangerous escalation path is severed. Deploy within hours as a stop-gap if patching WordPress requires a maintenance window. Verify with php -i | grep register_argc_argv.
3
CRITICAL 9.0→HIGH 7.0
SEVERITY REDUCED
Deploy WAF rule blocking double-encoded traversal in pagename — Cloudflare shipped a managed rule Oct 1; Patchstack's virtual patch was available Sep 22. For custom WAFs (ModSecurity, AWS WAF), create a rule matching pagename parameters containing %2e, %252e, or .. after recursive URL-decoding. This blocks the exploitation vector at the network edge. Deploy within hours. Note: determined attackers may find encoding bypasses, so this is defense-in-depth, not a substitute for patching.
4
CRITICAL 9.0→HIGH 7.5
SEVERITY REDUCED
Remove or chmod pearcmd.php to 000 — If PEAR is not needed (it rarely is on web servers), delete /usr/local/lib/php/pearcmd.php and peclcmd.php, or chmod 000 them. This eliminates the known RCE gadget. The LFI persists, but the proven code-execution path is gone. On Docker images, add RUN rm -f /usr/local/lib/php/pearcmd.php /usr/local/lib/php/peclcmd.php to your Dockerfile.
5
CRITICAL 9.0→HIGH 7.0
SEVERITY REDUCED
Switch to a theme without page-* top-level directories — If your active theme has a page-templates/ or similar page-* directory at its root, switching to a theme that lacks this structure (e.g., Twenty Twenty-One, Twenty Twenty-Three, Flavor) breaks the template-resolution traversal chain. This is a tactical stop-gap only — it addresses the RCE path but the LFI may still be exploitable through other filesystem paths.
6
CRITICAL 9.0→MEDIUM 6.0
SEVERITY REDUCED
Enable open_basedir restriction for the WordPress document root — Set open_basedir in PHP to restrict file access to the WordPress docroot and essential PHP directories. This prevents include() from reaching /usr/local/lib/php/pearcmd.php or other files outside the allowed tree. Deploy via php.ini or .htaccess. Verify with phpinfo(). Note: this can break plugins that access files outside docroot.
What doesn't work
  • IP-based blocking of known attackers — CrowdSec observed 30K+ IPs in 5 days with 100x source growth; attackers rotate through rented VPS and compromised hosts faster than blocklists update.
  • WordPress application-level firewalls (e.g., Wordfence free tier) — the vulnerability is in Core's template resolution, which executes before most plugin-based firewalls initialise their request inspection. Premium/paid tiers with early-load virtual patching may help, but free tiers run too late in the request lifecycle.
  • Disabling XML-RPC or REST API — this vulnerability is triggered through normal page requests (the pagename query parameter on frontend page loads), not through XML-RPC or the REST API. Disabling those endpoints has zero effect.
  • File permission hardening on theme directories — the theme directory only needs to *exist* and be traversable; the attacker does not write to it. Making theme files read-only does not prevent the LFI.
04 · Intelligence Metadata

The supporting signals.

In-the-Wild ExploitationConfirmed and massive. First exploitation recorded at 11:49 UTC September 22, 2026 — same day as patch. 30,813 unique attacking IPs and 322,680 signals in five days (CrowdSec). 350K+ sites reported compromised by Sep 25 (TechTimes).
CISA KEVListed September 25, 2026. FCEB remediation deadline: September 28, 2026 (3 days). CISA alert.
EPSS0.39979 — indicates significant predicted exploitation probability, consistent with observed mass scanning.
Proof-of-ConceptMultiple public PoCs. ressl/cve-2026-87902-poc (researcher's lab + exploit), crowsec-edtech/CVE-2026-87902 (Python scanner), bhideki/CVE-2026-87902, oliveiralimajr/CVE_2026_87902. Nuclei template merged as projectdiscovery/nuclei-templates#17302 on Sep 23.
CVSS VectorCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H — 8.1. Network-reachable, no privileges, no interaction. AC:H reflects the RCE preconditions (theme dir + pearcmd + register_argc_argv). Note: NVD CVSS v4.0 scored 9.2.
Affected VersionsWordPress 4.7.0 through 7.1.1 — all branches. WordPress ≤ 4.6 is EOL and receives no backport.
Fixed Versions7.1.2, 7.0.6, 6.9.9, 6.8.10, 6.7.9, 6.6.9, 6.5.12, 6.4.12, 6.3.12, 6.2.13, 6.1.14, 6.0.16, 5.9.18, 5.8.17, 5.7.19, 5.6.21, 5.5.22, 5.4.23, 5.3.25, 5.2.28, 5.1.26, 5.0.29, 4.9.33, 4.8.32, 4.7.37.
Scanning / ExposureCrowdSec Live Exploit Tracker counted sources growing from 237 → 23,321 in five days, with attackers rotating IPs across rented VPS and compromised hosts. Attackers use uppercase hex encoding, variable traversal depth (1–12 levels), and multipart/form-data wrapping to evade WAFs.
ReporterRobert Ressl (Wollerau, Switzerland) — reported via WordPress HackerOne program on July 20, 2026. Advisory: GHSA-7hp8-65ch-5whp.
Patch TurnaroundReported Jul 20 → patched Sep 22 (64-day coordinated disclosure). Exploitation began the same day the patch shipped — attackers reverse-engineered the one-line diff.

Sources.

  1. CISA KEV Alert — CVE-2026-87902
  2. CrowdSec Exploitation Tracking Report
  3. Patchstack — WordPress 7.1.2 Security Release
  4. Equixly — Path Traversal to RCE via PEAR
  5. Robert Ressl — Researcher PoC and Lab
  6. Nuclei Template PR #17302
  7. TechTimes — 350K Sites Compromised
  8. SecurityWeek — Exploited Immediately After Disclosure
05 · The Call

Final Verdict
↑ UPGRADED to CRITICAL (9.0/10)

Why this verdict

  • Active mass exploitation obliterates AC:H. The vendor's 8.1 scores AC:H because RCE needs three conditions. But 30K+ IPs are spraying automated exploits and succeeding at scale — the conditions are met far more often than a textbook probability model implies. When commodity Nuclei templates land RCE within 24 hours of disclosure, the practical attack complexity is LOW.
  • LFI is unconditional and pre-auth. Even without the RCE chain, *every* unpatched WordPress (4.7–7.1.1) is vulnerable to Local File Inclusion with zero prerequisites. Including server-side PHP files can leak database credentials, session secrets, and plugin configuration. The vendor CVSS doesn't adequately weight the standalone LFI impact.
  • RCE conditions are default-on in major hosting stacks. Official PHP Docker images ship register_argc_argv=On and include pearcmd.php. cPanel on PHP < 8.5 enables it. Several top-20 themes (Twenty Twelve, Twenty Fourteen, Neve, Hestia, Sydney) carry the page-templates/ directory. The intersection is not rare — it's a significant subset of the install base.
  • WordPress install base is enormous. WordPress powers ~43% of websites globally. The vulnerable version window (4.7–7.1.1) spans nine years. Even if only 10% of instances meet all RCE conditions, that's millions of exploitable servers.
  • KEV-listed with 3-day FCEB deadline. CISA added this Sep 25 with the shortest possible remediation window, signalling maximum urgency.
  • Role multiplier: WordPress is canonically *internet-facing web infrastructure* — most instances sit directly on the network edge or behind a CDN/reverse proxy. (a) *Low-value role*: dev/staging sites — chain succeeds, blast radius is host-level. (b) *Typical role*: corporate marketing/blog/intranet — chain succeeds, blast radius is web-server compromise + database credential theft + pivot point into internal network. (c) *High-value role*: WordPress as e-commerce platform (WooCommerce handling payment/PII data), membership sites with credential stores, or WordPress Multisite hosting dozens of tenant sites — chain succeeds, blast radius is mass data exfiltration and tenant compromise. While WordPress is not in the canonical IdP/hypervisor/PAM high-value catalog, its universal internet exposure and the unconditional pre-auth LFI make the effective blast radius comparable to a network-edge appliance vulnerability.

Why not higher?

A 10.0 would require unconditional RCE with no preconditions and no mitigating factors. Here, the full RCE chain genuinely requires three conditions to align (theme directory, pearcmd, register_argc_argv). Hosts with hardened PHP configs, managed WordPress hosting (WP Engine, Kinsta), or themes without page-* directories are exposed only to LFI, not RCE. The conditional nature of the worst outcome caps this at 9.0 rather than 9.8–10.0.

Why not lower?

Even setting aside the RCE chain entirely, the unconditional pre-auth LFI on the most popular CMS on earth is itself HIGH severity — it enables inclusion of any readable .php file on the server. Adding the proven, actively-exploited RCE chain that works by default on Docker and cPanel stacks, plus KEV listing and 350K+ compromised sites, makes anything below CRITICAL an indefensible position for a practitioner advisory. The scale of active exploitation alone overrides any theoretical friction discount.

06 · Verification

Crowdsourced verification payload.

Run this script on each WordPress host as any user with read access to the WordPress installation directory. Invoke with: bash check_cve_2026_87902.sh /var/www/html (pass the WordPress root path as the first argument). No network access or elevated privileges required — it reads wp-includes/version.php and checks local PHP/theme conditions.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# check_cve_2026_87902.sh — Detect CVE-2026-87902 exposure
# Usage: bash check_cve_2026_87902.sh /path/to/wordpress
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN

set -euo pipefail

WP_ROOT="${1:-}"
if [[ -z "$WP_ROOT" || ! -d "$WP_ROOT" ]]; then
  echo "UNKNOWN — Usage: $0 /path/to/wordpress"
  exit 2
fi

VERSION_FILE="$WP_ROOT/wp-includes/version.php"
if [[ ! -f "$VERSION_FILE" ]]; then
  echo "UNKNOWN — $VERSION_FILE not found; is this a WordPress installation?"
  exit 2
fi

# Extract WordPress version
WP_VERSION=$(grep -oP "\\\$wp_version\s*=\s*'\K[^']+" "$VERSION_FILE" 2>/dev/null || true)
if [[ -z "$WP_VERSION" ]]; then
  echo "UNKNOWN — could not parse WordPress version from $VERSION_FILE"
  exit 2
fi

echo "[*] WordPress version detected: $WP_VERSION"

# Known fixed versions per branch (security backports)
declare -A FIXED_VERSIONS=(
  ["7.1"]=2 ["7.0"]=6 ["6.9"]=9 ["6.8"]=10 ["6.7"]=9 ["6.6"]=9
  ["6.5"]=12 ["6.4"]=12 ["6.3"]=12 ["6.2"]=13 ["6.1"]=14 ["6.0"]=16
  ["5.9"]=18 ["5.8"]=17 ["5.7"]=19 ["5.6"]=21 ["5.5"]=22 ["5.4"]=23
  ["5.3"]=25 ["5.2"]=28 ["5.1"]=26 ["5.0"]=29 ["4.9"]=33 ["4.8"]=32
  ["4.7"]=37
)

# Parse major.minor and patch
IFS='.' read -r MAJOR MINOR PATCH <<< "$WP_VERSION"
PATCH=${PATCH:-0}
BRANCH="$MAJOR.$MINOR"

# Check if version is >= 7.2 (future, assumed patched)
if (( MAJOR > 7 )) || (( MAJOR == 7 && MINOR >= 2 )); then
  echo "PATCHED — WordPress $WP_VERSION is beyond the affected range."
  exit 0
fi

# Check if version is < 4.7 (not affected per advisory — vuln introduced in 4.7)
if (( MAJOR < 4 )) || (( MAJOR == 4 && MINOR < 7 )); then
  echo "PATCHED — WordPress $WP_VERSION predates the vulnerable code (introduced in 4.7)."
  exit 0
fi

# Look up the fixed patch level for this branch
FIXED_PATCH="${FIXED_VERSIONS[$BRANCH]:-}"
if [[ -z "$FIXED_PATCH" ]]; then
  echo "UNKNOWN — branch $BRANCH not in known-fixed table; verify manually."
  exit 2
fi

VULN=false
if (( PATCH < FIXED_PATCH )); then
  VULN=true
fi

# --- Additional context: check RCE preconditions ---
RCE_RISK=""

# Check for page-* theme directories
THEME_DIR="$WP_ROOT/wp-content/themes"
PAGE_DIR_FOUND=false
if [[ -d "$THEME_DIR" ]]; then
  while IFS= read -r -d '' tdir; do
    theme_name=$(basename "$tdir")
    for subdir in "$tdir"/page-*/; do
      if [[ -d "$subdir" ]]; then
        PAGE_DIR_FOUND=true
        RCE_RISK="${RCE_RISK}  [!] Theme '$theme_name' has directory '$(basename "$subdir")/'\n"
      fi
    done
  done < <(find "$THEME_DIR" -maxdepth 1 -mindepth 1 -type d -print0 2>/dev/null)
fi

# Check register_argc_argv
ARGV_ON=false
ARGV_VAL=$(php -r 'echo ini_get("register_argc_argv");' 2>/dev/null || echo "unknown")
if [[ "$ARGV_VAL" == "1" || "$ARGV_VAL" == "On" || "$ARGV_VAL" == "on" ]]; then
  ARGV_ON=true
fi

# Check for pearcmd.php
PEAR_FOUND=false
for p in /usr/local/lib/php/pearcmd.php /usr/share/php/pearcmd.php /usr/lib/php/pearcmd.php; do
  if [[ -r "$p" ]]; then
    PEAR_FOUND=true
    PEAR_PATH="$p"
    break
  fi
done

if $VULN; then
  echo "VULNERABLE — WordPress $WP_VERSION is below fixed version $BRANCH.$FIXED_PATCH for CVE-2026-87902."
  echo ""
  echo "[*] RCE precondition check:"
  if $PAGE_DIR_FOUND; then
    echo -e "$RCE_RISK"
  else
    echo "  [OK] No page-* theme directories found (RCE theme condition NOT met)."
  fi
  if $ARGV_ON; then
    echo "  [!] register_argc_argv is ON — RCE PHP condition met."
  else
    echo "  [OK] register_argc_argv is OFF — RCE PHP condition not met."
  fi
  if $PEAR_FOUND; then
    echo "  [!] pearcmd.php found at $PEAR_PATH — RCE gadget present."
  else
    echo "  [OK] pearcmd.php not found in common paths — RCE gadget absent."
  fi
  if $PAGE_DIR_FOUND && $ARGV_ON && $PEAR_FOUND; then
    echo ""
    echo "  >>> ALL THREE RCE CONDITIONS MET — FULL RCE IS POSSIBLE <<<"
  elif $PAGE_DIR_FOUND || $ARGV_ON || $PEAR_FOUND; then
    echo ""
    echo "  >>> PARTIAL RCE conditions met — LFI is still exploitable. Patch urgently. <<<"
  else
    echo ""
    echo "  >>> RCE conditions not met, but LFI is still exploitable. Patch required. <<<"
  fi
  exit 1
else
  echo "PATCHED — WordPress $WP_VERSION is at or above the fixed version ($BRANCH.$FIXED_PATCH) for CVE-2026-87902."
  exit 0
fi