Threat Intelligence

wp2shell : de la confusion de route REST API à la RCE pré-auth sur WordPress Core (CVE-2026-63030 / CVE-2026-60137)

Admin CyberAfrik 25 July 2026 25 lectures
wp2shell : de la confusion de route REST API à la RCE pré-auth sur WordPress Core (CVE-2026-63030 / CVE-2026-60137)

Rapport CTI technique du 25 juillet 2026

Résumé exécutif

Une chaîne de deux vulnérabilités du cœur de WordPress, baptisée wp2shell par la communauté, permet à un attaquant non authentifié d'obtenir une exécution de code à distance sur toute installation WordPress 6.9.x ou 7.0.x exposée. La première faille, CVE-2026-63030, provient d'un bug de synchronisation d'index dans le traitement des requêtes REST groupées : quand une sous-requête échoue à l'analyse d'URL, le tableau des handlers se décale d'une position par rapport au tableau des requêtes, ce qui fait atterrir une requête sur un handler qui ne lui était pas destiné. Ce détournement de route contourne la validation de schéma censée forcer le paramètre author__not_in à rester un tableau d'entiers, ouvrant la porte à CVE-2026-60137, une injection SQL dans WP_Query. La chaîne complète ne s'arrête pas à l'exfiltration de données : les chercheurs de Wiz et Picus Security ont documenté un chemin allant jusqu'à la création forgée d'un compte administrateur, sans craquage de mot de passe, suivie du dépôt d'un plugin malveillant faisant office de webshell.

Les deux CVE ont été corrigées le 17 juillet 2026 dans WordPress 6.8.6, 6.9.5 et 7.0.2, avec mise à jour automatique forcée sur la majorité du parc via WordPress.org. Des preuves de concept publiques ont circulé en quelques heures, en partie développées avec l'aide d'outils IA selon les chercheurs à l'origine de la découverte, et l'exploitation active a été confirmée par plusieurs éditeurs (Wiz, Tenable, Patchstack, Hexastrike) avant l'ajout au catalogue CISA KEV le 21 juillet. Pour toute installation encore en 6.8.x, 6.9.x ou 7.0.x non patchée à ce jour, le risque est un rachat complet du site en une poignée de requêtes HTTP, sans authentification préalable.

Chronologie

DateÉvénement
17/07/2026Publication des advisories GHSA-ff9f-jf42-662q et GHSA-fpp7-x2x2-2mjf, sortie des correctifs 6.8.6 / 6.9.5 / 7.0.2, mise à jour automatique forcée sur WordPress.org
17-19/07/2026Preuves de concept publiques sur GitHub en quelques heures ; premières tentatives d'exploitation observées en honeypot par Hexastrike dès le week-end suivant
20/07/2026Publication de l'analyse technique complète par Searchlight Cyber ; blogs techniques Tenable et Wiz ; Patchstack confirme une exploitation active sur le terrain
21/07/2026Ajout des deux CVE au catalogue CISA KEV ; échéance de remédiation fédérale fixée au 24/07 pour CVE-2026-63030 et au 04/08 pour CVE-2026-60137
22/07/2026Wiz publie sa liste d'indicateurs de compromission ; IONIX documente les deux fiches CVE en détail
25/07/2026Exploitation toujours active contre le parc non patché, sujet largement relayé par la presse spécialisée

Fiche vulnérabilité

CVE-2026-63030 | CVSS 3.1 : 9.8 (CNA WPScan) / 7.5 (CISA-ADP) | WordPress Core, contrôleur REST batch (/wp-json/batch/v1)

