
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.[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 :
Ce dépôt implémente le pont : il n'a besoin d'aucun privilège de base de données au-delà de ce que WordPress a déjà, fonctionne lorsque les couches base et web ne partagent rien, et ne laisse aucun fichier pour le chemin du privilège FILE. Le compromis est la complexité - le pont est un graphe de messages empoisonnés sur sept lignes - ce qui est exactement là où le faux négatif du cache d'objets §1 cachait le chemin.
Construit pour être exécuté contre des systèmes de production sous autorisation, pas seulement en laboratoire :
check est non destructif par défaut - empreinte passive plus un lot de marqueur bénin ; aucune charge utile SQL n'est envoyée sauf si --confirm-sqli est donné. Après le correctif, le triplet de marqueurs qui disparaît sert également de validation de correctif.--user-agent sur chaque commande pour que tout le trafic d'exploitation soit identifiable dans les journaux (une règle de conduite que les outils publics n'activent pas par défaut).author_exclude → author__not_in, la primitive UNION de faux WP_Post, et le concept de pont customizer sont tous des techniques publiques (lignée reconnue ci-dessous).INTO OUTFILE), miroir sur Sploitus -
https://sploitus.com/exploit?id=7CD079AD-E27B-5C54-A696-60635BFDB241| 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 |
| 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 |