← Back to Feed CACHED · 2026-10-07 10:17:30 · CACHE_KEY CVE-2026-64638
CVE-2026-64638 · CWE-79 · Disclosed 2026-08-07

WordPress Core Pre-Auth Reflected XSS in wp-login.php via strip_tags/KSES Parser Differential (XSS2Shell)

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

Like leaving a skeleton key taped to the front door of 500 million buildings, but it only works if the building manager picks it up

CVE-2026-64638 is a reflected cross-site scripting vulnerability in WordPress Core's wp-login.php, caused by a parser differential between PHP's strip_tags() and WordPress's wp_kses_post() sanitizer. Malformed tag-like input POSTed as the username in a failed login attempt survives strip_tags() as text but is reconstructed as executable HTML by the KSES layer inside wp_admin_notice() (introduced in WordPress 6.4). The result is attacker-controlled JavaScript executing in the WordPress origin — no authentication required. All WordPress versions before 7.0.3 are affected. Backported security fixes are available for every supported branch back to 4.7. The research team at pwn.ai discovered the flaw on July 26, 2026, reported it to WordPress on July 27, and WordPress shipped 7.0.3 on August 6, 2026 — an 11-day turnaround.

Third-party CVSS assessments diverge dramatically: v3.1 scores it at 5.4 (Medium) for the XSS in isolation, while CVSS v4.0 scores it at 8.9 (High) when the full XSS-to-RCE chain ('XSS2Shell') is considered. Neither score fully captures the real-world risk profile. The v3.1 score underweights the demonstrated RCE chain and the 500M+ site attack surface. The v4.0 score overweights the theoretical maximum impact without adequately discounting the social-engineering friction: the full RCE chain requires a logged-in administrator to visit an attacker-controlled page, which is a non-trivial prerequisite even at WordPress scale. With 80% of WordPress installations auto-updating minor security releases, the real exposure window is narrower than the raw install base suggests — but the remaining 20% (roughly 100M+ sites) represents an enormous long-tail attack surface. One confirmed in-the-wild exploitation was documented as of September 17, 2026, and 60+ public PoC repositories exist. noisgate assesses this at HIGH (7.2) — the XSS is trivially reachable and the RCE chain is fully weaponized, but per-target social engineering prevents mass autonomous exploitation.

"Pre-auth XSS on the world's most popular CMS chains to RCE when an admin clicks one link."
02 · The Attack Path

6 steps from start to impact.

STEP 01

Craft malicious login URL

The attacker constructs a URL pointing to the target's /wp-login.php with a log parameter containing a payload that exploits the strip_tags() / wp_kses_post() parser differential. The payload uses URL-encoded angle brackets (%3C) followed by whitespace variants (%09, %0a, %0d) that strip_tags() treats as text but KSES later promotes to valid HTML. No tooling beyond curl or a browser is needed; multiple PoC generators exist (e.g., xss2shell_chain.py from imbas007/CVE-2026-64638-POC).
Conditions required:
  • Target runs WordPress < 7.0.3 (or unpatched backport branch)
  • wp-login.php is reachable from the internet
Where this breaks in practice:
  • 80% of WordPress installs auto-update security releases, shrinking the vulnerable population rapidly after August 6, 2026
  • WAF rules blocking encoded angle brackets in the log parameter neutralize the payload
Detection/coverage: Sigma rule matching wp-login.php URIs containing <script, %3Cscript, onerror=, %3Csvg, %3Cimg in the log parameter. Nuclei templates available from community repos.
STEP 02

Deliver link to WordPress administrator

The attacker must social-engineer a WordPress administrator into clicking the crafted URL while the admin has an active authenticated session. This is typically done via spear-phishing email, Slack/Teams message, or a watering-hole page. The link appears to point to the legitimate WordPress login page on the target's own domain, which increases click-through likelihood compared to a foreign phishing domain. The reflected XSS fires when the login page renders the 'invalid username' error message.
Conditions required:
  • Attacker identifies a valid WordPress administrator email or messaging handle
  • Administrator clicks the link while logged into the WordPress backend
