← Retour à tous les articles
Publié le · par Renaud Deraison

La dernière étape était un prompt

Le 18 septembre, Hacktron a publié la chaîne qui l'a menée jusqu'au monorepo privé d'OpenAI. Elle commence par une image déposée sur un forum public et un débordement de tas dans un décodeur d'images dont le correctif amont ne portait aucun CVE. Elle se termine par des chercheurs qui envoient un prompt au compte Codex d'un employé, et l'agent ouvre la pull request. Rien n'a été injecté, rien n'a dysfonctionné ; l'agent a fait ce que demandait celui qui tenait la session. Bromure Agentic Coding décide cette écriture sur votre Mac, en dehors de la session.

La dernière étape de cette chaîne n'a demandé aucun exploit. Les chercheurs tenaient l'agent de codage d'un employé ; ils lui ont donc demandé d'ouvrir une pull request dans le monorepo privé, et il en a ouvert une.

À six heures du matin UTC le 25 juillet, trois chercheurs de Hacktron avaient l'exécution de code sur le forum communautaire public d'OpenAI. En début d'après-midi, une pull request numérotée 1186742 se trouvait dans openai/openai, le monorepo privé, et ils n'avaient écrit aucune ligne de code pour l'y mettre. Ils ont demandé à un agent, et l'agent l'a déposée.

Hacktron a publié la chaîne le 18 septembre, et elle mérite d'être lue maillon par maillon, parce que chaque maillon est ordinaire. Les chercheurs sont Harsh Jaiswal, Mohan Pedhapati et Rahul Maini. The Hacker News en a rendu compte le lendemain.

Une image, une conversion et un parseur

La porte d'entrée était l'envoi d'images du forum. Discourse vérifie les images déposées avec une bibliothèque Ruby nommée FastImage, qui lit assez de l'en-tête d'un fichier pour en déduire les dimensions et le format. FastImage ne comprend pas le HEIF, le conteneur dans lequel arrive une photo d'iPhone ; Discourse passait donc ces fichiers à la commande magick d'ImageMagick pour conversion. ImageMagick décode le HEIC avec libheif, qui est écrit en C.

Voilà toute la condition d'entrée. Un format que le vérificateur sûr ne reconnaissait pas est parti vers un convertisseur qui appelait un parseur écrit dans un langage où un champ de longueur malformé devient un problème de sûreté mémoire. Hacktron a trouvé un débordement de tampon de tas dans le décodage HEIC et en a tiré des primitives de lecture et d'écriture hors limites.

La paperasse est pire que le bug. L'amont avait corrigé ce débordement l'année précédente sans jamais qualifier le commit de correctif de sécurité. Il n'a reçu ni CVE, ni bulletin, ni ligne de changelog qu'un scanner de dépendances aurait pu lire ; Debian ne l'a donc jamais rétroporté, et l'image Docker de Discourse embarquait libheif 1.19.7 sur Debian 12 avec le bug intact. Aucun scanner au monde n'avait matière à signaler cette version. Debian a publié DSA-6417-1 plus tard et le problème porte désormais le CVE-2026-32882, attribué après que quelqu'un l'a exploité.

On ne peut pas scanner un bug que personne n'a déclaré. Ce que l'on peut décider à l'avance, c'est ce dans quoi se tient le parseur au moment où il tombe.

D'un envoi à une pull requestl'analyseun envoi HEICposté sur un forum publicaucun privilège requisFastImagele contrôle d'en-tête sûrne gère pas le HEIFmagickconvertit à sa placeImageMagick appelle libheiflibheif 1.19.7débordement de tas, R/W OOBcorrigé en amont, ni CVE ni backportle pivotexécution de codecommunity.openai.com06:00 UTC, 25 juilletune faille SSOune session du forum devientune prise de compte, sans clicle Codex d'un employéconnecté à l'org GitHubdéjà autorisé à écriresans exploitun promptPR #1186742monorepo privé, openai/openai
Chaque maillon de la chaîne est ordinaire. FastImage ne gère pas le HEIF, le fichier part donc vers ImageMagick, qui appelle libheif, qui contenait un débordement de tas dont le correctif amont ne portait aucun CVE et n'a jamais atteint Debian. Une faille SSO distincte a transformé l'exécution de code sur le forum en accès au compte, et l'accès au compte en pull request dans le monorepo privé. Seule la dernière étape n'a demandé aucun exploit : les chercheurs ont envoyé un prompt à un agent de codage déjà autorisé à pousser.

