Files
Icex0-wp2shell-poc/README.md
T
2026-07-18 12:23:05 +12:00

138 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# wp2shell-poc
Independent proof-of-concept for the unauthenticated WordPress REST batch route-confusion
SQL injection associated with Searchlight Cyber's wp2shell advisory.
The unauthenticated primitive reaches the injection through a single endpoint:
`POST /wp-json/batch/v1`.
This repository is not Searchlight Cyber's official checker and does not claim to reproduce
undisclosed wp2shell internals. `check` confirms the SQLi path, `read` demonstrates database
read, and `shell` is an optional post-authentication helper that requires valid administrator
credentials before uploading a plugin webshell.
## Affected versions
Searchlight Cyber's advisory lists these wp2shell RCE exposure ranges:
| Version range | Status |
| ------------- | ------ |
| <= 6.8.5 | Not affected |
| 6.9.0 6.9.4 | Affected |
| 7.0.0 7.0.1 | Affected |
## 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. This PoC
can use that database read path to recover administrator password hashes. Turning those hashes into
plugin-upload code execution is outside the unauthenticated primitive and depends on obtaining valid
administrator credentials.
Searchlight has not published the final jump from SQLi to pre-auth RCE. That may involve a
database file-write trick such as MySQL `OUTFILE`, or it may be something else in core. This repo
does not include that step.
## Requirements
Python 3.8+ and the standard library. No third-party dependencies.
## Usage
Run it from the repository directory:
```
./wp2shell.py <command> <url> [options]
```
Or `pip install .` to get a `wp2shell` command on your `PATH`.
### check — confirm the vulnerability (safe)
Confirms exploitability with paired differential time delays. It reads no data and changes
nothing. By default it sends three baseline/delayed pairs and decides on the median delta, which
is more reliable on noisy or rate-limited targets than a single timing comparison. If timing
confirmation fails, `check` also looks for passive public WordPress version hints from the REST API
generator, the homepage generator meta tag, and core asset `?ver=` query strings.
```
./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 — post-auth plugin webshell helper
Optional post-exploitation helper. This is not a pre-authentication RCE step: it requires valid
administrator credentials. If `read --preset users` recovers a password hash, supply the recovered
plaintext here after offline cracking.
```
./wp2shell.py shell http://target --user admin --password '<recovered>' --cmd id
./wp2shell.py shell http://target --user admin --password '<recovered>' -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 — either pass `--cleanup` to have it delete itself after running, or
delete the plugin manually.
## Options
| Option | Applies to | Description |
| ------------------- | ---------- | -------------------------------------------------------------------- |
| `--rest-route` | check, read | 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. |
| `--samples N` | check | Baseline/delayed timing pairs to compare (default 3). |
| `--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. |
| `--cleanup` | shell | Delete the webshell from the target when finished. |
## Remediation
Update to WordPress 7.0.2, or 6.9.5 if the site is on the 6.9 branch. 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. No warranty is provided and no liability is accepted for misuse.
## References
- WordPress 7.0.2 release announcement — <https://wordpress.org/news/2026/07/wordpress-7-0-2-release/>
- Searchlight Cyber wp2shell advisory — <https://slcyber.io/research-center/wp2shell-pre-authentication-rce-in-wordpress-core/>