Where this breaks in practice:
  • Requires targeted social engineering — cannot be automated at scale without a contact list
  • Email gateways and link-scanning proxies (Microsoft Defender for Office 365, Proofpoint, Mimecast) may defang or sandbox the URL
  • Security-aware admins may recognize the suspicious URL parameters
Detection/coverage: Email gateway logs showing outbound clicks to own-domain wp-login.php with unusually long query strings or encoded payloads.
STEP 03

DOM clobbering and SOME execution

Once the XSS fires in the admin's browser within the WordPress origin, the injected DOM clobbers variables used by WordPress's user-profile.js script (which is enqueued on the login page for password-reset functionality). Using Same-Origin Method Execution (SOME) techniques, the attacker's JavaScript elevates its execution context to issue authenticated REST API calls. The script leverages _jsonp= callbacks with dot notation (e.g., window.opener.approve.click) to invoke WordPress REST endpoints in the admin's security context.
Conditions required:
  • XSS payload successfully executes in the admin's browser
  • user-profile.js is enqueued on the login page (default in WordPress 6.4+)
Where this breaks in practice:
  • Content Security Policy with strict script-src directives would block inline script execution, but WordPress core does not ship CSP headers by default
  • Browser-based XSS auditors (largely deprecated) offer no protection in modern browsers
Detection/coverage: Server-side logging of REST API requests with _jsonp= callbacks containing dot notation from the admin's session.
STEP 04

Application Password exfiltration

The SOME technique triggers the WordPress Application Password creation flow via the REST API (/wp-json/wp/v2/users/me/application-passwords), generating a long-lived API token bound to the admin account. This token is exfiltrated to the attacker's callback server. Application Passwords are enabled by default in WordPress 5.6+ and bypass cookie-based session expiry, giving the attacker persistent authenticated API access.
Conditions required:
  • Application Passwords feature is enabled (default since WP 5.6)
  • REST API is accessible
Where this breaks in practice:
  • Setting add_filter('wp_is_application_passwords_available', '__return_false') in a mu-plugin disables the feature entirely
  • Monitoring authorize-application.php hits with off-origin success_url values catches this step
Detection/coverage: Audit log entry for Application Password creation event. Plugins like WP Activity Log or Simple History record this.
STEP 05

Malicious plugin upload via REST API

Using the stolen Application Password, the attacker authenticates to the WordPress REST API and uploads a malicious plugin ZIP containing a PHP webshell. The upload targets /wp-admin/update.php?action=upload-plugin. Once uploaded, WordPress automatically extracts the ZIP to wp-content/plugins/<plugin-name>/. The attacker then issues a direct GET request to the webshell file, achieving arbitrary PHP code execution as the web-server user (typically www-data or apache).
Conditions required:
  • Admin-level API credentials (obtained in step 4)
  • File modification is allowed (DISALLOW_FILE_MODS is not set — this is the default)
  • wp-content/plugins/ is writable by the web server
Where this breaks in practice:
  • Setting define('DISALLOW_FILE_MODS', true) in wp-config.php blocks plugin installation entirely
  • Managed hosting platforms (WP Engine, Pantheon, Kinsta) often enforce read-only filesystems for wp-content
  • File integrity monitoring (OSSEC, Wazuh, Tripwire) detects new files in plugin directories
Detection/coverage: File integrity alerts on new .php files in wp-content/plugins/. Process monitoring for web server users spawning shell processes (bash, sh, curl, wget, nc, python).
STEP 06

Post-exploitation: data exfiltration or lateral movement

With PHP execution as the web-server user, the attacker can read wp-config.php (containing database credentials), dump the WordPress database (user tables, WooCommerce order/payment data), pivot to other services accessible from the web server's network position, or install a persistent backdoor. On WooCommerce sites, this grants access to customer PII and potentially stored payment tokens. On WordPress multisite installations, a single compromised super-admin yields control over all network sites.
Conditions required:
  • PHP code execution achieved (step 5)
  • Database credentials in wp-config.php (universal)
Where this breaks in practice:
  • Network segmentation limiting web server egress reduces data exfiltration options
  • Database-level encryption or tokenized payment processing (Stripe, PayPal) limits payment data exposure
  • Container-based WordPress deployments may restrict lateral movement