L'exploit a pris une soirée

Hacktron a commencé à regarder la chaîne d'envoi le 23 juillet. Le 24 juillet, ils sont partis à l'assaut du débordement avec Claude Opus 4.8 qui, selon eux, a peiné sur plusieurs sessions à produire quoi que ce soit de fonctionnel contre la randomisation de l'espace d'adressage, la défense qui brasse la carte mémoire d'un processus pour qu'un attaquant ne puisse pas prédire où se trouve quoi que ce soit. Transformer un débordement de tas en exécution fiable face à l'ASLR est la partie difficile, et c'est là que ce genre de recherche cale une semaine.

Anthropic a sorti Opus 5 ce soir-là. Hacktron a ouvert une session neuve et, trois heures plus tard, disposait d'un exploit ARM64 fonctionnel tournant contre un Mac local. Ils ont demandé au modèle de le porter vers la configuration x86-64 et jemalloc sur laquelle tourne Discourse, et ont confirmé l'exécution de code en local via un envoi d'image à six heures le lendemain matin. Puis ils ont lancé Claude en boucle autonome contre leur propre instance Discourse Cloud. À dix heures, il y avait aussi l'exécution de code, et lisait /etc/hosts pour le prouver.

Ils sont prudents sur ce que cela signifie, et la réserve a sa place dans toute lecture de l'affaire :

Il ne s'agissait pas de piratage entièrement autonome. L'expertise humaine est restée nécessaire pour diriger les sessions, valider les résultats et gérer la divulgation.

Juste. Trois chercheurs compétents ont pointé la chose, et leur compétence n'a pas changé du jour au lendemain. Le bug n'a pas changé non plus. Entre ces deux sessions, la seule variable nouvelle était le modèle, et il a fait passer un travail d'exploitation de plusieurs tentatives infructueuses à trois heures. La campagne dont cela faisait partie a coûté à Hacktron moins de 3 000 dollars de jetons et quelques heures de temps humain.

Ce chiffre est la donnée de planification. Un bug qui n'a jamais reçu de CVE, niché dans une dépendance transitive d'un convertisseur de fichiers, se convertit désormais en primitive fiable pour le prix d'un ordinateur portable. Attendre le bulletin est un espoir, et personne ne devance cette horloge en lisant les changelogs plus vite.

La dernière étape était un prompt

L'exécution de code sur le forum n'a rien apporté à Hacktron dont OpenAI se souciait. C'est un second problème qui l'a fait. OpenAI avait câblé son authentification unique de telle sorte qu'une session de forum compromise devenait une prise de contrôle sans interaction du compte ChatGPT et Codex de tout employé utilisant le forum. Selon les termes de Hacktron, c'est le problème SSO qui a transformé une compromission de forum en accès à ChatGPT et Codex.

Le Codex d'un employé était connecté à l'organisation GitHub de l'entreprise ; les chercheurs ont donc fait la chose évidente. Ils ont envoyé à ce compte un prompt lui demandant d'ouvrir une pull request dans le monorepo interne, et la PR 1186742 est apparue dans openai/openai. Puis ils se sont arrêtés, sans lire le moindre code source, et ont déposé leur rapport.

Relisez cette étape. Quelqu'un détenant une session valide a demandé à l'agent de faire une chose ordinaire, et l'agent l'a faite avec l'accès en écriture qu'il possédait déjà. Pas d'injection, pas de jailbreak, pas d'entrée malformée. Tous les contrôles que quiconque avait construits se tenaient derrière cette session : la connexion, l'appartenance à l'organisation, les permissions du dépôt. Dès lors qu'un attaquant tenait la session, il les tenait tous.

