
Non-destructive detector for CVE-2026-64638 (XSS2Shell) — WordPress pre-auth XSS reflection primitive
Non-destructive, dependency-free detector for the CVE-2026-64638 ("XSS2Shell")
reflection primitive in WordPress wp-login.php.
CVE-2026-64638 is a pre-authentication reflected XSS in WordPress core, disclosed by PWN.AI on 2026-08-07 (CVSS 8.9, CWE-79) and patched in 7.0.3 with backports to every maintained branch (6.9.6, 6.8.7, 6.7.6, 6.6.6, 6.5.9, …).
The root cause is a parser differential between PHP's strip_tags() and
WordPress's KSES sanitizer:
strip_tags() refuses to recognize a tag when whitespace follows <, so a
username like < area id=x> survives as plain text.<area id="x">
element inside the login error message.That yields attacker-chosen DOM injection with chosen id/class/href
attributes — the primitive behind the published multi-stage chain
(DOM clobbering → REST JSONP → Application Password theft → plugin upload →
RCE against a logged-in admin victim).
Two non-destructive probes (a failed login with a random marker username):
<b>marker</b> appears unescaped in the error response.< area id=marker> survives strip_tags() and reappears
as a live/normalized element (e.g. area id="marker") in the response.No payload executes anything; the tool only measures whether attacker HTML is rendered as live DOM. Optional extras:
--version — fingerprint core version via the feed <generator> tag.--jsonp — verify the REST JSONP envelope (_jsonp/_envelope) used by
the published chain is reachable.python3 xss2shell_check.py https://example.com
python3 xss2shell_check.py --jsonp --verbose https://example.com
python3 xss2shell_check.py --version https://example.com
python3 xss2shell_check.py --json https://example.com
Python 3.6+, stdlib only. No dependencies.
| Code | Meaning |
|---|---|
| 0 | VULNERABLE (reflection primitive present) |
| 1 | PATCHED / NOT VULNERABLE |
| 2 | ERROR (unreachable, WAF challenge, unexpected response) |
<area id="…">).
Attribute quoting varies by KSES generation (old custom parser:
<area id=x>; DOMDocument: <area id="x"> / <area id="x" />), so all
forms are matched. A raw < area …> passthrough without re-parsing is
inert text in the browser, not the primitive — it is reported as
RAW_TEXT_ONLY and does not trigger a false positive.login_errors filter or CDN/WAF
rewrites responses, or the login error never echoes the username.Authorized security testing only. This tool is intended exclusively for testing systems you own or have explicit written permission to assess. Unauthorized testing is illegal in most jurisdictions.
| Flag | Purpose |
|---|
--version | fingerprint core version (feed generator tag, homepage meta fallback) |
--jsonp | also verify the REST JSONP envelope (_jsonp/_envelope) is reachable |
-v, --verbose | print probe response snippets (evidence for reports) |
--json | machine-readable JSON result (CI / automation) |
| Observed behavior | Verdict | Exit |
|---|
<b>marker</b> echoed unescaped | RAW_HTML — vulnerable | 0 |
< area id=marker> re-parsed into a live <area id="marker"> (any quoting / void form) | AREA_BYPASS — vulnerable | 0 |
| Username HTML-escaped | ESCAPED — not vulnerable | 1 |
| Tags stripped, text remains | STRIPPED — not vulnerable | 1 |
Raw < area …> passes through but is never re-parsed | RAW_TEXT_ONLY — inert text, not vulnerable | 1 |
| No echo at all | NOT_REFLECTED — not vulnerable | 1 |
| Probe blocked (HTTP ≥ 400 / challenge page) or target unreachable | ERROR | 2 |