Detection/coverage: Database query logging for bulk SELECT on wp_users, wp_wc_orders tables. Egress monitoring for unusual outbound connections from the web server.
03 · Compensating Control

1
HIGH 7.2→LOW 2.5
SEVERITY REDUCED
Deploy WAF rule blocking encoded angle brackets in wp-login.php POST body — Configure your WAF (Cloudflare, Sucuri, AWS WAF, ModSecurity) to reject or sanitize POST requests to /wp-login.php where the log parameter contains %3C, %3E, <script, <svg, <img, or onerror. This blocks the initial XSS vector at the network edge before it reaches WordPress. Do not restrict to specific tag names — the KSES parser accepts multiple whitespace variants and allowlisted tags. Deploy within 30 days per the noisgate mitigation SLA for HIGH severity. Given confirmed in-the-wild exploitation, prioritize deployment within days.
2
HIGH 7.2→MEDIUM 5.0
SEVERITY REDUCED
Disable Application Passwords via mu-plugin — Add add_filter('wp_is_application_passwords_available', '__return_false'); to a must-use plugin in wp-content/mu-plugins/. This breaks the XSS2Shell chain at step 4 by preventing Application Password creation, eliminating the attacker's path to persistent API credentials. The XSS itself still fires, so credential phishing via fake login overlays remains possible, but the automated RCE chain is severed. Deploy within 30 days per the noisgate mitigation SLA.
3
HIGH 7.2→MEDIUM 4.8
SEVERITY REDUCED
Set DISALLOW_FILE_MODS in wp-config.php — Add define('DISALLOW_FILE_MODS', true); to wp-config.php. This prevents plugin and theme installation/modification via the WordPress admin interface and API, breaking the chain at step 5 (plugin upload). Does not close the XSS or prevent Application Password theft, but eliminates the path to PHP code execution. Combined with disabling Application Passwords, this defense-in-depth approach leaves only the reflected XSS. Deploy within 30 days.
4
HIGH 7.2→LOW 2.0
SEVERITY REDUCED
Restrict wp-login.php access to trusted networks — Use server-level access controls (nginx allow/deny, Apache Require ip, or WAF geo-fencing) to limit access to /wp-login.php and /wp-admin/ to known admin IP ranges or VPN egress IPs. This eliminates the XSS attack surface for external attackers entirely. Note: this blocks public user registration and password reset if those features are needed. Deploy within 30 days.
5
HIGH 7.2→IGNORE 0.0
SEVERITY REDUCED
Patch WordPress to 7.0.3 or the latest backported security release — This is the definitive remediation. Update via WP-CLI (wp core update), the WordPress dashboard, or your managed hosting platform's update mechanism. Verify auto-updates are enabled for future security releases. Per the noisgate remediation SLA for HIGH severity, complete within 180 days. Given confirmed in-the-wild exploitation, aim to complete within 30 days for internet-facing sites.
What doesn't work
  • Rate limiting on wp-login.php — Rate limiting mitigates brute-force credential stuffing, not reflected XSS. The attack payload is delivered in a single crafted request; throttling login attempts has zero effect on the XSS trigger.
  • Two-factor authentication (2FA) — 2FA protects the login flow itself but does not prevent the reflected XSS from executing in the admin's browser. The XSS2Shell chain abuses the admin's *existing authenticated session*, not their login credentials. 2FA is bypassed entirely because the attack operates within an already-authenticated context.
  • WordPress security plugins (Wordfence, iThemes Security, Sucuri plugin) — These plugins apply server-side filtering heuristics that may not catch the specific strip_tags()/KSES parser differential payload. The malformed input is specifically designed to survive PHP-level sanitization. Do not rely on these as a substitute for patching.
04 · Intelligence Metadata

The supporting signals.

