## https://sploitus.com/exploit?id=7C8C05DB-25C5-557D-BAAA-1ED7912E0D5E
# wp2shell-poc
Proof-of-concept for an unauthenticated SQL injection in WordPress core that chains to remote
code execution, via REST batch route confusion (CVE-2026-63030).
The exploit needs no credentials and no configuration beyond a reachable target. It reaches the
injection through a single endpoint: `POST /wp-json/batch/v1`.
The finding itself is that unauthenticated SQL injection โ `check` confirms it and `read`
demonstrates arbitrary database read. The `shell` command is optional post-exploitation
(recovered admin credentials โ plugin upload โ command execution), included to demonstrate full
impact; it is not the vulnerability.
## Affected versions
| Branch | Affected | Fixed in |
| ------ | ------------- | -------- |
| 6.9.x | 6.9.0 โ 6.9.4 | 6.9.5 |
| 7.0.x | 7.0.0 โ 7.0.1 | 7.0.2 |
Versions before 6.9.0 are not affected by this chain.
## How it works
The REST batch endpoint (`/batch/v1`) is unauthenticated and runs several sub-requests in one
call, relying on each sub-request being validated and permission-checked on its own.
`serve_batch_request_v1()` builds two parallel arrays โ `$matches` (the matched handler per
sub-request) and `$validation` (the validation result per sub-request) โ then indexes both by
the same offset when dispatching. A sub-request whose path fails `wp_parse_url()` is appended to
`$validation` but not to `$matches`, so the arrays fall out of step and a sub-request is
dispatched under a **different** sub-request's handler. That is the route confusion.
The PoC nests the primitive twice:
1. A `POST /wp/v2/posts` request that carries a `requests` body is dispatched under the batch
handler itself. Having been validated as a posts request, its `requests` list is never checked
against the batch schema, so its sub-requests may use `GET` โ the method allow-list is
bypassed.
2. Inside that inner batch, a `GET /wp/v2/users` request carrying an `author_exclude` string
(the users schema has no such parameter, so the value passes validation untouched) is
dispatched under posts `get_items()`. There `author_exclude` maps to the `WP_Query`
`author__not_in` query var, which the vulnerable build interpolates into SQL as a string.
The result is a boolean- and time-based blind SQL injection reachable pre-authentication. With
database read access the administrator password hash can be recovered; once cracked, admin access
yields code execution through a plugin upload.
## Requirements
Python 3.8+ and the standard library. No third-party dependencies.
## Usage
Run it from the repository directory:
```
./wp2shell.py [options]
```
Or `pip install .` to get a `wp2shell` command on your `PATH`.
### check โ confirm the vulnerability (safe)
Confirms exploitability with a differential time delay. It reads no data and changes nothing.
```
./wp2shell.py check http://target
```
### read โ extract data (blind SQL injection)
```
./wp2shell.py read http://target # server fingerprint
./wp2shell.py read http://target --preset users # user logins and password hashes
./wp2shell.py read http://target --query "SELECT @@version"
```
### shell โ execute a command (remote code execution)
Optional post-exploitation. Requires valid administrator credentials; the injection recovers the
password *hash*, so supply the recovered plaintext here.
```
./wp2shell.py shell http://target --user admin --password '' --cmd id
./wp2shell.py shell http://target --user admin --password '' -i # interactive shell
```
`shell` uploads a plugin webshell (locked behind a random path and a per-run token) and prints its
path. Remove it when finished.
## Options
| Option | Applies to | Description |
| ------------------- | ---------- | -------------------------------------------------------------------- |
| `--rest-route` | all | Use `/?rest_route=/batch/v1` (for sites without pretty permalinks). |
| `--proxy URL` | all | Route traffic through an HTTP proxy (for example, Burp). |
| `--timeout N` | all | Request timeout in seconds. |
| `--sleep N` | check | Delay used to confirm the injection. |
| `--preset` | read | `fingerprint` or `users`. |
| `--query` | read | A scalar SQL expression to read. |
| `--prefix` | read | Database table prefix (default `wp_`). |
| `--max-length N` | read | Maximum characters read per value (default 128). |
| `--user` / `--password` | shell | Admin credentials (plaintext, recovered from the hash). |
| `--cmd` | shell | Command to run (omit when using `-i`). |
| `-i` / `--interactive` | shell | Open an interactive shell after deploying. |
## Remediation
Update to WordPress 6.9.5 or 7.0.2. Until then, block both `/wp-json/batch/v1` and the
`rest_route=/batch/v1` query parameter at the edge, or require authentication for the batch
endpoint via the `rest_pre_dispatch` filter.
## Legal
For authorized security testing only. Use it exclusively against systems you own or have explicit
written permission to test. The author accepts no liability for misuse.
## References
- WordPress 7.0.2 release announcement โ
- CVE-2026-63030