Back to Intelligence

CVE-2026-87902: WordPress get_page_template() Exploited Within Hours — Detection and Remediation Guide

SA
Security Arsenal Team
September 24, 2026
11 min read

Within hours of public disclosure, threat actors began actively exploiting CVE-2026-87902, a critical vulnerability in WordPress core carrying a CVSS score of 9.2. The flaw allows a completely unauthenticated attacker to manipulate the get_page_template() page-template resolution logic so that WordPress includes a chosen, readable local .php file — a local file inclusion (LFI) primitive that, on most real-world deployments, converts directly into remote code execution.

This is the worst-case disclosure pattern we see in vulnerability management: a critical, unauthenticated, remotely reachable flaw in the world's most widely deployed CMS, weaponized before most organizations have even read the advisory. WordPress powers roughly 40% of the web. If your organization — or your marketing team, or a subsidiary you forgot about — runs a WordPress instance, assume it is being scanned and probed right now. Mass exploitation of WordPress core flaws within hours of disclosure has historically meant automated botnets harvesting webshell access at scale, and there is no reason to expect this campaign to behave differently.

The window between disclosure and exploitation has effectively collapsed to zero. Your patch window needs to match.

Technical Analysis

The Vulnerability

CVE-2026-87902 (CVSS 9.2, Critical) resides in WordPress's page-template resolution code path, specifically the get_page_template() function. Under normal operation, this function determines which PHP template file from the active theme should render a given page request. The flaw allows an unauthenticated remote attacker to influence that resolution logic such that WordPress includes an arbitrary local .php file that is readable by the web server process.

Key characteristics from a defender's perspective:

  • Authentication: None required. Any unauthenticated remote client can reach the vulnerable code path.
  • Affected component: WordPress core template loader (get_page_template() and its calling chain in wp-includes/template-loader.php).
  • Prerequisite: The attacker must be able to place or identify a readable .php file on the local filesystem. On paper this looks like a constraint; in practice it is trivially satisfied.

Why This Is Effectively Unauthenticated RCE

The critical point your leadership needs to understand: an LFI limited to .php files sounds constrained, but WordPress deployments almost always give attackers a way to get attacker-controlled PHP onto disk:

  1. Media uploads. Many WordPress configurations and plugins process uploaded files in ways that leave attacker-influenced content in .php-parseable locations, and misconfigured upload handlers remain endemic.
  2. Log poisoning-adjacent techniques. Session files, cache files, and plugin-generated files under wp-content/ frequently contain request-influenced data and are readable by the web user.
  3. Third-party plugins and themes. The average WordPress site runs 20+ plugins, any of which may write .php files (backups, caches, exports) into web-readable or web-user-readable paths.

Once the attacker includes their planted PHP file through the template loader, code executes in the context of the web server process (typically www-data, apache, or nginx on Linux; the application pool identity under IIS). From there the standard post-exploitation playbook follows: webshell deployment (the classic pattern is a dropped shell in wp-content/uploads/), credential harvesting from wp-config.php (database credentials, auth keys), lateral movement, and persistence via rogue admin accounts or malicious must-use plugins (wp-content/mu-plugins/).

Exploitation Status

  • Active in-the-wild exploitation: CONFIRMED. Exploitation began within hours of public disclosure, indicating either pre-disclosure access to patch details (diffing the security release) or extremely rapid reverse engineering.
  • Attack volume: Expect automated, internet-wide scanning. WordPress core RCEs are immediately folded into botnet exploitation kits.
  • CISA KEV: Monitor the CISA Known Exploited Vulnerabilities catalog for addition; given confirmed active exploitation of a CVSS 9.2 unauthenticated RCE, listing is likely. Federal civilian agencies should track associated remediation deadlines.

What an Attack Looks Like on the Wire

