- GPT5.6 Sol Ultra a analysé la dernière version stable de WordPress et a construit en une dizaine d’heures une chaîne d’attaque allant d’une injection SQL pré-authentification à la création d’un compte administrateur puis à l’exécution de code à distance (RCE)
- Le point de départ est une désynchronisation des index de tableau dans la Batch API, présente depuis WordPress 5.6, qui permet de contourner les limites sur GET et la validation des paramètres en combinant des requêtes Batch récursives
- Après avoir provoqué une injection UNION via une chaîne
author_excludenon validée, l’attaque place des articles manipulés dans le cache mémoire et abuse en chaîne du cache oEmbed, des changesets, des références circulaires et des hooks - Avec
user_id: 1danscustomize_changeset, elle obtient temporairement les privilèges administrateur, relance la requête Batch via le hookparse_requestpour créer un nouvel administrateur, puis téléverse un ZIP de plugin backdoor afin d’atteindre l’exécution de code - En calculant au prorata 50 % de l’usage hebdomadaire d’un abonnement à 200 dollars par mois, le coût est d’environ 25 dollars ; le rôle humain pourrait davantage se déplacer vers la direction de haut niveau de la recherche, comme le choix du produit et de la surface d’attaque ou l’ajustement des prompts
Processus de découverte et conditions de l’expérience
- Le prompt qu’OpenAI a indiqué avoir utilisé pour résoudre la conjecture Cycle Double Cover a été modifié pour la recherche en sécurité et fourni à GPT5.6 Sol Ultra
- La dernière version stable de WordPress a été clonée dans
main/, le répertoire.gita été supprimé, et un répertoire videthird_party/a été préparé pour permettre l’examen du code dépendant - Le prompt demandait d’utiliser jusqu’à 4 agents en parallèle et de maintenir différentes pistes d’attaque pendant au moins 6 heures
- Exploration du parsing des entrées, des jeux de caractères, des téléversements de fichiers, de la gestion des erreurs, des chemins intégrés, de la sérialisation, du cache, des conditions de concurrence, du chiffrement, des types, de l’affectation de masse, etc.
- Enregistrement de familles d’approches et réaffectation des agents vers des zones moins explorées lorsqu’ils convergeaient vers une stratégie précise
- Revérification des bugs concrets par un agent adverse, et réouverture des pistes échouées lorsqu’un nouveau mécanisme apparaissait
- L’expérience était limitée à la recherche de nouvelles vulnérabilités dans le code source lui-même, sans utiliser l’historique des changements, les différences avec des versions corrigées ni des indices trouvés sur Internet
- Pour éviter les configurations irréalistes ou les préconditions qu’un attaquant ne pourrait pas satisfaire, l’objectif était explicitement défini comme une « RCE pré-authentification sur un déploiement de production courant utilisant MySQL »
- Après environ 6 heures, le modèle a trouvé une injection SQL pré-authentification, reproduite en extrayant l’e-mail de l’administrateur en quelques minutes sur un serveur WordPress distant par défaut
- Après une demande supplémentaire d’escalade vers une RCE, il a fallu environ 4 heures de plus pour finaliser une chaîne passant d’une injection SQL en lecture seule à des privilèges administrateur, sans cassage de mot de passe ni calcul hors ligne
- Le temps total de travail a été d’un peu plus de 10 heures, consommant 50 % de l’usage hebdomadaire. En calculant au prorata l’abonnement mensuel de 200 dollars, le coût était d’environ 25 dollars
- Avant la publication, les exploitants ont eu le temps de mettre WordPress à niveau pendant le week-end ; entre-temps, Calif et Hacktron ont reproduit indépendamment toute la chaîne avant les autres PoC sur GitHub
- Les instances en production peuvent vérifier leur vulnérabilité sur wp2shell.com
Désynchronisation de validation dans la Batch API
- La Batch API, introduite dans WordPress 5.6, traite plusieurs requêtes API virtuelles au sein d’une seule requête. L’endpoint lui-même est accessible sans authentification, mais les informations d’authentification sont transmises à chaque sous-requête
- Une requête REST classique est traitée dans l’ordre suivant
- Vérification des valeurs requises et de leur validité avec
has_valid_params() - Nettoyage des valeurs avec
sanitize_params() - Exécution du callback d’autorisation
- Exécution du callback de l’endpoint
- Vérification des valeurs requises et de leur validité avec
- Pour des raisons de performance, la Batch API sépare validation et exécution en deux boucles
- La première boucle valide et nettoie toutes les requêtes
- La seconde boucle vérifie les résultats de validation puis exécute les callbacks d’autorisation et d’endpoint
- L’implémentation suppose que les mêmes index correspondent entre
$matches, le résultat de correspondance des routes, et$validation, le résultat de validation - Lorsqu’une requête incorrecte entre dans la branche
is_wp_error($single_request), un élément est ajouté à$validation, mais pas à$matchesà cause ducontinue- Tous les éléments suivants de
$matchessont alors décalés d’une position - Les paramètres d’une requête peuvent être validés avec les règles d’une autre, puis exécutés dans un handler d’endpoint qui n’était pas prévu à l’origine
- Tous les éléments suivants de
- Cette désynchronisation des index permet d’appliquer à un endpoint compatible Batch le résultat de validation d’un autre endpoint qui ne nettoie pas les paramètres
Injection SQL dans author__not_in
GET /wp/v2/postsutilise la variable de requête interneauthor__not_inpour exclure certains auteurs des résultats- Si la valeur est un tableau,
absintest appliqué à chaque élément pour le convertir en entier, mais si c’est une valeur scalaire, elle est transmise telle quelle àimplodepuis insérée dans la clause SQLNOT IN - Lors d’un appel normal, le paramètre public
author_excludedoit être un tableau d’entiers, donc le problème ne se manifeste pas - La désynchronisation d’index de la Batch API permet de valider une chaîne
author_excludeavec les règles deDELETE /wp/v2/posts/1, qui ne la reconnaissent pas, puis de la transmettre àGET /wp/v2/posts - La restriction de la Batch API qui interdit les sous-requêtes GET est également contournée par un appel Batch récursif
- Le Batch externe provoque la désynchronisation d’index pour ignorer la validation de
methoddans la requête interne - Le Batch interne provoque à nouveau la désynchronisation d’index pour contourner la validation de
author_exclude
- Le Batch externe provoque la désynchronisation d’index pour ignorer la validation de
- En injectant une valeur comme
0) OR 1=1 --, toutes les lignes d’articles sont retournées, ce qui permet de confirmer l’injection - Ensuite, une injection basée sur UNION permet de construire des lignes ayant la même forme que
wp_postset d’exfiltrer des valeurs arbitraires de la base de données - Les mots de passe, jetons de réinitialisation, clés API, etc. étant hachés dans la base de données, la seule fuite de données ne mène pas à une prise de contrôle de compte si le mot de passe administrateur n’est pas faible
Manipulation du cache d’articles dans la requête
- WordPress stocke dans un cache mémoire les objets
WP_Postréférencés plusieurs fois au sein d’une même requête afin de réduire les allers-retours vers la base de données - En renvoyant une fausse ligne d’article via l’injection UNION, il est possible de placer dans le cache un ID d’article, un type, un statut, une relation parent, un contenu et de nombreux autres champs définis par l’attaquant
- La réponse API post-traite aussi le contenu des articles, ce qui permet d’exécuter des chemins de code supplémentaires avec un contenu manipulé
- Ce cache disparaît à la fin de la requête et les faux articles n’existent pas dans la base de données ; la seule manipulation du cache ne permet donc pas d’obtenir une persistance entre requêtes
Création de lignes en base via le cache oEmbed
- La fonctionnalité embeds de WordPress insère du contenu distant pris en charge via la syntaxe
[embed]...[/embed]dans le corps des articles - Pour éviter d’envoyer une requête HTTP à chaque fois, le résultat est stocké dans la base de données sous forme d’article de type
oembed_cachedanswp_posts - Lorsqu’un article WordPress local est intégré via un chemin relatif, la requête HTTP est omise et l’existence réelle de l’ID d’article référencé n’est pas vérifiée
- Même l’intégration d’un article local inexistant comme
/?p=10peut créer une ligneoembed_cachepour ces données - En relisant la ligne créée via l’injection SQL tout en manipulant en mémoire son type d’article en
post, par exemple, les représentations en base et en cache divergent - En réconciliant ces deux représentations, WordPress appelle
wp_update_post()et privilégie les valeurs en mémoire créées par l’attaquant pour les champs autres que l’IDet lepost_contentexplicitement spécifiés - Il devient ainsi possible de convertir une ligne
oembed_cacheen article normal, mais dans cet appel,post_contentest remplacé par le résultat de l’embed ; l’attaquant ne contrôle donc pas aussi le contenu
Privilèges administrateur temporaires via customize_changeset
- Les brouillons de personnalisation de thème sont stockés dans
wp_postscomme articles spéciaux de typecustomize_changeset post_contentcontient un JSON avec, pour chaque clé de réglage, la valeur modifiée, le type et l’ID utilisateur qui effectue le changement- Lors de l’application d’un changeset, WordPress lit le
user_idde chaque élément et change temporairement l’utilisateur courant avecwp_set_current_user() - Si l’attaquant applique un changeset avec
user_id: 1, il peut momentanément utiliser l’identité administrateur même dans une requête anonyme - Comme l’appel d’ajustement oEmbed précédent écrase
post_content, un autre chemin d’appel àwp_update_post()est nécessaire pour conserver le JSON de changeset malveillant
Conservation du contenu via une boucle de parent d’article
- Un article WordPress peut avoir un parent, mais une structure circulaire dans laquelle un article a pour parent lui-même ou un de ses descendants n’est pas autorisée
- Le filtre
wp_insert_post_parentsuit la hiérarchie des parents pour vérifier les cycles et, lorsqu’il en détecte un, appellewp_update_post()afin de remettre lepost_parentde l’article à 0 - Ce second appel ne spécifie que
IDetpost_parentet n’écrase paspost_content - En présentant via l’injection SQL l’article en mémoire comme un
customize_changesetayant lui-même pour parent, le processus de réparation du cycle écrit dans la base de données le JSON de changeset malveillant spécifié par l’attaquant - En créant un changeset daté dans le passé avec le statut
future, WordPress l’applique et effectue les modifications de réglages indiquées avec les privilèges administrateur correspondant àuser_id: 1 - Les privilèges administrateur ne sont conservés que pendant l’opération sur le changeset, puis reviennent aux droits invité
Réexécution de toute la requête via un hook dynamique
- Les hooks WordPress se divisent en actions et filtres ; ils permettent aux plugins d’intervenir à de nombreux points du cycle de vie, comme la connexion, la publication ou l’enregistrement de scripts
- Lorsqu’un statut d’article change, WordPress exécute une action dynamique de la forme
"{$new_status}_{$post->post_type}"- Pour un article normal, cela donne un nom comme
publish_post - Un faux article en mémoire pouvant avoir un statut et un type arbitraires, il est possible de construire le nom d’action souhaité dès lors qu’il contient au moins un underscore
- Pour un article normal, cela donne un nom comme
- Les arguments du hook étant limités à l’ID d’article et à l’objet
WP_Postdéfinis par l’attaquant, appeler directement une action arbitraire est difficilement utile - La chaîne d’attaque manipule le statut en
parseet le type enrequestpour appeler le hookparse_request parse_requestétant un hook exécuté au début du cycle de vie de la requête, le rappeler fait retraiter toute la requête Batch d’origine depuis le début- Le retraitement s’effectue pendant que l’identité administrateur temporaire définie par le changeset est toujours active ; les requêtes réservées à l’administrateur, qui avaient échoué au premier passage faute de droits, réussissent au second
Chaîne RCE complète en deux requêtes
- L’exploit final utilise deux requêtes HTTP et assigne aux faux articles des ID suffisamment grands pour ne pas entrer en conflit avec de vrais articles
-
Première requête : préparation des lignes persistantes
- L’injection SQL renvoie un faux article avec trois embeds locaux afin de créer 3 lignes
oembed_cachecorrespondant àO,CetD - Les trois embeds pointent vers le même article
S, mais utilisent des chaînes de requête différentes afin de produire des hachages de cache oEmbed distincts
- L’injection SQL renvoie un faux article avec trois embeds locaux afin de créer 3 lignes
-
Deuxième requête : assemblage de six articles
- Les six faux articles suivants sont construits dans le cache mémoire
O: cache ancien dont le statut/type estpublish/oembed_cacheet dont le parent estCC: statut/typefuture/customize_changeset, se désigne lui-même comme parent et contient le JSON de changeset malveillantP:draft/pagedont le parent estDD: statut/typeparse/requestet se désigne lui-même comme parentS:publish/postfournissant les données d’embedT:publish/postcontenant l’embed externe- L’embed de
TconsulteOet, à cause de son ancienne date de modification, force l’actualisation du cache deS - Lors de la mise à jour de
O, la détection du cycle du parentCremet le parent deCà 0 et écrit dans la base lecustomize_changeseten mémoire avec son JSON malveillant - Le changeset
futuredaté dans le passé est appliqué, publiantPavec l’identité administrateur deuser_id: 1 - La mise à jour de
Pdétecte le cycle du parentD, écritD, puis appelle l’actionparse_requestavec le statut et le type manipulés - La requête Batch contient dès le départ une requête de création d’un nouvel administrateur
- Au premier traitement, elle échoue avec les droits invité
- Lors de la réexécution via
parse_request, les privilèges administrateur temporaires sont toujours actifs, et la requête réussit - Après connexion avec le nouveau compte administrateur, le téléversement d’un ZIP de plugin backdoor permet finalement d’atteindre l’exécution de code à distance
Évolution des rôles dans la recherche de sécurité avec l’IA
- Dans toute la chaîne, les étapes particulièrement créatives sont le contournement de la limite GET via des appels Batch récursifs, l’obtention de privilèges administrateur en combinant cache et changeset, et la réexécution de la requête en appelant
parse_requestavec un faux article - Sans en tirer de conclusion générale de supériorité, l’évaluation est qu’il aurait été impossible pour un chercheur en sécurité sans IA de découvrir et finaliser la même chaîne en 10 heures
- Même en fournissant à l’avance le bug Batch initial, il est jugé difficile d’être certain qu’une RCE pourrait être construite dans ce délai
- GPT5.6 Sol Ultra est considéré comme ayant fortement progressé par rapport à GPT5.5 dans sa capacité à trouver plusieurs gadgets de code dispersés et à les relier en une seule chaîne
- Plus le modèle prend en charge le développement technique d’exploits, plus l’humain se concentre sur le choix du produit et de la surface d’attaque à étudier, le temps à investir, l’orientation de recherche et la correction de trajectoire lorsque le modèle s’égare
- Ces compétences de méta-recherche restent difficiles à gérer pour l’IA et devraient devenir d’autant plus importantes que les capacités techniques des modèles augmentent
1 commentaires
Avis sur Hacker News
Il n’y a aucune preuve que 500 000 dollars aient été versés ou le seront pour ce type d’exploit. L’article dit que le prompt a été ajusté avec le plus grand soin, presque comme un texte sacré ; autant vendre ce prompt 500 000 dollars, dans ce cas
L’auteur travaille chez https://www.assetnote.io/, qui propose un produit d’IA de scan automatisé
Ce genre d’intermédiaires ne paie généralement pas de telles sommes en une seule fois ; ils vendent d’abord l’accès à des acteurs étatiques, puis paient par tranches tant que le bug n’est pas corrigé. C’est structuré ainsi pour éviter la revente ou une consommation trop rapide, donc il y aura sans doute très peu de gens capables de confirmer avoir réellement touché la somme entière pour une vulnérabilité comparable
L’exemple le plus proche, ce sont des ados de Floride arrêtés après avoir glissé un malware dans un jeu Steam pour voler des comptes. Ils auraient fini par se faire prendre de toute façon, mais l’enquête aurait probablement pris bien plus longtemps s’ils ne s’en étaient pas vantés sur les réseaux sociaux
En regardant https://github.com/WordPress/WordPress/commit/3a640e1c5e39aa..., on voit qu’il y a encore en 2026 des injections SQL par concaténation de chaînes
Ils disent d’utiliser
dbDeltaau lieu d’exécuter directement du SQL, mais imposent des règles de formatage extrêmement tatillonnes : une ligne par champ, deux espaces entrePRIMARY KEY, utiliserKEYau lieu deINDEX, etc. Il ne faut ni guillemets ni backticks dans les noms de champs, les types doivent être en minuscules, les mots-clés SQL en majuscules, et tous les paramètres de longueur doivent être préciséssprintfLes requêtes
POSTetGETcontenaient une charge utile de la forme/wp/v2/widgets?author_exclude=1%29+AND+1%3D0+UNION+ALL+SELECT...Principal Software Engineer @ Bluehost,WordPress Core Committer, mais avec un code pareil, le terme “principal” sonne vraiment bizarrementJ’en ai assez de cette écriture façon FOMO. La découverte n’a pas été faite avec seulement 25 dollars ; elle a reposé sur une expertise métier pour savoir où regarder et comment explorer, ainsi que sur des années de documentation accumulée
Il faut arrêter de vendre ce récit de casino et cette illusion que tout le monde passe à côté d’une opportunité
La partie étonnante, c’est le prix élevé d’une vulnérabilité déjà connue, et il est possible que ce ne soit pas vrai. On dit souvent de WordPress que c’est un shell root distant avec une fonction blog en bonus
Je comprends qu’il soit plus simple d’expliquer le glisser-déposer à un utilisateur lambda que de lui demander de commit dans un dépôt GitHub puis de builder avec Hugo. Mais du point de vue de la sécurité, c’est une architecture qui n’attend qu’une vulnérabilité dans le cœur ou dans l’un des milliers de plugins pour ouvrir une exécution de code à distance en tant que service
Article intéressant, et la découverte et divulgation d’exploits basées sur les LLM sont une vraie source d’inquiétude. J’ai déjà réussi à faire produire assez vite par un modèle du code d’évasion de conteneur à partir d’une vulnérabilité Linux locale d’élévation de privilèges
En revanche, il est surprenant que GPT-5.6 n’ait pas bloqué le prompt avec ses garde-fous. Depuis GPT-5.5, comme Opus 4.7+/Fable, ces modèles ont plutôt tendance à refuser le travail de sécurité offensive ; il semble donc possible que l’auteur ait obtenu auprès d’OpenAI une autorisation cybersécurité assouplissant ces garde-fous
Même des outils de test statique de sécurité des applications (SAST) non liés à l’IA d’avant 2020 détectaient souvent ce type d’injection SQL, et cela aurait au minimum dû être repéré lors d’une revue de code. On peut se demander si WordPress n’utilise ni revue de code ni SAST
L’un de mes sites web a été piraté via cette vulnérabilité, mais heureusement il n’avait pas d’utilisateurs
Les attaquants ont créé deux comptes administrateur dans la base de données et installé un web shell d’exécution de commandes à distance
wp-core-[12 caractères aléatoires].phpdanswp-content/plugins/wp-core. Dansmu-plugins, ils ont placé une porte dérobéefirewall.phpqui crée un administrateur avecGET ?sergei, ajouté aussi la porte dérobéecache-seo-helper.php, puis utiliséfixer.phppour faire apparaître la version de WordPress comme une version corrigée. J’ai finalement décidé d’abandonner WordPressVers la fin du texte, il s’est mis à désigner les publications par des noms étranges, ce qui a rendu la compréhension difficile. Je me demande pourquoi un ID a été noté
O, un autre0, et pourquoi il a utilisé une seule lettre et un identifiant apparemment aléatoire commeOCPDSTau lieu deEMBED_01ouABCDEFOsignifiepublish/oembed_cache,Csignifiefuture/customize_changeset,Psignifiedraft/page,Dsignifieparse/request,Ssignifiepublish/postqui fournit les données d’intégration, etTsignifiepublish/postqui contient une intégration externeOn suppose peut-être que les gens prêts à payer 500 000 dollars ne sont pas capables d’utiliser directement GPT-5.6
Moi aussi, j’utilise des LLM pour trouver des failles de sécurité, mais je ne peux pas simplement soumettre le résultat tel quel et m’arrêter là, même si beaucoup essaient de le faire
La question de savoir si GPT-5.6 Sol est surhumain n’appelle pas simplement une réponse par oui ou par non. Les ordinateurs surpassent les humains aux échecs depuis des décennies, et à en croire ce texte, ils semblent désormais aussi les dépasser dans la compréhension du code