6.1 KiB
wp2shell by: ek0ms savi0r
Unauthenticated Remote Code Execution for WordPress Core POC (CVE-2026-63030 + CVE-2026-60137)
wp2shell exploit for critical vulnerability chain in WordPress core. It chains a REST API batch route confusion bug with an SQL injection in WP_Query to achieve unauthenticated remote code execution on vulnerable WordPress installations.
| Detail | Info |
|---|---|
| CVEs | CVE-2026-63030 (REST batch route confusion) + CVE-2026-60137 (author__not_in SQLi) |
| Affected | WordPress 6.9.0 – 6.9.4, 7.0.0 – 7.0.1 |
| Patched | 6.9.5, 7.0.2 |
| Auth Required | None — unauthenticated |
| CVSS | Critical (RCE chain) |
| Discovered By | Adam Kues (Assetnote / Searchlight Cyber) |
| Dependencies | Python 3.8+ — standard library only |
How It Works
The exploit chains two bugs in WordPress core:
-
CVE-2026-60137 — SQL Injection – When
author__not_inis passed as a string instead of an array, WordPress skips theis_array()sanitisation guard and interpolates the raw value into aNOT IN (...)SQL clause. -
CVE-2026-63030 — Batch Route Confusion – The
/wp-json/batch/v1endpoint builds parallel$matchesand$validationarrays. A malformed sub-request is appended to$validationbut not$matches, causing a +1 index shift. Sub-request i gets dispatched with sub-request i+1's handler.
Chained together: A POST /wp/v2/posts request carrying an inner GET /wp/v2/users request with an author_exclude string bypasses both the method allow-list and input sanitisation. The string reaches WP_Query as author__not_in and gets interpolated into SQL — giving you unauthenticated blind SQL injection.
From there, the tool can:
- Extract the administrator password hash
- Authenticate as admin (once the hash is cracked)
- Upload a malicious plugin
- Execute arbitrary system commands
Features
| Feature | Description |
|---|---|
| Vulnerability Check | Safe time-delay probe — reads no data, changes nothing |
| Data Extraction | Blind SQL injection with binary-search optimisation for efficient extraction |
| User Hash Dump | Extract user_login and user_pass hashes from wp_users |
| Remote Code Execution | Authenticate as admin, upload a webshell plugin, execute commands |
| Interactive Shell | Persistent command execution session |
| Colour Output | Clear visual feedback for scan results |
| No Dependencies | Uses only Python's standard library |
Installation
git clone https://git.churchofmalware.org/ek0mssavi0r/wordpressTOWN.git
cd wordpressTOWN
chmod +x wp2shell.py
That's it. No third-party packages required.
Usage
./wp2shell.py <url> [command] [options]
Interactive Mode (Menu-Driven)
./wp2shell.py http://target.com
Launches an interactive menu with all functionality available via numbered options.
Commands
check — Confirm Vulnerability (Safe)
Performs a time-delay probe to confirm exploitability. Reads no data and changes nothing.
./wp2shell.py http://target.com check
read — Extract Data (Blind SQL Injection)
Extracts information from the database using time-based blind SQL injection.
# Server fingerprint (version, database, user)
./wp2shell.py http://target.com read
# Extract user logins and password hashes
./wp2shell.py http://target.com read --users
# Extract database name
./wp2shell.py http://target.com read --database
# Extract MySQL version
./wp2shell.py http://target.com read --version
# Custom SQL query
./wp2shell.py http://target.com read --query "SELECT @@version"
shell — Remote Code Execution
Requires valid administrator credentials. The SQL injection can recover the password hash, but you must supply the recovered plaintext password.
# Execute a single command
./wp2shell.py http://target.com shell --user admin --password 'recovered_pass' --cmd "id"
# Execute a single command (short form)
./wp2shell.py http://target.com shell --user admin --password 'recovered_pass' --cmd whoami
Options
| Option | Description |
|---|---|
--users |
Extract user_login and user_pass from wp_users |
--database |
Extract the current database name |
--version |
Extract the MySQL version |
--query "SQL" |
Execute a custom SQL query (blind extraction) |
--user USER |
Admin username for RCE |
--password PASS |
Admin password for RCE (plaintext, recovered via SQLi) |
--cmd CMD |
Command to execute via the webshell |
Example Workflow
# 1. Check if the target is vulnerable
./wp2shell.py https://example.com check
# 2. Extract admin password hashes
./wp2shell.py https://example.com read --users
# 3. Crack the hash offline (using hashcat, john, etc.)
# hashcat -m 400 <hash> /path/to/wordlist.txt
# 4. Execute a command with the recovered password
./wp2shell.py https://example.com shell --user admin --password 'cracked_pass' --cmd "id"
Affected Versions
| Branch | Affected | Fixed |
|---|---|---|
| 6.9.x | 6.9.0 – 6.9.4 | 6.9.5 |
| 7.0.x | 7.0.0 – 7.0.1 | 7.0.2 |
| 6.8.x | 6.8.0 – 6.8.5 (SQLi only, no RCE) | 6.8.6 |
Versions before 6.9.0 are not affected by the full RCE chain.
Detection & Mitigation
For Defenders:
- Update to 6.9.5, 7.0.2, or later immediately
- Cloudflare WAF customers are automatically protected
- Wordfence customers have firewall rules
- Monitor for
POST /wp-json/batch/v1requests with nestedrequestsbodies - A
404on the batch route doesn't always mean "patched" — it can also mean a WAF/CDN is blocking anonymous REST API access
Legal Disclaimer
This tool is for educational and authorised security testing purposes only.
Credits
- Discovery: Adam Kues (Assetnote / Searchlight Cyber)
- CVE-2026-60137 (SQLi): TF1T, dtro, haongo
- CVE-2026-63030 (Batch route confusion): Adam Kues
For more details on the vulnerability, see the Searchlight Cyber research blog.