wp2shell (CVE-2026-63030) : la RCE pré-auth qui touche le cœur
de WordPress
wp2shell (CVE-2026-63030) enchaîne une confusion de routes dans l'API REST de WordPress et une injection SQL jusqu'à la RCE pré-authentifiée, sur une install par défaut. Mécanisme réel, détection, mitigation.
own2pwn12 min de lectureMis à jour le

Combien de WordPress exposez-vous, vraiment ?
Notre EASM découvre vos instances, empreinte leurs versions et remonte celles qui traînent une CVE critique.
Cartographier ma surface WordPressUne seule requête. Un POST vers /wp-json/batch/v1, un corps JSON un peu biscornu, et le serveur répond un 200 OK parfaitement banal. Trente secondes plus tard, il y a un compte administrateur de plus dans la base, un plugin inconnu déposé sous wp-content/plugins/, et un shell qui répond à id. Pas de mot de passe. Pas de plugin tiers vulnérable. Pas de thème exotique. Juste WordPress, dans sa version par défaut. C'est wp2shell, et c'est la faille qui a fait tourner les équipes sécurité le 17 juillet 2026.
wp2shell check : votre site est-il vulnérable ?
Avant la dissection, la question pratique : votre WordPress est-il exposé ? Le vérificateur ci-dessous répond sans jamais toucher à la faille. Il empreinte la version du cœur depuis des sources publiques en lecture seule (balise generator, flux RSS, readme.html, index /wp-json/), la compare aux plages vulnérables, puis confirme la joignabilité de la route batch par un simple OPTIONS bénin. Aucun POST /wp-json/batch/v1, aucun payload d'exploitation : c'est la même logique que le détecteur open-source wp2shell-detect, durcie contre le SSRF et limitée en débit.
Détection seule, sans rien exploiter : on lit la version de votre WordPress et on la compare aux versions vulnérables (CVE-2026-63030). Un domaine que vous administrez. Rien n'est stocké.
wp2shell n'est qu'une faille sur un asset.
Combien d'autres WordPress, sous-domaines et services exposez-vous sans le savoir ? Découvrez votre surface d'attaque complète.
Cadre légal : à lire avant tout
wp2shell, c'est quoi au juste ?
Contrairement à ce que le nom laisse croire, wp2shell n'est pas un plugin, ni un énième outil "admin-vers-shell" qui suppose que vous ayez déjà les identifiants. C'est le nom de baptême donné par Searchlight Cyber (via son unité Assetnote) à une RCE pré-authentifiée dans le cœur de WordPress, divulguée le 17 juillet 2026. "Pré-auth" veut dire ce que ça veut dire : un attaquant anonyme, sans le moindre compte, obtient l'exécution de code sur le serveur.
Techniquement, wp2shell n'est pas une faille mais un enchaînement de deux bugs du noyau WordPress :
- CVE-2026-63030, une confusion de routes dans l'endpoint batch de l'API REST (
/wp-json/batch/v1). C'est le maillon qui transforme un bug "théorique" en RCE exploitable sans compte. - CVE-2026-60137, une injection SQL dans le traitement de
author__not_inparWP_Query.
Prises séparément, ces deux failles sont gênantes. Chaînées, elles donnent une RCE complète sur une installation standard, sans aucun composant tiers. La confusion de routes a été trouvée par Adam Kues (Assetnote / Searchlight Cyber) et remontée via le programme HackerOne de WordPress ; l'injection SQL a été rapportée en parallèle par les chercheurs TF1T, dtro et haongo. WordPress a réagi en forçant les mises à jour automatiques sur les installs concernées.
Le périmètre exact des versions :
Le bug de fond : la confusion de routes dans /wp-json/batch/v1
L'endpoint batch de l'API REST existe pour regrouper plusieurs appels en une seule requête HTTP : vous envoyez un tableau de sous-requêtes, WordPress les traite l'une après l'autre et vous renvoie un tableau de réponses. Pratique pour un front-end qui veut créer dix objets d'un coup. Le contrôleur, en interne, tient deux tableaux parallèles indexés en même temps : un pour la validation de chaque sous-requête, un pour le handler qui va la router.
Le bug tient en une phrase, celle du chercheur qui l'a reproduit : "une sous-requête dont le chemin échoue à wp_parse_url() est ajoutée à $validation mais pas à $matches, donc les tableaux se désynchronisent". Autrement dit : il suffit de glisser une seule sous-requête au chemin volontairement malformé pour décaler les deux tableaux d'un cran. À partir de là, chaque sous-requête suivante est validée sous un index, mais exécutée sous le handler du voisin. La validation de méthode et d'authentification s'applique à une route ; le code, lui, tourne pour une autre.
Schématiquement (reconstruction)
foreach sur le même tableau de sous-requêtes, mais l'un skip les chemins invalides et l'autre non. Un tableau avance de N, l'autre de N-1.wp_parse_url() mais reste comptée ici.La forme de la requête n'a rien de sorcier : c'est le JSON batch standard, avec un chemin cassé glissé au bon endroit. On en montre la structure (l'endpoint est public et documenté), pas un exploit fonctionnel :
POST /wp-json/batch/v1 HTTP/1.1
Host: cible
Content-Type: application/json
{
"requests": [
{ "method": "GET", "path": "chemin-volontairement-invalide" },
{ "method": "GET", "path": "/wp/v2/users?author_exclude=..." }
]
}À noter : la route batch est aussi joignable via ?rest_route=/batch/v1, ce qui compte pour la détection et le filtrage (les deux formes doivent être surveillées).
Du décalage d'index à l'injection SQL (CVE-2026-60137)
Le décalage sert à faire livrer une sous-requête au mauvais handler. Concrètement, l'exploit envoie un GET /wp/v2/users portant un paramètre author_exclude. Ce paramètre n'existe pas dans le schéma de l'endpoint users, et normalement il serait ignoré. Mais grâce à la confusion de routes, la requête est dispatchée vers le handler des posts, où author_exclude est bien reconnu et mappé sur la variable de requête author__not_in de WP_Query.
Et c'est là que ça casse. author__not_in est censé être une liste d'identifiants numériques. Le cœur attend des entiers ; l'exploit lui passe une chaîne, qui n'est pas normalisée et finit interpolée directement dans la requête SQL. Injection SQL, sur le compte utilisateur du serveur WordPress, sans authentification. Le correctif est tout aussi parlant que le bug : il force le passage par wp_parse_id_list(), qui coerce l'entrée en une liste d'entiers propres. Une ligne de type-safety oubliée ; c'est souvent tout ce qui sépare une API d'une injection.
author_exclude est valide et devient author__not_in.wp_users.Du "SQL" au "shell" : la partie "2shell"
Une injection SQL en lecture, c'est déjà une fuite de base complète. Mais wp2shell va jusqu'au bout : de la lecture SQL à l'exécution de code. La démarche, telle que documentée par le PoC public, exploite la mécanique interne de WordPress plutôt qu'une fonction dangereuse :
- Forger de fausses lignes
wp_postsvia une injection UNION, des lignes conçues pour être rendues à travers le mécanisme de cache oEmbed. - Récupérer les vrais identifiants des posts de cache par la même SQLi, puis les recaster en changesets du customizer et en éléments de menu de navigation.
- Créer un compte administrateur "fabriqué" en une seule requête batch empoisonnée.
- Se connecter avec cet admin et déposer un plugin webshell, l'étape qui donne l'exécution de commandes.
Une précondition qui limite la RCE (mais pas la SQLi)
Le code complet existe dans les PoC publics ; l'objectif ici est que vous compreniez la chaîne assez bien pour la reconnaître dans vos logs, pas pour la rejouer contre le premier WordPress venu.
wp2shell PoC : reproduire la chaîne en lab
Un PoC indépendant, Icex0/wp2shell-poc (licence MIT), automatise la chaîne dans un outil sobrement nommé wp2shell.py. Sur votre lab, ses sous-commandes racontent bien la progression : un test non destructif, une lecture de base, puis l'exécution :
# détection non destructive : la cible est-elle vulnérable ?
./wp2shell.py check http://lab-wordpress
# confirmer l'injection SQL (toujours en lecture)
./wp2shell.py check http://lab-wordpress --confirm-sqli
# extraire des données de la base (union / error / blind)
./wp2shell.py read http://lab-wordpress --preset users
# exécution de commande via le pont pré-auth vers l'admin
./wp2shell.py shell http://lab-wordpress --cmd id
./wp2shell.py shell http://lab-wordpress -i # shell interactifSearchlight Cyber a aussi mis en ligne un vérificateur non offensif, wp2shell.com, qui dit simplement si une instance est exposée. Côté outillage défensif, la divulgation a déjà déclenché des templates Nuclei de détection et des règles de vérification authentifiées chez les éditeurs de scanners (Rapid7, entre autres). En revanche, au moment où on écrit, aucun module Metasploit ni entrée Exploit-DB spécifique à CVE-2026-63030 n'est confirmé. Attention à ne pas confondre avec le vieux module générique wp_admin_shell_upload, qui suppose, lui, d'être déjà admin.
Ne confondez pas wp2shell et WP-SHELLSTORM
CVE-2026-3844) en déposant des webshells type down.php. C'est une campagne d'exploitation de bugs connus, sans aucun rapport avec wp2shell : failles différentes, cœur non concerné. Les deux noms finissent juste en "shell".Détecter l'exposition en blackbox, sans l'exploiter
Bonne nouvelle pour qui doit auditer un parc : toute la chaîne est conditionnée par la version du cœur. Inutile de tirer un payload pour savoir si une instance est exposée ; il suffit de lire sa version et de la comparer aux plages vulnérables. C'est exactement la logique de notre détecteur open-source, wp2shell-detect (Python, zéro dépendance, détection seule). On la déroule étape par étape.
1. Empreinter la version du cœur
WordPress laisse fuiter sa version par plusieurs canaux publics, en lecture seule. On les interroge et on croise :
- La balise generator de la page d'accueil :
<meta name="generator" content="WordPress 6.9.3">. - Le flux RSS (
/feed/) :?v=6.9.3dans la balise<generator>. /readme.html, qui afficheVersion 6.9.3en clair.- L'index
/wp-json/, parfois bavard sur la version dans sa description.
Le parsing doit être strict : on n'accepte qu'un X.Y ou X.Y.Z purement numérique, sinon on renvoie "inconnu" plutôt que de deviner. Une version mal lue, c'est un faux positif (ou pire, un faux négatif) garanti.
# plages vulnérables (inclusives), d'après la divulgation
RCE_RANGES = [((6, 9, 0), (6, 9, 4)), ((7, 0, 0), (7, 0, 1))]
SQLI_RANGES = [((6, 8, 0), (6, 8, 5))] # SQLi seule, pas de RCE
# corrigé dans 6.8.6 / 6.9.5 / 7.0.2
def parse_version(v):
m = re.match(r"^(\d+)\.(\d+)(?:\.(\d+))?$", v.strip()) # strict
return (int(m[1]), int(m[2]), int(m[3] or 0)) if m else None
def verdict(v):
if any(lo <= v <= hi for lo, hi in RCE_RANGES): return "RCE pré-auth"
if any(lo <= v <= hi for lo, hi in SQLI_RANGES): return "injection SQL"
return "hors plage (probablement patché)"Le cas de l'install durcie
readme.html. La version devient alors invisible en blackbox pur. Notre détecteur le dit franchement ("WordPress confirmé, version masquée") au lieu de bluffer : confirmer wp2shell sans la version reviendrait à l'exploiter, ce qu'on refuse de faire pour une simple détection. Le réflexe sain : patcher quand même.2. Confirmer que la route batch est joignable
La version dit "vulnérable" ; reste à savoir si le point d'entrée de la chaîne est exposé ou déjà filtré au bord. On le vérifie sans jamais l'actionner, avec une requête OPTIONS : le routeur REST renvoie le schéma de la route (les méthodes autorisées) sans exécuter le handler. Un 200 avec Allow: POST confirme que /wp-json/batch/v1 répond ; un 403 signale qu'une mitigation est déjà posée.
# OPTIONS = introspection de la route, aucun handler déclenché, zéro payload
curl -s -X OPTIONS -i http://cible/wp-json/batch/v1 | grep -i '^Allow:'
# -> Allow: POST (route batch exposée) | 403/401 (filtrée au bord)3. Le verdict
Sur un parc entier, en une commande :
git clone https://github.com/own2pwn-fr/wp2shell-detect && cd wp2shell-detect
./wp2shell_detect.py --targets parc.txt # une URL (ou host) par ligne
# [!!] https://blog.exemple.fr -> VULNERABLE (pre-auth RCE) 6.9.3 CVE-2026-63030Le code sort en statut 2 dès qu'une cible est vulnérable : pratique pour un balayage nocturne en CI ou en cron. Rien dans tout ça ne touche à la faille ; ce sont des requêtes qu'un crawler banal pourrait émettre.
Détecter l'exploitation dans vos logs
La détection d'exposition répond à "suis-je vulnérable". Vos logs, eux, répondent à "suis-je attaqué". Aucun éditeur n'a publié de liste d'IOC définitive : la faille a quelques jours. Mais le mécanisme dicte ses propres signatures. Ce qu'il faut chasser :
- Des requêtes anonymes sur l'endpoint batch :
POST /wp-json/batch/v1ou?rest_route=/batch/v1sans session admin. L'usage anonyme légitime de cette route est rare : traitez-le comme suspect par défaut. - Des corps batch avec un tableau
requestsdont un chemin est manifestement malformé (le déclencheur de désynchro). - Un
author_excludesur/wp/v2/users(illégitime pour ce schéma), surtout si sa valeur contient de la syntaxe SQL (UNION, apostrophes, marqueurs de commentaire). - Des indices post-exploitation : un compte administrateur apparu de nulle part, des lignes
wp_postsanormales liées au cache oEmbed, auxcustomize_changesetou auxnav_menu_item, et surtout un nouveau plugin avec un fichier PHP inconnu souswp-content/plugins/.
Deux passes simples sur vos accès et votre système de fichiers dégrossissent le travail :
# 1) hits anonymes sur la route batch (les deux formes)
grep -E '/wp-json/batch/v1|rest_route=/batch/v1' access.log \
| grep -iv 'wp-admin' | less
# 2) author_exclude passé à l'endpoint users (n'a rien à y faire)
grep -E '/wp/v2/users' access.log | grep -i 'author_exclude'
# 3) plugins déposés récemment (webshell suspect)
find wp-content/plugins -name '*.php' -mtime -7 -printf '%T+ %p\n' | sortMitiger : patcher d'abord, filtrer ensuite
La seule vraie réponse, c'est le correctif. Passez en WordPress 7.0.2 ou 6.9.5 (ou 6.8.6 pour la branche 6.8, qui ne portait que la SQLi). WordPress force les mises à jour automatiques sur les installs concernées, mais ne comptez pas dessus : vérifiez. Rapid7 le dit sans détour, les contournements ne remplacent pas le patch. Et une fois le feu éteint, profitez-en pour faire le tour du durcissement WordPress dans son ensemble (mises à jour automatiques, comptes, extensions, API REST) : c'est l'objet de sécuriser un WordPress, la prochaine CVE du cœur ne préviendra pas plus que celle-ci.
Si vous ne pouvez vraiment pas patcher tout de suite, on filtre au bord. Bloquez l'endpoint batch au niveau du reverse-proxy, dans ses deux formes :
# nginx : couper l'accès anonyme à l'API batch en attendant le patch
location = /wp-json/batch/v1 { return 403; }
# et la forme via query string
if ($arg_rest_route = "/batch/v1") { return 403; }Plus fin, sans casser un éventuel usage légitime authentifié : un mu-plugin (must-use) qui rejette les requêtes batch anonymes avant qu'elles n'atteignent le contrôleur. À déposer dans wp-content/mu-plugins/ :
<?php
// wp-content/mu-plugins/000-block-anon-batch.php
// Mesure temporaire : rejette /wp-json/batch/v1 pour les non-connectés.
add_filter('rest_pre_dispatch', function ($result, $server, $request) {
if (strpos($request->get_route(), '/batch/v1') === 0 && !is_user_logged_in()) {
return new WP_Error('rest_forbidden', 'Batch API disabled', ['status' => 403]);
}
return $result;
}, 0, 3);Enfin, restreignez l'accès anonyme à l'API REST de façon générale (via un plugin de sécurité) et, en post-incident, auditez les comptes admin, les plugins inconnus, faites tourner les salts et les identifiants, et passez en revue les tables de cache/oEmbed. Cette faille est un rappel utile : la gestion des vulnérabilités ne se joue pas au moment de la CVE, mais dans votre capacité à savoir, en quelques minutes, où vous tournez la version vulnérable.
Ce que wp2shell dit de votre surface d'attaque externe
Le vrai piège avec une CVE cœur de WordPress, ce n'est pas le WordPress que vous administrez et surveillez. C'est l'autre : le blog corporate monté il y a trois ans pour une campagne, le sous-domaine events. d'une filiale, l'install de test qu'un prestataire n'a jamais éteinte. Ces instances-là ne sont dans aucun inventaire, et ce sont exactement celles qui traînent en 6.9.3 pendant que vos assets connus, eux, sont patchés le jour même. Impossible de corriger ce que l'on ne sait pas exposer : c'est tout le sujet de la découverte d'assets.
C'est précisément le travail d'une plateforme d'External Attack Surface Management : découvrir en continu tous vos WordPress exposés (y compris ceux que personne ne gère), empreinter leur version, et remonter en priorité ceux qui portent une CVE critique comme wp2shell. Un scan qui teste /wp-json/batch/v1 et compare la version détectée au seuil corrigé transforme une divulgation de vendredi soir en une liste d'URLs à patcher lundi matin. C'est ce que fait notre EASM. Et là où la détection automatisée s'arrête, un pentest web blackbox confirme l'exploitabilité réelle, sans faux positif. Pour la frontière exacte entre les deux, on l'a détaillée dans pentest vs scan de vulnérabilité, et le contexte plus large (pourquoi et quand faire tester son parc) dans le pentest en entreprise.
À retenir
- wp2shell (
CVE-2026-63030) est une RCE pré-authentifiée du cœur de WordPress, pas un plugin : aucune dépendance tierce, install par défaut suffisante. - Elle chaîne deux bugs : une confusion de routes dans
/wp-json/batch/v1(désync$validation/$matches) et une injection SQL viaauthor__not_in(CVE-2026-60137). - Versions à risque de RCE : 6.9.0–6.9.4 et 7.0.0–7.0.1. La 6.8.x n'a que la SQLi. Correctifs : 6.8.6 / 6.9.5 / 7.0.2.
- Détection : hits anonymes sur la route batch,
author_excludesur/wp/v2/users, nouveaux comptes admin et plugins PHP inconnus. - Mitigation : patcher avant tout ; à défaut, bloquer
/wp-json/batch/v1(WAF ou mu-pluginrest_pre_dispatch). Un objet-cache externe gêne la RCE mais laisse la fuite SQL. - Le risque réel, c'est le WordPress que vous ne savez pas exposer : la découverte d'assets et l'empreinte de version font la différence entre un patch le lundi et une compromission.
Vous ne savez pas combien d'instances WordPress portent votre nom sur internet ? C'est exactement la première chose que remonte own2pwn. Sinon, parlez d'une exposition précise avec un humain via la page contact.
Questions fréquentes sur wp2shell
Qu'est-ce que wp2shell (CVE-2026-63030) ?
wp2shell est une RCE pré-authentifiée du cœur de WordPress, divulguée le 17 juillet 2026 par Searchlight Cyber. Ce n'est pas un plugin : une installation par défaut suffit, sans compte ni composant tiers. Elle chaîne deux bugs : une confusion de routes dans l'endpoint /wp-json/batch/v1 (CVE-2026-63030) et une injection SQL via author__not_in (CVE-2026-60137), jusqu'à l'exécution de code sur le serveur.
Quelles versions de WordPress sont vulnérables à wp2shell ?
La RCE complète touche les branches 6.9.0 à 6.9.4 et 7.0.0 à 7.0.1. Les versions 6.8.0 à 6.8.5 ne portent que l'injection SQL, sans la prise de shell. Les correctifs sont WordPress 6.8.6, 6.9.5 et 7.0.2. En dessous de 6.8.0, la chaîne n'est pas exploitable. WordPress a forcé les mises à jour automatiques sur les installations concernées, mais mieux vaut vérifier plutôt que compter dessus.
Comment savoir si mon site WordPress est vulnérable à wp2shell ?
Toute la chaîne dépend de la version du cœur : inutile de tirer un payload. Il suffit de lire la version exposée (balise generator, flux RSS, readme.html, index /wp-json/) et de la comparer aux plages vulnérables. Notre vérificateur en ligne et le détecteur open-source wp2shell-detect automatisent cette lecture sans jamais exploiter la faille. Une install durcie qui masque sa version doit être patchée par précaution.
Comment se protéger de wp2shell ?
La seule vraie réponse est le correctif : passez en WordPress 7.0.2, 6.9.5 ou 6.8.6. Si vous ne pouvez pas patcher immédiatement, bloquez l'endpoint /wp-json/batch/v1 au reverse-proxy, dans ses deux formes (chemin direct et ?rest_route=/batch/v1), ou déposez un mu-plugin qui rejette les requêtes batch anonymes. Un cache d'objets externe gêne la prise de shell mais laisse la fuite SQL : seul le patch corrige les deux.
wp2shell et WP-SHELLSTORM, est-ce la même chose ?
Non. WP-SHELLSTORM est une campagne de revente d'accès qui a backdooré des sites via 27 vieilles failles de plugins, sans rapport avec le cœur de WordPress. wp2shell est une vulnérabilité du cœur lui-même, sur une installation par défaut. Les deux noms finissent en "shell" et circulent à la même période, mais ils ne partagent ni les failles, ni le vecteur.
Existe-t-il un exploit public pour wp2shell ?
Un PoC indépendant existe sur GitHub (Icex0/wp2shell-poc) et automatise la chaîne à des fins de lab. Des templates Nuclei et des vérifications authentifiées ont été publiés côté défensif. En revanche, au moment de la rédaction, aucun module Metasploit ni entrée Exploit-DB spécifique à CVE-2026-63030 n'est confirmé. L'éditeur d'origine a volontairement retenu les payloads prêts à l'emploi, et cet article fait de même.
Veille sécurité
La suite, une fois par mois
Ce qui bouge vraiment sur la surface d'attaque externe, NIS2 et la sécurité applicative, écrit par le pentester qui signe ces articles. Un envoi par mois, désinscription en un clic.
Votre adresse ne sert qu'à cet envoi. Voir la politique de confidentialité.
Articles liés
appsec
Audit de sécurité informatique : grille, exemple de rapport et prix
Audit de sécurité informatique : ce que chaque type d'audit prouve vraiment, une grille de contrôles concrète, un extrait de rapport anonymisé et les prix affichés. Le volet technique, sans jargon.
appsec
Pentest en entreprise : pourquoi, quand et quel type commander
Pourquoi une entreprise commande un pentest : risque, conformité NIS2 et DORA, exigences clients. Quand le faire, quel type choisir (black, grey, white box, API, red team) et comment cadrer un prestataire.
appsec
Sécurisation WordPress : durcir un site que tout le monde attaque
Une install WordPress par défaut livre sa version et le login de son admin à qui demande. On énumère un site de test en réel, on liste 5 CVE de plugins 2025, puis on durcit et on mesure l'écart.