
wp2shell — WordPress Core Pre-Auth RCE (CVE-2026-63030 + CVE-2026-60137). Exploit toolkit + remediation.
CVE-2026-63030 (Batch Route Confusion, CVSS 7.5) + CVE-2026-60137 (SQL Injection, CVSS 9.1)
A pre-authentication remote code execution chain in WordPress Core that requires no plugins, no special configuration, and works on default installs.
| Version Range | Impact | Fixed In |
|---|---|---|
| WordPress 7.0.0 – 7.0.1 | Full RCE | 7.0.2 |
| WordPress 6.9.0 – 6.9.4 | Full RCE | 6.9.5 |
| WordPress 6.8.0 – 6.8.5 | SQL Injection only | 6.8.6 |
Precondition: No persistent object cache (Redis/Memcached). This is the default configuration for the vast majority of WordPress installations.
The exploit chains two vulnerabilities:
REST API Batch Route Confusion — A malformed path in a batch sub-request causes wp_parse_url() to return false, creating a WP_Error that desynchronizes the $matches[] and $requests[] arrays. Subsequent requests are dispatched against wrong handlers, bypassing authentication.
SQL Injection in WP_Query — When author__not_in is passed as a string (not array), absint() sanitization is skipped and the raw value is interpolated directly into the SQL WHERE clause.
Combined with WordPress's oEmbed caching system (write primitive), Customizer changeset auto-publishing (privilege escalation), and REST API re-entrancy (privileged dispatch), this achieves unauthenticated code execution.
wp2shell/
├── README.md ← This file
│
├── wp2shell-exploit/ ← Exploitation tools
│ ├── exploit.py # Full pre-auth RCE (no password cracking)
│ ├── exploit_hash.py # Hash extraction + authenticated RCE
│ ├── detect.py # Non-destructive vulnerability scanner
│ └── README.md
│
├── wp2shell-patch/ ← Remediation
│ ├── patch.sh # Source code patch (mirrors official fix)
│ ├── wp2shell-shield.php # Drop-in mu-plugin (30-second deploy)
│ ├── block-batch.conf # Nginx mitigation
│ ├── block-batch.htaccess # Apache mitigation
│ └── README.md
│
├── docker-compose.yml # Vulnerable test environment (WP 7.0.1)
└── Dockerfile.debug # XDebug-enabled image for research
WordPress source is not included. Download from https://wordpress.org/download/releases/ (7.0.1 for vulnerable, 7.0.2 for patched).
cd wp2shell-exploit
# Single target
python3 detect.py https://target.example
# With SQL injection timing confirmation
python3 detect.py https://target.example --confirm-sqli
# Batch scan from file
python3 detect.py targets.txt -q
# Full pre-auth RCE (recommended — no password cracking needed)
python3 exploit.py https://target.example -c "id"
# Just extract data via blind SQLi
python3 exploit.py https://target.example "SELECT user_login FROM wp_users LIMIT 1"
# Alternative: extract hash + crack + auth RCE
python3 exploit_hash.py https://target.example
# Then after cracking:
python3 exploit_hash.py https://target.example --user admin --pass cracked_pw -c "id"
cd wp2shell-patch
# Option 1: Drop-in plugin (fastest, no restart needed)
cp wp2shell-shield.php /path/to/wordpress/wp-content/mu-plugins/
# Option 2: Web server block
# Nginx: include block-batch.conf in server block
# Apache: prepend block-batch.htaccess to .htaccess
# Option 3: Source patch (complete fix)
sudo bash patch.sh /path/to/wordpress
# Best option: just update WordPress
wp core update # or Dashboard → Updates
┌─────────────────────────────┐
│ Anonymous HTTP Request │
│ POST /?rest_route=/batch/v1 │
└──────────────┬──────────────┘
│
┌──────────────▼──────────────┐
│ Batch Desync (outer) │
│ Malformed path → WP_Error │
│ $matches[] array shifts │
└──────────────┬──────────────┘
│
┌──────────────▼──────────────┐
│ Steal /batch/v1 handler │
│ (no permission_callback!) │
│ → nested batch executes │
└──────────────┬──────────────┘
│
┌──────────────▼──────────────┐
│ Batch Desync (inner) │
│ GET methods now allowed │
│ author_exclude unsanitized │
└──────────────┬──────────────┘
│
┌────────────────────┼────────────────────┐
│ │ │
┌──────────▼──────────┐ ┌──────▼──────┐ ┌──────────▼──────────┐
│ Phase 1: oEmbed │ │ Phase 2: │ │ Phase 3: Escalation │
│ UNION SELECT fake │ │ Blind SQLi │ │ Cache poison + │
│ post with [embed] │ │ extract IDs │ │ Changeset publish │
│ → WP creates cache │ │ + admin ID │ │ → wp_set_current_ │
│ posts (write prim.) │ │ │ │ user(admin) │
└─────────────────────┘ └─────────────┘ └──────────┬──────────┘
│
┌──────────────▼──────────────┐
│ Re-entrancy │
│ parse_request triggers │
│ serve_request() re-entry │
│ → now running as admin! │
└──────────────┬──────────────┘
│
┌──────────────▼──────────────┐
│ POST /wp/v2/users │
│ Creates new administrator │
│ → Login → Plugin → Shell │
└─────────────────────────────┘
WordPress 6.9.5 / 7.0.2 applies three fixes that each break one link in the chain:
WordPress 7.0.2 additionally removes the collaboration feature (defense in depth).
# Start vulnerable WordPress 7.0.1
docker compose up -d
# Wait for MySQL to init, then install
curl -s "http://localhost:8888/wp-admin/install.php?step=2" \
--data-urlencode "weblog_title=Test" \
--data-urlencode "user_name=admin" \
--data-urlencode "admin_password=TestPassword123" \
--data-urlencode "admin_password2=TestPassword123" \
--data-urlencode "[email protected]" \
--data-urlencode "blog_public=0" \
--data-urlencode "Submit=Install WordPress"
# Exploit
python3 wp2shell-exploit/exploit.py http://localhost:8888 -c "id"
# Clean up
docker compose down
This repository is provided for authorized security research, penetration testing, and educational purposes only. Use only on systems you own or have explicit written permission to test.
| Fix | File | Effect |
|---|
| Array alignment | class-wp-rest-server.php | $matches[] = $single_request for WP_Error entries — prevents desync |
| Re-entrancy guard | class-wp-rest-server.php + rest-api.php | if ($this->is_dispatching()) return false — prevents nested serve_request |
| SQL sanitization | class-wp-query.php | wp_parse_id_list() always applied — prevents injection |