Un agent de codage est une concession permanente d'accès en écriture à vos dépôts, attachée à une conversation. Quiconque atteint la conversation hérite de la concession. Cela vaut pour Codex dans un navigateur, et cela vaut pour Claude Code, Codex CLI ou Gemini CLI sur un portable, où l'agent porte un vrai jeton ghp_ dans ~/.git-credentials et un vrai GH_TOKEN dans son environnement. Sur un portable, les voies d'accès se multiplient : un fichier empoisonné dans un dépôt, un commentaire d'issue, un script postinstall de paquet, un terminal détourné.

OpenAI a corrigé le forum en quatorze heures environ. Le rapport est parti ce matin-là et OpenAI a confirmé le correctif à 22h49 UTC le jour même. Discourse a livré son propre correctif pendant le week-end et publié le bulletin GHSA-vhm9-85gw-x335 le 28 juillet, et la prime s'est élevée à 6 500 dollars. La part de cette forme qui se trouve sur votre propre bureau, c'est l'agent.

Où se décide l'écriture

Bromure Agentic Coding garde la réponse à « qui a le droit de pousser » sur votre Mac, en dehors de la session de l'agent, parce que la session est précisément ce qu'un attaquant cherche à atteindre. Deux choses s'y produisent.

D'abord, l'environnement de l'agent ne contient aucun jeton dont hériter. La VM de l'espace de travail reçoit un leurre : ghp_ suivi de 36 caractères, de forme telle que gh et git l'acceptent sans broncher, dérivé de votre vrai jeton par HKDF-SHA256 avec un sel propre à l'installation, de sorte qu'il ne semble jamais tourner d'une session à l'autre. Votre vrai jeton vit chiffré sur l'hôte, et le proxy hôte le substitue sur le fil après que la requête a quitté la VM, uniquement lorsque la destination correspond à l'hôte pour lequel ce jeton a été frappé. Comme le dit le manuel : aucun fichier, variable d'environnement ou processus à l'intérieur de la VM ne contient jamais une vraie clé d'API, un jeton OAuth, un secret AWS ou une clé SSH privée. Emportez ce leurre là où il n'a rien à faire et le proxy répond HTTP 451 sans transmettre un octet, met la VM en pause et inscrit une ligne rouge dans la Security Timeline.

La seconde est celle que cette histoire appelle. Chaque identifiant de forge git porte une politique d'écriture, réglée par espace de travail dans le volet Guardrails, et les nouveaux espaces de travail démarrent par défaut sur Demander avant écriture. Les lectures passent sans commentaire. Un git push arrive sur le fil sous la forme git-receive-pack et compte comme une écriture, tout comme un POST vers l'API des pull requests. Lorsqu'il en arrive un, le proxy retient la requête et ouvre une fenêtre sur votre Mac qui porte l'opération littérale plutôt qu'un résumé : la méthode et le chemin. Vous obtenez Autoriser 15 minutes, Autoriser une fois, Autoriser pour le reste de la session ou Ne pas autoriser. Un refus parvient à l'agent comme une erreur d'API ordinaire qu'il peut rapporter, non comme une connexion suspendue.

La description que le volet donne lui-même de l'endroit où cela s'exécute est la phrase qui compte : appliqué dans le proxy, de sorte qu'un agent compromis dans la VM ne peut pas le contourner.

Qui que ce soit qui pilote l'agent — vous, une injection de prompt enfouie dans un fichier de fixtures, ou quelqu'un qui tient une session qu'il vous a prise — l'écriture sort par le même proxy et se voit classée de la même manière. La fenêtre sur votre Mac est l'autorisation, et un attaquant qui tient votre session ne tient pas votre Mac.

Un agent sur le portablela session de l'agentjoignable par vous, par une injection, parquiconque prend la sessionde vrais identifiants, à portéeGH_TOKEN · ~/.git-credentialslisibles par tout processus de la machinePOST /repos/org/repo/pulls → GitHubla session porte l'autoritéUn agent dans un espace Bromurela session de l'agent, dans la VMghp_ + 36 caractères, un leurrerien de réel à hériterle commutateur dévie :80 et :443le proxy hôte le classePOST /repos/org/repo/pulls = écrituregit-receive-pack compte aussiune fenêtre sur votre Mac, avec l'appel littéral15 minutes · une fois · la session · refuserle Mac porte l'autorité
Sur un portable de développeur, l'agent détient un vrai jeton : un prompt suffit et la session porte l'autorité. Dans un espace de travail Bromure, la VM ne détient qu'un leurre, le proxy hôte substitue le vrai jeton à la sortie, et le garde-fou classe l'écriture avant qu'elle n'atteigne GitHub. Demander avant écriture, le réglage par défaut des nouveaux espaces, ouvre une fenêtre sur votre Mac portant la méthode et le chemin littéraux. Quiconque tient la session ne tient pas le Mac.