In-the-Wild ExploitationOne confirmed case documented as of September 17, 2026. Not yet on CISA KEV catalog. Targeted exploitation, not mass scanning.
Proof-of-Concept Availability60+ public exploits indexed on Sploitus. Key repos: imbas007/CVE-2026-64638-POC (11★, 3 forks; includes xss2shell_chain.py, xss2shell_scanner.py), 0xlipon/xss2shell, Boreas37/CVE-2026-64638-PoC-XSS2Shell- (includes nuclei template). Packet Storm has a Python exploit by 1dayexploit. Over 40 researchers published variants between August 7 – October 6, 2026.
EPSS0.00894 (0.894%) — roughly top 8th percentile. Non-trivial probability of exploitation within 30 days, consistent with available weaponization and one confirmed in-the-wild event.
KEV StatusNot listed as of October 7, 2026. One in-the-wild case may not meet CISA's threshold for inclusion.
CVSS Vectorsv3.1: 5.4 / CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N (XSS-only scope). v4.0: 8.9 / CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:A/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H (full chain scope). No official NVD or WordPress CNA score published.
Affected VersionsAll WordPress Core versions prior to 7.0.3. The wp_admin_notice() sink was introduced in WordPress 6.4, so the full XSS2Shell chain requires 6.4+. The underlying strip_tags() / KSES differential affects older versions for the reflected XSS alone.
Fixed VersionsWordPress 7.0.3 (released August 6, 2026). Backported to all supported branches: 6.9.x, 6.8.x, 6.7.x, 6.6.x, 6.5.x, 6.4.x, 6.3.x, 6.2.x, 6.1.x, 6.0.x, 5.9.x through 4.7.x. Fix: esc_html() applied at the username interpolation site in wp-includes/user.php.
Exposure / Scanning DataWordPress powers ~43% of all websites (~500M+ sites). Shodan dorks targeting /wp-login.php are included in PoC toolkits for target enumeration. No mass scanning campaigns observed on GreyNoise sensors as of October 2026 — consistent with the social-engineering requirement limiting automated exploitation.
Disclosure TimelineJuly 26, 2026: pwn.ai discovers and reproduces full chain. July 27: Reported to WordPress security team with browser evidence and PHP-execution proof. August 6: WordPress 7.0.3 released with fix. August 7: Public disclosure. September 17: First documented in-the-wild exploitation.
Discovering Researcherpwn.ai research team. Demonstrated the full XSS2Shell chain (pre-auth XSS → DOM clobbering → SOME → Application Password theft → plugin upload → PHP execution). Nickname: XSS2Shell.

Sources.

  1. op-c.net — XSS2Shell: WordPress CVE-2026-64638 Pre-Auth XSS Can Lead to RCE
  2. Hadrian — WordPress XSS2Shell: Unauthenticated Login-Screen XSS to PHP Code Execution
  3. SecurityOnline — WordPress Security Update 7.0.3
  4. SecurityArsenal — CVE-2026-64638: Detection, Chaining Risk, and Remediation Guide
  5. GitHub — imbas007/CVE-2026-64638-POC (XSS2Shell PoC toolkit)
  6. Sploitus — 60+ exploits for CVE-2026-64638
  7. HivePro — Threat Advisory: CVE-2026-64638 XSS2Shell
  8. Patrowl — XSS2Shell: Critical WordPress Login Vulnerability
05 · The Call

Final Verdict
= UNCHANGED to HIGH (7.2/10)

Why this verdict

  • Pre-auth entry point on massive attack surface: The reflected XSS requires zero authentication and fires on wp-login.php, which is publicly reachable on virtually every WordPress installation. With 500M+ sites running WordPress, the addressable target population dwarfs almost any other CVE.
  • Fully weaponized RCE chain with 60+ public PoCs: The XSS2Shell chain from reflected XSS to PHP code execution is documented, automated, and available in turnkey toolkits. One confirmed in-the-wild exploitation demonstrates real-world viability.
  • Social-engineering friction is real but bounded: The full RCE chain requires a logged-in WordPress administrator to click a crafted link — a per-target social engineering step that prevents mass autonomous exploitation. However, the link points to the target's own legitimate domain (victim.com/wp-login.php?...), which significantly increases click-through rates compared to foreign phishing domains.
  • Auto-update adoption reduces but does not eliminate exposure: ~80% of WordPress installs auto-update security releases, meaning ~100M+ sites may remain unpatched weeks after disclosure. The backport to 4.7+ helps, but sites on unmaintained hosting or with auto-updates disabled form a massive long tail.
  • Role multiplier: WordPress is not a canonical high-value-role component (not an IdP, DC, hypervisor, etc.), but WooCommerce e-commerce sites represent well over 1% of the WordPress installed base. On WooCommerce installs, successful exploitation yields database access containing customer PII, order history, and potentially payment tokens — qualifying as *mass data egress*. On WordPress Multisite networks, a compromised super-admin controls all network sites — qualifying as *fleet-scale* compromise within that network boundary. These high-value role outcomes floor the verdict at HIGH.

