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.
6 steps from start to impact.
Craft malicious login URL
/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).- Target runs WordPress < 7.0.3 (or unpatched backport branch)
- wp-login.php is reachable from the internet
- 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
logparameter neutralize the payload
wp-login.php URIs containing <script, %3Cscript, onerror=, %3Csvg, %3Cimg in the log parameter. Nuclei templates available from community repos.Deliver link to WordPress administrator
- Attacker identifies a valid WordPress administrator email or messaging handle
- Administrator clicks the link while logged into the WordPress backend
- 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
DOM clobbering and SOME execution
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.- XSS payload successfully executes in the admin's browser
- user-profile.js is enqueued on the login page (default in WordPress 6.4+)
- Content Security Policy with strict
script-srcdirectives 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
_jsonp= callbacks containing dot notation from the admin's session.Application Password exfiltration
/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.- Application Passwords feature is enabled (default since WP 5.6)
- REST API is accessible
- Setting
add_filter('wp_is_application_passwords_available', '__return_false')in a mu-plugin disables the feature entirely - Monitoring
authorize-application.phphits with off-originsuccess_urlvalues catches this step
Malicious plugin upload via REST API
/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).- Admin-level API credentials (obtained in step 4)
- File modification is allowed (
DISALLOW_FILE_MODSis not set — this is the default) - wp-content/plugins/ is writable by the web server
- 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
Post-exploitation: data exfiltration or lateral movement
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.- PHP code execution achieved (step 5)
- Database credentials in wp-config.php (universal)
- 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
/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.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.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.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.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.- 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.
The supporting signals.
| In-the-Wild Exploitation | One confirmed case documented as of September 17, 2026. Not yet on CISA KEV catalog. Targeted exploitation, not mass scanning. |
|---|---|
| Proof-of-Concept Availability | 60+ 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. |
| EPSS | 0.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 Status | Not listed as of October 7, 2026. One in-the-wild case may not meet CISA's threshold for inclusion. |
| CVSS Vectors | v3.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 Versions | All 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 Versions | WordPress 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 Data | WordPress 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 Timeline | July 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 Researcher | pwn.ai research team. Demonstrated the full XSS2Shell chain (pre-auth XSS → DOM clobbering → SOME → Application Password theft → plugin upload → PHP execution). Nickname: XSS2Shell. |
Sources.
- op-c.net — XSS2Shell: WordPress CVE-2026-64638 Pre-Auth XSS Can Lead to RCE
- Hadrian — WordPress XSS2Shell: Unauthenticated Login-Screen XSS to PHP Code Execution
- SecurityOnline — WordPress Security Update 7.0.3
- SecurityArsenal — CVE-2026-64638: Detection, Chaining Risk, and Remediation Guide
- GitHub — imbas007/CVE-2026-64638-POC (XSS2Shell PoC toolkit)
- Sploitus — 60+ exploits for CVE-2026-64638
- HivePro — Threat Advisory: CVE-2026-64638 XSS2Shell
- Patrowl — XSS2Shell: Critical WordPress Login Vulnerability
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.
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
#!/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