2 points par GN⁺ 16 시간 전 | 1 commentaires | Partager sur WhatsApp
  • 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_exclude non 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: 1 dans customize_changeset, elle obtient temporairement les privilèges administrateur, relance la requête Batch via le hook parse_request pour 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 .git a été supprimé, et un répertoire vide third_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
  • 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 du continue
    • Tous les éléments suivants de $matches sont 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
  • 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/posts utilise la variable de requête interne author__not_in pour exclure certains auteurs des résultats
  • Si la valeur est un tableau, absint est appliqué à chaque élément pour le convertir en entier, mais si c’est une valeur scalaire, elle est transmise telle quelle à implode puis insérée dans la clause SQL NOT IN
  • Lors d’un appel normal, le paramètre public author_exclude doit ê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_exclude avec les règles de DELETE /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 method dans la requête interne
    • Le Batch interne provoque à nouveau la désynchronisation d’index pour contourner la validation de author_exclude
  • 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_posts et 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_Post ré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_cache dans wp_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=10 peut créer une ligne oembed_cache pour 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’ID et le post_content explicitement spécifiés
  • Il devient ainsi possible de convertir une ligne oembed_cache en article normal, mais dans cet appel, post_content est 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_posts comme articles spéciaux de type customize_changeset
  • post_content contient 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_id de chaque élément et change temporairement l’utilisateur courant avec wp_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_parent suit la hiérarchie des parents pour vérifier les cycles et, lorsqu’il en détecte un, appelle wp_update_post() afin de remettre le post_parent de l’article à 0
  • Ce second appel ne spécifie que ID et post_parent et n’écrase pas post_content
  • En présentant via l’injection SQL l’article en mémoire comme un customize_changeset ayant 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
  • Les arguments du hook étant limités à l’ID d’article et à l’objet WP_Post définis par l’attaquant, appeler directement une action arbitraire est difficilement utile
  • La chaîne d’attaque manipule le statut en parse et le type en request pour appeler le hook parse_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_cache correspondant à O, C et D
    • 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
  • 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 est publish/oembed_cache et dont le parent est C
    • C : statut/type future/customize_changeset, se désigne lui-même comme parent et contient le JSON de changeset malveillant
    • P : draft/page dont le parent est D
    • D : statut/type parse/request et se désigne lui-même comme parent
    • S : publish/post fournissant les données d’embed
    • T : publish/post contenant l’embed externe
    • L’embed de T consulte O et, à cause de son ancienne date de modification, force l’actualisation du cache de S
    • Lors de la mise à jour de O, la détection du cycle du parent C remet le parent de C à 0 et écrit dans la base le customize_changeset en mémoire avec son JSON malveillant
    • Le changeset future daté dans le passé est appliqué, publiant P avec l’identité administrateur de user_id: 1
    • La mise à jour de P détecte le cycle du parent D, écrit D, puis appelle l’action parse_request avec 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_request avec 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é

    • Il semble sans doute faire référence à https://www.crowdfense.com/exploit-acquisition-program/. Zerodium proposait aussi jusqu’à 300 000 dollars en 2021 : https://www.securityweek.com/sites/default/files/images/Zero...
      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
    • Les 500 000 dollars ont été retirés du titre
    • Même avant les LLM, l’intersection entre les personnes capables de trouver ce genre de vulnérabilités, prêtes à les vendre à des courtiers, et en même temps assez stupides pour afficher sur les réseaux sociaux une immense pancarte “arrêtez-moi” devait être minuscule
      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
    • Ce n’est pas parce que quelqu’un a acheté pour 25 dollars sur un marché d’occasion un Macintosh qui coûtait 5 000 dollars à sa sortie que la même comparaison de valeur tient encore
    • Je me demande si “ajuster comme un texte sacré” veut dire ne rien corriger du tout, même quand c’est manifestement contradictoire ou moralement corrompu
  • 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

    • Le pire est dans https://developer.wordpress.org/plugins/creating-tables-with...
      Ils disent d’utiliser dbDelta au lieu d’exécuter directement du SQL, mais imposent des règles de formatage extrêmement tatillonnes : une ligne par champ, deux espaces entre PRIMARY KEY, utiliser KEY au lieu de INDEX, 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és
    • La base de code WordPress est honteuse. PHP est désormais un excellent langage, mais WordPress l’utilise de manière désastreuse et refuse toujours de s’améliorer
    • La manière de corriger est elle aussi affreuse. Je me demande si WordPress construit encore ses requêtes SQL avec de simples concaténations de chaînes et sprintf
    • J’ai vu ce week-end une attaque exploitant cette faille sur un site en production
      Les requêtes POST et GET contenaient une charge utile de la forme /wp/v2/widgets?author_exclude=1%29+AND+1%3D0+UNION+ALL+SELECT...
    • Son profil dit Principal Software Engineer @ Bluehost, WordPress Core Committer, mais avec un code pareil, le terme “principal” sonne vraiment bizarrement
  • J’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é

    • Ce genre d’articles est nocif, un peu comme une version presse d’Instagram où l’on ne publie que les moments de réussite, en donnant l’impression que toute la vie est formidable. Le calcul des 25 dollars ignore non seulement des années d’expérience, mais aussi d’innombrables échecs
    • Même le calcul des coûts n’est pas exact. Les 25 dollars ne correspondent qu’au coût des tokens subventionnés via une formule d’abonnement
    • S’il l’avait fait lui-même, il ne l’aurait pas présenté comme quelque chose de “gratuit”, donc c’est étrange de voir le regard sur le résultat changer simplement parce que 25 dollars ont été dépensés en tokens
  • 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

    • J’ai toujours du mal à comprendre pourquoi un blog ne se contente pas de pages statiques, surtout quand la plupart des problèmes de WordPress sont “résolus” en ajoutant du cache
      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
    • Pour savoir si c’est vrai, il faudrait faire du threat intelligence et infiltrer les groupes Telegram où ces courtiers opèrent, ce que l’auteur a peu de chances d’avoir fait. Il a peut-être confondu une vulnérabilité classique avec un zero-day
    • WordPress fait partie des cibles les plus durcies de toute l’histoire. Son vieux code a à peine changé pendant des décennies, donc on peut aussi considérer que la plupart des bugs ont déjà été trouvés et corrigés
    • Il existe aussi des statistiques selon lesquelles près de 50 % des sites web sur Internet utilisent WordPress, donc mettre 500 000 dollars sur une exécution de code à distance zero-day non authentifiée non divulguée n’est pas totalement irréaliste
  • 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

    • Cette attaque exige de combiner plusieurs vulnérabilités, donc il est probable que ce type d’outils seuls ne l’aurait pas détectée
  • 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].php dans wp-content/plugins/wp-core. Dans mu-plugins, ils ont placé une porte dérobée firewall.php qui crée un administrateur avec GET ?sergei, ajouté aussi la porte dérobée cache-seo-helper.php, puis utilisé fixer.php pour faire apparaître la version de WordPress comme une version corrigée. J’ai finalement décidé d’abandonner WordPress

  • Vers 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 autre 0, et pourquoi il a utilisé une seule lettre et un identifiant apparemment aléatoire comme OCPDST au lieu de EMBED_01 ou ABCDEF

    • Ce sont tous des espaces réservés, et leur signification est indiquée dans le texte. O signifie publish/oembed_cache, C signifie future/customize_changeset, P signifie draft/page, D signifie parse/request, S signifie publish/post qui fournit les données d’intégration, et T signifie publish/post qui contient une intégration externe
  • On suppose peut-être que les gens prêts à payer 500 000 dollars ne sont pas capables d’utiliser directement GPT-5.6

    • Dans ce cas, il faut se demander pourquoi une analyse équivalente n’est pas sortie plus tôt. Il faut encore de l’expertise pour relire la sortie d’un LLM et en faire une preuve de concept réellement valide
      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
    • On voit régulièrement passer des articles disant que les forces de l’ordre ont obtenu les journaux de LLM cloud et les ont utilisés comme preuves dans des poursuites pénales ; un criminel professionnel a donc de fortes chances de blanchir ses activités en passant par un intermédiaire non éthique mais légal
    • Ceux qui gagnent de l’argent ne sont pas forcément ceux qui écrivent le meilleur code. Elon Musk non plus n’a pas écrit lui-même le code des fusées ; il a embauché des gens pour 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

    • En calcul arithmétique, ils surpassent les humains depuis bien plus longtemps