Why not higher?

The chain cannot reach CRITICAL because exploitation is not autonomously scalable — each target requires per-instance social engineering of a specific administrator with an active session. WordPress is not a canonical high-value-role component where ≥10% of installs occupy identity, infrastructure, or supply-chain roles. The auto-update mechanism means the majority of the installed base patches within days of release, and the exploit cannot propagate laterally without repeated social engineering.

Why not lower?

Despite the social-engineering friction, the verdict cannot drop below HIGH. WooCommerce e-commerce sites (well over 1% of WordPress installs) face mass data egress if the chain succeeds. The full RCE chain is publicly weaponized with 60+ PoC tools, one confirmed in-the-wild exploitation exists, and the link's use of the victim's own legitimate domain makes the social engineering materially more effective than typical phishing. The ~100M+ unpatched long-tail sites ensure a large addressable population even months post-disclosure.

06 · Verification

Crowdsourced verification payload.

Run this script on each WordPress host (or via SSH/Ansible across your fleet). Pass the WordPress installation path as the first argument. Requires read access to wp-includes/version.php and wp-includes/user.php — no root needed. Example: bash check_cve_2026_64638.sh /var/www/html

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# CVE-2026-64638 Checker — WordPress Pre-Auth Reflected XSS (XSS2Shell)
# Verifies whether the esc_html() fix is present at the interpolation
# site in wp-includes/user.php, independent of branch/backport version.
#
# Usage:  bash check_cve_2026_64638.sh [/path/to/wordpress]
# Exit:   0 = PATCHED, 1 = VULNERABLE, 2 = UNKNOWN

set -uo pipefail

WP_PATH="${1:-/var/www/html}"
VERSION_FILE="$WP_PATH/wp-includes/version.php"
USER_FILE="$WP_PATH/wp-includes/user.php"

# --- Locate WordPress ---
if [[ ! -f "$VERSION_FILE" ]]; then
  echo "UNKNOWN — cannot find WordPress at $WP_PATH"
  exit 2
fi

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

# --- Primary check: look for the esc_html() fix in user.php ---
# The patch adds esc_html() around the \$username variable at the
# interpolation site that feeds wp_admin_notice(). This check is
# branch-agnostic and works for all backported releases.
if [[ ! -f "$USER_FILE" ]]; then
  echo "UNKNOWN — cannot find $USER_FILE"
  exit 2
fi

if grep -qE 'esc_html\s*\(\s*\$username' "$USER_FILE" 2>/dev/null; then
  echo "PATCHED — WordPress $WP_VERSION contains the esc_html() fix for CVE-2026-64638"
  exit 0
fi

# --- Fallback check: version comparison for 7.0.x branch ---
IFS='.' read -r MAJOR MINOR PATCH <<< "$WP_VERSION"
PATCH="${PATCH:-0}"

if [[ "$MAJOR" -gt 7 ]]; then
  echo "PATCHED — WordPress $WP_VERSION is newer than the affected range"
  exit 0
elif [[ "$MAJOR" -eq 7 && "$MINOR" -eq 0 && "$PATCH" -ge 3 ]]; then
  echo "PATCHED — WordPress $WP_VERSION >= 7.0.3"
  exit 0
elif [[ "$MAJOR" -eq 7 && "$MINOR" -gt 0 ]]; then
  echo "PATCHED — WordPress $WP_VERSION is on a branch released after the fix"
  exit 0
fi

echo "VULNERABLE — WordPress $WP_VERSION is missing the esc_html() fix for CVE-2026-64638"
echo "[!] Update to 7.0.3+ or the latest security release for your branch."
exit 1