Vecteur AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H selon le score WPScan. Concerne les versions 6.9.0 à 6.9.4 et 7.0.0 à 7.0.1 (la route batch v1 n'existe pas avant 6.9). Corrigée en 6.9.5, 7.0.2 et 7.1 beta2. Découverte par Adam Kues (Searchlight Cyber). PoC public disponible, notamment sur le dépôt Icex0/wp2shell-poc. Patch : mise à jour vers une version corrigée, ou blocage temporaire de l'endpoint /wp-json/batch/v1 et ?rest_route=/batch/v1 au niveau du WAF en attendant.

CVE-2026-60137 | CVSS 3.1 : 9.1 (CISA-ADP, chaîne complète) / 5.9 (CNA WPScan, injection isolée) | WordPress Core, WP_Query, paramètre author__not_in (CWE-89)

Concerne les versions 6.8.0 à 6.8.5 pour l'injection isolée (authentification requise dans ce cas), et 6.9.0 à 7.0.1 pour la chaîne complète pré-authentification une fois combinée à CVE-2026-63030. Découverte par les chercheurs TF1T, dtro et haongo. Corrigée en 6.8.6, 6.9.5, 7.0.2 et 7.1 beta2. PoC public disponible. Patch : mise à jour immédiate, ou désactivation de l'accès REST anonyme en mitigation temporaire.

Diagramme de la chaîne d'attaque

Chaîne d'exploitation wp2shell, de la confusion de route REST à la RCE

Chaîne d'exploitation en quatre étapes : confusion de route REST, injection SQL et empoisonnement d'objets, forgeage d'un compte administrateur, dépôt du webshell. Sources : Wiz Research, Picus Security, Rapid7.

Analyse technique

Étape 1 : la confusion d'index dans le traitement batch REST

WP_REST_Server::serve_batch_request_v1() maintient trois tableaux parallèles indexés par position pour traiter un lot de sous-requêtes : $requests, $matches et $validation. Lorsqu'une sous-requête échoue à wp_parse_url() et renvoie un WP_Error, le code place bien l'erreur dans $validation, mais omet d'insérer un placeholder correspondant dans $matches. À partir de cette requête défaillante, $matches se retrouve décalé d'un cran par rapport à $requests. La boucle d'exécution associe ensuite chaque requête à l'indice $i avec le handler $matches[$i] : toute requête placée après l'erreur récupère donc le callback, le permission_callback et le schéma de validation d'une requête voisine plutôt que les siens.

Étape 2 : de la confusion de route à l'injection SQL

Le contrôleur REST des articles (posts) expose un paramètre author_exclude, mappé côté serveur sur la variable de requête author__not_in. En temps normal, le schéma REST valide ce paramètre comme un tableau d'entiers avant qu'il n'atteigne WP_Query. La confusion de route contourne cette validation : une requête peut désormais atteindre le handler posts sans être passée par son propre schéma, et le paramètre arrive côté WP_Query sous forme de scalaire brut. Or la sanitisation de WP_Query ne s'exécute que dans la branche if ( is_array( $query_vars['author__not_in'] ) ) : un scalaire échappe entièrement à array_map('absint', ...) et se retrouve interpolé tel quel dans la clause WHERE ... post_author NOT IN (...), ouvrant une injection SQL de type UNION exploitable sans authentification via un appel batch construit pour être récursif.

Étape 3 : de l'injection SQL au compte administrateur forgé

La chaîne documentée par Picus Security et Rapid7 ne passe pas par un vol puis un craquage de hash de mot de passe. WP_Query convertit les lignes retournées en objets WP_Post mis en cache ; l'injection permet de forger des lignes arbitraires, donc des objets WP_Post empoisonnés (identifiant, statut, type, parent, auteur). Ces objets sont réécrits en base via le chemin de sauvegarde du cache oEmbed, qui réutilise get_post(). En exploitant la routine de réparation des boucles parent-enfant sur des sauvegardes imbriquées, l'attaquant publie d'abord un changeset Customizer forgé dont l'auteur mémorisé est l'identifiant d'un administrateur choisi, ce qui fait brièvement basculer l'utilisateur courant sur ce compte. Une seconde sauvegarde imbriquée est construite pour que la combinaison status et type reconstitue l'action parse_request, déclenchant le chargeur REST interne pendant que le contexte administrateur est encore actif. Une requête POST /wp-json/wp/v2/users, initialement rejetée en 401 dans le lot batch d'origine, est alors réévaluée sous cette identité et aboutit en 201 : un compte administrateur existe désormais, sans qu'aucun mot de passe n'ait été deviné ni craqué.

Étape 4 : dépôt du webshell et persistance

Une fois connecté avec le compte forgé sur /wp-admin/, l'opérateur observé par Wiz Research charge un plugin malveillant via /wp-admin/plugin-install.php?tab=upload puis POST /wp-admin/update.php?action=upload-plugin. Les échantillons récupérés vont du simple one-liner eval($_POST[...]) à un faux plugin de 150 Ko déguisé en outil légitime, dont le code est obfusqué par une chaîne eval(gzuncompress(base64_decode(...))), jusqu'à un plugin enregistrant sa propre route REST avec un permission_callback fixé à __return_true et exécutant passthru(base64_decode(...)) à la demande. Une activité annexe a été observée sur certaines cibles : énumération via /wp-json/wp/v2/users?context=edit et tentatives de LFI par admin-ajax.php?template=../../../wp-config.

Étape 5 : reconnaissance et détection en masse

En amont de cette chaîne, Hexastrike a détecté des vagues de scan automatisé cherchant des sites vulnérables dès le week-end suivant la disclosure, avec des user-agents caractéristiques (wp2shell, rezwp2shell) et un IP de scan de masse isolé (172.235.128.52). Les réponses HTTP en 207/200 Multi-Status sur /wp-json/batch/v1 constituent un indicateur de détection fiable de ce type de sondage.

Indicateurs de compromission

TypeValeur
Hash SHA-1 webshell2a1410d8e2a8337ac2171cedea8c0fdc47c647a0
Hash SHA-1 webshell58eca847e9eae9e6b08cc211f1559817b71bc4cc
Hash SHA-1 webshellebea44890f434d5d67ede22009a3f4bb5cac33f8
Hash SHA-1 webshelld9a220c8039f1c4d72cae7ccb8b3a33dec8815be
Hash SHA-1 webshelle9756e2338f84746007235e4cab7a70d5b3ca47f
IP d'exploitation34.81.132.62
IP d'exploitation79.177.131.206
IP d'exploitation15.157.135.170
IP d'exploitation94.100.52.128
IP de scan de masse172.235.128.52
User-agent outilwp2shell / rezwp2shell
Route REST malveillante/morning/v1/... (permission_callback forcé à true)
Signal réseauRéponses 207/200 Multi-Status répétées sur /wp-json/batch/v1

MITRE ATT&CK

Aucune cartographie officielle éditeur n'a été publiée pour cette chaîne à la date de rédaction. Le tableau ci-dessous est notre propre lecture technique à partir des rapports Wiz, Picus et Rapid7, pas une attribution vendeur.

TactiqueTechniqueIDJustification
ReconnaissanceActive Scanning: Vulnerability ScanningT1595.002Vagues de scan automatisées avec user-agents dédiés dès le lendemain de la disclosure
Initial AccessExploit Public-Facing ApplicationT1190Exploitation directe de l'endpoint REST public /wp-json/batch/v1
Privilege EscalationExploitation for Privilege EscalationT1068Élévation vers un contexte administrateur via le détournement des sauvegardes de cache oEmbed/Customizer, sans identifiants valides
PersistenceCreate Account: Local AccountT1136.001Création d'un compte administrateur WordPress forgé via l'injection
PersistenceServer Software Component: Web ShellT1505.003Upload d'un plugin malveillant agissant comme webshell PHP
Command and ControlWeb ServiceT1102Routes REST personnalisées enregistrées par le plugin malveillant pour exécuter des commandes à la demande

Remédiation : checklist opérationnelle

  1. Mettre à jour immédiatement vers WordPress 6.8.6, 6.9.5 ou 7.0.2 (ou 7.1 beta2 sur les environnements de test).
  2. Si la mise à jour immédiate est impossible, bloquer au WAF les deux formats d'accès à la route batch : /wp-json/batch/v1 et ?rest_route=/batch/v1.
  3. Auditer la liste des comptes administrateurs et supprimer tout compte créé après le 17 juillet 2026 qui n'est pas reconnu par l'équipe.
  4. Scanner l'arborescence wp-content/plugins à la recherche de plugins non installés volontairement, en particulier ceux contenant eval(, gzuncompress(, base64_decode( de façon suspecte.
  5. Comparer les fichiers PHP présents aux hashs SHA-1 listés dans la section IOC.
  6. Vérifier les logs d'accès pour des réponses 207/200 Multi-Status répétées sur /wp-json/batch/v1, et pour les user-agents wp2shell/rezwp2shell.
  7. En cas de compromission confirmée, considérer le site comme intégralement compromis : rotation de toutes les clés secrètes WordPress (AUTH_KEY, SECURE_AUTH_KEY, etc.), des identifiants de base de données, et réinstallation propre du cœur, des thèmes et des plugins depuis une source de confiance.
  8. Désactiver temporairement l'accès REST anonyme (rest_authentication_errors) sur les sites où seule une API interne est nécessaire.

Sources

Tags : wp2shellCVE-2026-63030CVE-2026-60137WordPressRCEREST APIinjection SQLWPScan
Partager cet article