Exploitation attempts will manifest as crafted HTTP GET/POST requests to WordPress front-end endpoints (index.php, page permalinks, /?p=<id>, or REST-adjacent paths) carrying parameters intended to steer template resolution toward a target local file path. Expect to see path traversal sequences (../, ....//, URL-encoded variants %2e%2e%2f, %252e), references to wp-content/uploads, /tmp/, /var/tmp/, session paths, and plugin cache directories in request parameters. Post-exploitation, look for new .php files in upload and cache directories and outbound connections from the web server process.

Detection & Response

The detections below are built around the observable behaviors this vulnerability produces: traversal-laden template-inclusion requests in web logs, PHP files appearing in directories that should never contain executable code, and the web server process spawning shells or unexpected children. These are high-fidelity behaviors — tune thresholds to your environment, but do not skip them.

SIGMA Rules

YAML
---
title: WordPress CVE-2026-87902 Template Inclusion Exploitation Attempt
id: 8f2c1a94-6b3d-4e17-a9f2-5c7d8e901a2b
status: experimental
description: Detects HTTP requests containing path traversal sequences combined with references to local PHP file paths in WordPress-facing web access logs, consistent with exploitation of the get_page_template() local file inclusion (CVE-2026-87902).
references:
  - https://thehackernews.com/2026/09/attackers-exploit-wordpress-cve-2026.html
  - https://attack.mitre.org/techniques/T1190/
author: Security Arsenal
date: 2026/09/18
tags:
  - attack.initial_access
  - attack.t1190
logsource:
  category: webserver
detection:
  selection_uri_traversal:
    cs-uri|contains:
      - '../'
      - '..\\'
      - '%2e%2e%2f'
      - '%252e'
      - '....//'
  selection_php_target:
    cs-uri|contains:
      - '.php'
      - 'wp-content/uploads'
      - 'wp-content/cache'
      - '/tmp/'
      - '/var/tmp/'
  condition: selection_uri_traversal and selection_php_target
falsepositives:
  - Rare; legitimate WordPress front-end requests do not carry traversal sequences targeting local PHP paths
level: high
---
title: PHP Webshell Dropped in WordPress Uploads or Cache Directory
id: 3b7e5f12-9a4c-4d28-b6e3-1f9a2c4d8e67
status: experimental
description: Detects creation of PHP files in WordPress upload, cache, or temporary directories, a strong post-exploitation indicator following template-inclusion code execution such as CVE-2026-87902.
references:
  - https://thehackernews.com/2026/09/attackers-exploit-wordpress-cve-2026.html
  - https://attack.mitre.org/techniques/T1505/003/
author: Security Arsenal
date: 2026/09/18
tags:
  - attack.persistence
  - attack.t1505.003
logsource:
  category: file_event
  product: linux
detection:
  selection:
    TargetFilename|endswith: '.php'
    TargetFilename|contains:
      - '/wp-content/uploads/'
      - '/wp-content/cache/'
      - '/wp-content/mu-plugins/'
      - '/wp-content/upgrade/'
  condition: selection
falsepositives:
  - Legitimate plugin or theme updates writing PHP files; correlate with maintenance windows and plugin update activity
level: high
---
title: Web Server Process Spawning Shell or System Utilities
id: 61d4a8c3-2f7b-4e95-9c14-7b3e6d5a2098
status: experimental
description: Detects web server or PHP worker processes (php-fpm, apache, nginx, httpd, w3wp) spawning command shells or system utilities, indicating post-exploitation activity after unauthenticated code execution on a CMS such as WordPress.
references:
  - https://thehackernews.com/2026/09/attackers-exploit-wordpress-cve-2026.html
  - https://attack.mitre.org/techniques/T1059/
author: Security Arsenal
date: 2026/09/18
tags:
  - attack.execution
  - attack.t1059
logsource:
  category: process_creation
  product: linux
detection:
  selection_parent:
    ParentImage|endswith:
      - '/php-fpm'
      - '/php-fpm8.2'
      - '/php-fpm8.3'
      - '/apache2'
      - '/httpd'
      - '/nginx'
  selection_child:
    Image|endswith:
      - '/sh'
      - '/bash'
      - '/dash'
      - '/zsh'
      - '/curl'
      - '/wget'
      - '/nc'
      - '/ncat'
      - '/python3'
      - '/perl'
  condition: selection_parent and selection_child
falsepositives:
  - Backup or cron-driven maintenance scripts invoked through PHP; whitelist known management tooling by full command line
level: critical

KQL — Microsoft Sentinel / Defender

This first query hunts web request logs (Apache/Nginx/IIS ingested via Syslog or CEF) for the exploitation pattern. The second hunts endpoint telemetry for webshell drops and suspicious child processes of web server workers.

KQL — Microsoft Sentinel / Defender
// Hunt 1: CVE-2026-87902 exploitation attempts in web request logs
// Requires Apache/Nginx access logs ingested via Syslog, or WAF/CEF via CommonSecurityLog
let traversal = dynamic(["../", "%2e%2e%2f", "%252e", "....//", "..\\"]);
let php_targets = dynamic([".php", "wp-content/uploads", "wp-content/cache", "/tmp/", "/var/tmp/", "mu-plugins"]);
union isfuzzy=true
    (Syslog
    | where TimeGenerated > ago(7d)
    | extend Request = tostring(SyslogMessage)),
    (CommonSecurityLog
    | where TimeGenerated > ago(7d)
    | extend Request = strcat(RequestMethod, " ", RequestURL))
| where Request has_any (traversal) and Request has_any (php_targets)
| where Request has_any ("wp", "index.php", "?p=", "page", "template", "rest_route")
| summarize AttemptCount = count(), SampleRequests = make_set(Request, 5)
    by SourceIP, Computer, bin(TimeGenerated, 1h)
| where AttemptCount >= 3
| order by AttemptCount desc;

// Hunt 2: Webshell drops and suspicious child processes of web workers (MDE-equipped hosts)
DeviceFileEvents
| where TimeGenerated > ago(7d)
| where FileName endswith ".php"
| where FolderPath has_any ("wp-content/uploads", "wp-content/cache", "wp-content/mu-plugins", "wp-content/upgrade")
| project TimeGenerated, DeviceName, FolderPath, FileName, SHA256, InitiatingProcessAccountName
| join kind=leftouter (
    DeviceProcessEvents
    | where TimeGenerated > ago(7d)
    | where InitiatingProcessFileName in~ ("php-fpm", "apache2", "httpd", "nginx", "w3wp.exe", "php-cgi.exe")
    | where FileName in~ ("sh", "bash", "curl", "wget", "nc", "ncat", "python3", "perl", "cmd.exe", "powershell.exe")
    | project ProcTime=TimeGenerated, DeviceName, FileName, ProcessCommandLine, InitiatingProcessFileName
) on DeviceName
| project TimeGenerated, DeviceName, FolderPath, FileName, SHA256, ProcTime, ProcessCommandLine
| order by TimeGenerated desc

Velociraptor VQL

Use this hunt across your Linux web tier to surface recently written PHP files in directories that should be static-only, plus active network connections from web server processes — the two fastest tripwires for this intrusion set.

VQL — Velociraptor
-- WordPress CVE-2026-87902 post-exploitation hunt:
-- recent .php files in upload/cache dirs + outbound connections from web workers
LET webshells = SELECT FullPath, Size, Mtime, Btime
FROM glob(globs=[
  '/var/www/*/wp-content/uploads/**/*.php',
  '/var/www/*/wp-content/cache/**/*.php',
  '/var/www/*/wp-content/mu-plugins/**/*.php',
  '/srv/www/*/wp-content/uploads/**/*.php'
])
WHERE Mtime > now() - 604800

LET web_conns = SELECT Pid, Name, RemoteAddress, RemotePort, Status
FROM netstat()
WHERE Name =~ 'php-fpm|apache2|httpd|nginx'
  AND RemotePort > 0
  AND NOT RemoteAddress =~ '^(127\\.|10\\.|192\\.168\\.|172\\.(1[6-9]|2[0-9]|3[01])\\.)'

SELECT * FROM chain(a=webshells, b=web_conns)

Remediation & Verification Script

Run this on each WordPress host to report the current core version, hunt for suspected webshells and rogue admin accounts, and verify that PHP execution is disabled in upload directories. It is read-only by design — review output before making changes.

Bash / Shell
#!/bin/bash
# CVE-2026-87902 WordPress verification & hunting script
# Run as root or with sudo on each WordPress host.

WP_PATH="/var/www/html"   # Adjust to your document root
REPORT="/tmp/wp_cve_2026_87902_check_$(date +%Y%m%d_%H%M).txt"

echo "=== WordPress Core Version ===" | tee -a "$REPORT"
if command -v wp >/dev/null 2>&1; then
  wp core version --path="$WP_PATH" --allow-root 2>/dev/null | tee -a "$REPORT"
else
  grep -m1 "\$wp_version" "$WP_PATH/wp-includes/version.php" 2>/dev/null | tee -a "$REPORT"
fi

echo -e "\n=== Suspicious .php files in uploads/cache (modified last 14 days) ===" | tee -a "$REPORT"
find "$WP_PATH/wp-content/uploads" "$WP_PATH/wp-content/cache" \
  -type f -name "*.php" -mtime -14 2>/dev/null | tee -a "$REPORT"

echo -e "\n=== Must-use plugins (common persistence) ===" | tee -a "$REPORT"
ls -la "$WP_PATH/wp-content/mu-plugins/" 2>/dev/null | tee -a "$REPORT"

echo -e "\n=== PHP execution blocking in uploads (.htaccess) ===" | tee -a "$REPORT"
if [ -f "$WP_PATH/wp-content/uploads/.htaccess" ]; then
  cat "$WP_PATH/wp-content/uploads/.htaccess" | tee -a "$REPORT"
else
  echo "MISSING: no .htaccess in uploads — PHP may execute there (Apache)." | tee -a "$REPORT"
fi

echo -e "\n=== Nginx: check for location block denying PHP in uploads ===" | tee -a "$REPORT"
grep -rE "uploads.*\.php|php.*uploads" /etc/nginx/ 2>/dev/null | tee -a "$REPORT" || \
  echo "No nginx PHP-in-uploads deny rule found." | tee -a "$REPORT"

echo -e "\n=== Rogue admin users (requires wp-cli) ===" | tee -a "$REPORT"
if command -v wp >/dev/null 2>&1; then
  wp user list --role=administrator --fields=user_login,user_email,user_registered \
    --path="$WP_PATH" --allow-root 2>/dev/null | tee -a "$REPORT"
fi

echo -e "\nReport saved to $REPORT"
echo "If suspicious .php files or unknown admins appear: isolate the host, preserve logs, and initiate IR."

Remediation

Given confirmed exploitation within hours of disclosure, treat this as an emergency change, not a routine patch cycle.

  1. Patch immediately. Update WordPress core to the patched release identified in the official advisory at wordpress.org/news/category/security/ and the vulnerability record at cve.org. Use wp core update or the admin dashboard. Verify auto-updates are enabled for security releases (WP_AUTO_UPDATE_CORE should not be set to false). Do not wait for a maintenance window — exploitation is automated and ongoing.
  2. Inventory every instance. Enumerate all WordPress deployments across your estate: marketing microsites, staging servers, acquired-company properties, container images, and forgotten VPS instances. The instance you don't know about is the one that gets popped.
  3. Disable PHP execution in upload directories. Even after patching, this hardening step blunts the entire class of upload-to-include attacks. On Apache, place a .htaccess in wp-content/uploads/ with php_flag engine off and a Require all denied rule for .php; on Nginx, add a location ~* /uploads/.*\.php$ { deny all; } block.
  4. Hunt before you trust. Patching closes the door but does not evict anyone already inside. Run the detections above against at least the last 14 days of web logs. Check for new .php files in wp-content/uploads/, cache/, mu-plugins/; unexpected administrator accounts; modified wp-config.php; and cron entries or systemd units created by the web user.
  5. Rotate credentials if any indicator hits. Database credentials and auth keys/salts in wp-config.php must be assumed compromised on any exploited host. Rotate DB passwords, WordPress salts, and any service accounts the web tier can reach.
  6. Put a WAF in front. Deploy or update WAF rules (ModSecurity CRS with traversal rules enabled, Cloudflare managed rules, or equivalent) to block requests carrying path-traversal sequences against WordPress endpoints as a compensating control while patching completes.
  7. Restrict egress from web servers. A compromised web worker that cannot initiate outbound connections cannot fetch second-stage payloads or exfiltrate. Default-deny egress for the web tier is one of the highest-value controls against this threat class.
  8. Monitor CISA KEV. Track cisa.gov/known-exploited-vulnerabilities-catalog for CVE-2026-87902's addition and any associated federal remediation deadline; use it as internal leverage if you encounter patch resistance.

If you find evidence of post-exploitation — webshells, rogue admins, unexpected outbound connections — do not simply delete files and patch. Isolate the host, preserve volatile data and logs, and engage your IR process. Attackers who gain code execution on a CMS routinely pivot to the database and adjacent internal systems.

Related Resources

Security Arsenal Penetration Testing Services AlertMonitor Platform Book a SOC Assessment vulnerability-management Intel Hub

Is your security operations ready?

Get a free SOC assessment or see how AlertMonitor cuts through alert noise with automated triage.