Le problème du parseur arrive aussi sur votre bureau

L'autre moitié de la chaîne se généralise elle aussi, et peu de gens l'ont intégrée à leurs calculs.

Un agent de codage passe sa journée à lire des fichiers qu'il n'a pas écrits : des fixtures de test livrées avec le dépôt, une image jointe à un rapport de bug, une capture d'écran que vous avez collée dans la conversation, le PDF d'une spécification, une archive tirée d'une page de release, une police, une vidéo que le front end est censé lire. Les traiter suppose d'appeler magick, ffmpeg, pdftotext ou unzip, ou une bibliothèque d'images liée à Python, et chacun de ces outils est un parseur C ou C++ qui avale des octets choisis par quelqu'un d'autre. C'est exactement la condition qui a permis à un envoi de forum d'atteindre libheif. Un agent la rencontre plus souvent que vous, parce qu'il n'hésite jamais avant d'ouvrir quelque chose.

La détection n'a pas de réponse ici. Le bug de libheif ne portait aucun CVE au moment où cela comptait ; aucun scanner, aucune liste d'autorisation, aucun épinglage de version n'aurait dit un mot. Vous pouvez choisir le rayon de l'explosion, et c'est tout.

Dans un espace de travail Bromure, ce décodeur tourne à l'intérieur d'un invité Ubuntu sous Virtualization.framework d'Apple, une frontière d'hyperviseur plutôt qu'un bac à sable de processus, dans un invité que Bromure traite comme non fiable par construction. Un débordement de tas dans un parseur d'images achète à un attaquant un shell dans une VM Linux dont les identifiants sont des leurres et dont la route vers Internet est un jeu de règles ordonné sur l'hôte. Le commutateur virtuel applique ce jeu de règles sur l'IP de destination et le nom d'hôte relevé par espionnage DNS, en TCP comme en UDP, et le proxy l'applique de nouveau sur le nom de serveur TLS et la méthode HTTP. Réglez le trafic non apparié sur Refuser et le shell dispose d'une liste d'autorisation et de rien d'autre. Le commutateur dévie :80 et :443 vers le proxy lui-même, de sorte qu'il n'y a aucune variable de proxy que le shell puisse désactiver. Les modifications de règles atteignent les sessions en cours sans redémarrage, et chaque verdict atterrit en ligne Firewall dans la Security Timeline.

Partez du principe que le parseur tombe

Ce bug ne portait aucun CVE au moment où cela comptait. Prévoyez que le décodeur perde, et décidez à l'avance dans quoi il se tient à ce moment-là.

Une session n'est pas une autorisation

Quiconque atteint la conversation hérite de l'accès en écriture de l'agent. Placez la décision d'un push là où la conversation ne peut pas aller.

Ne laissez rien à voler dans la VM

Un leurre que les outils acceptent ne vaut rien en amont, et vaut moins que rien pour un attaquant qui l'emporte ailleurs.

Chiffrez le prix de l'exploit

Trois heures et quelques milliers de dollars de jetons. Lire les bulletins plus vite ne répond pas à ce chiffre.

L'analyse de Hacktron est de la bonne recherche, et honnête sur la part qu'un humain a encore dû piloter. La fin est ce qui sera encore vrai l'an prochain. Chaque défense de la chaîne se tenait derrière une identité, et dès qu'une session portant cette identité a changé de mains, plus aucune n'avait d'avis. Quelqu'un a demandé à l'agent, et l'agent s'est exécuté.

Si votre agent peut pousser, demandez-vous où vit la permission de pousser, et si un attaquant qui prendrait tout ce que possède l'agent prendrait cela aussi. Sur un portable, oui. Déplacez cette seule décision hors de la machine où tourne l'agent, et la dernière étape de cette chaîne a un endroit où s'arrêter.