
Analyse et implémentation de bout en bout de la vulnérabilité RCE corrigée de WordPress - CVE-2026-60137 et CVE-2026-63030

tous les PoC exploitent les deux mêmes bugs - la confusion de route REST par lot (CVE-2026-63030) et l'injection SQL author__not_in (CVE-2026-60137) - avec la même forme de lot doublement imbriqué. Ce qui diffère entre eux est le chemin RCE choisi, les préconditions d'environnement et les paramètres de sécurité par défaut. Ce document indique concrètement où se situe l'implémentation de ce dépôt dans ce paysage.
check n'envoie aucune charge utile SQL sauf demande explicite ; tout le trafic peut porter une balise d'attribution ; tout ce que la commande shell écrit sur la cible est automatiquement supprimé ensuite. Voir §3.| Capacité | Ce dépôt | Icex0/wp2shell-poc | sergiointel/wp2shell-poc | 0xsha/wp2shell | Variante OUTFILE [4] |
|---|---|---|---|---|---|
| Lecture SQLi aveugle/ temporelle pré-authentification | oui | oui | oui (temporelle) | oui | oui |
| Lecture UNION en bande (1 requête/valeur) | oui | oui | - [1] | - [1] | - |
| Lecture basée sur erreur (EXTRACTVALUE) | oui | oui | - | - | - |
| Canal UNION survit au cache d'objets persistant | oui (base vidée) | non - sonde faux-négatifs [2] | non documenté | non - précondition documentée [3] | N/A [5] |
| RCE pré-authentification sans cassage | oui (pont SQLi-à-admin) | oui (même pont) | oui (pont d'origine) | oui (même pont) | oui, via INTO OUTFILE [5] |
| Préconditions supplémentaires pour RCE | aucune au-delà d'une installation par défaut | aucune (sur hôtes sans cache d'objets) | aucune (identique) | aucune (identique) | Privilège MySQL FILE + chemin accessible en écriture par le serveur web et partagé avec mysqld |
| Vérification / validation de correctif non destructive | oui (triplet de marqueur ; aucune charge utile par défaut) | oui | non | oui (block_cannot_read) | oui (lot de marqueur) |
| Marquage d'attribution / User-Agent | oui, sur toutes les commandes | non | non | indicateur de transport | non |
| Nettoyage automatique (webshell + admin généré) | oui | oui | non documenté | webshell uniquement verrouillé par jeton | dropper supprimé [5] |
| Guide de détection pour équipe bleue | oui, à partir d'une exécution en production | non | non | matrice de laboratoire à la place | notes d'atténuation |
| Dépendances | stdlib uniquement | stdlib uniquement | fichier unique | stdlib uniquement, fichier unique | Paquet Python ≥3.10 |
[1] Lecture uniquement temporelle/aveugle comme canal de lecture ; la primitive de faux message UNION existe à l'intérieur du pont mais n'est pas exposée comme oracle d'extraction.
[2] La sonde de disponibilité naïve (0) UNION SELECT …) est silencieusement abandonnée pendant l'hydratation du cache d'objets, available() retourne faux, et tout le pont pré-authentification échoue - voir §1.
[3] Le README du projet liste "pas de cache d'objets persistant (Redis/Memcached)" sous Préconditions.
[4] Variante publique miroir sur Sploitus (lien ci-dessous) : lecture aveugle plus un dropper INTO OUTFILE comme étape RCE, utilisant un conteneur per_page=-1 categories.
[5] Le chemin RCE OUTFILE ne dépend pas du rendu des faux messages, donc les caches d'objets ne le bloquent pas - ce sont le privilège MySQL FILE et un répertoire partagé accessible en écriture qui le font. L'hébergement managé n'accorde presque jamais FILE à l'utilisateur WordPress de la base de données, et secure_file_priv est souvent défini.
La primitive de faux message UNION dépend de la façon dont WP_Query retourne les lignes :
wp_posts ; une ligne injectée par UNION devient un WP_Post directement. Le faux message est rendu.Sur les hôtes avec un cache d'objets persistant, un jeu de résultats de base rempli pousse WP_Query en mode fractionné. La sonde standard utilisée par les PoC publics -
0) UNION SELECT <ligne forgée> -- -
post_author NOT IN (0) correspond à chaque ligne), donc derrière un cache d'objets le faux message s'évapore : la sonde de disponibilité fait un faux négatif, available() retourne faux, et tout le pont pré-authentification est signalé "mort" sur un hôte qui est en fait entièrement exploitable. Le fichier unificateur public documente la même limite en listant "pas de cache d'objets persistant" comme une précondition stricte.Ce dépôt vide l'ensemble de base à la place :
1) AND 1=0 UNION ALL SELECT <ligne forgée> -- -
Avec zéro ligne de base, la ligne forgée est la seule ligne ; la requête reste en mode ligne complète ; aucune recherche d'hydratation n'est jamais exécutée. Un mot-clé injecté (AND 1=0) est toute la différence entre "canal UNION mort" et "RCE complète pré-authentification" sur les hôtes avec cache d'objets - qui sont la majorité des environnements WordPress de production managés. Le diagnostic, la matrice de sondes (per_page × forme d'injection).
Note de portée : le canal de lecture aveugle/temporel n'est pas sensible au cache d'objets (compter les lignes en SQL n'implique pas l'hydratation des faux messages), donc la lecture aveugle de chaque PoC fonctionne partout. Ce que le cache d'objets tue dans les autres PoC est spécifiquement la partie dépendante de UNION : l'extraction en bande et le pont SQLi-à-admin.
Une deuxième leçon connexe documentée dans l'étude de cas : lorsque les deux canaux fonctionnent, traitez la lecture UNION en bande comme faisant autorité - l'oracle temporel de production a produit des inversions de bits sous gigue sur une valeur que la lecture en bande a établie sans ambiguïté.
Trois chemins RCE pré-authentification existent dans les PoC publics :
| Chemin | Utilisé par | Préconditions supplémentaires |
|---|---|---|
Pont SQLi-à-admin (forger des lignes oEmbed/changeset/nav → POST /wp/v2/users → connexion → téléchargement de plugin) | ce dépôt, sergiointel (origine), Icex0, 0xsha | aucune au-delà d'une installation par défaut |
Dropper INTO OUTFILE (écrire un fichier PHP via SQLi, le récupérer pour un shell) | Variante OUTFILE [4] | Privilège MySQL FILE, secure_file_priv l'autorisant, et un répertoire accessible en écriture par mysqld et servi par le serveur web |
Récupération de hash → cassage → connexion (vider user_pass, casser hors ligne, puis téléchargement de plugin) | tous (comme solution de repli) | le hash bcrypt doit réellement être cassé ($wp$2y$, hashcat -m 35500) - lent, souvent jamais |