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

La page web qui s'appelait localhost

Le 17 août, la CISA a ajouté la CVE-2025-62593 de Ray à son catalogue des vulnérabilités activement exploitées. Il s'agit d'une exécution de code à distance sur la machine du développeur lui-même, déclenchée par une page web qu'il a visitée, atteignant un service qui n'écoutait que sur 127.0.0.1. Les agents de code remplissent cette interface de serveurs non authentifiés depuis un an. Bromure Agentic Coding les place là où aucun onglet de navigateur ne peut composer le numéro.

Votre serveur de développement, votre notebook et votre tableau de bord local lient chacun un port non authentifié à 127.0.0.1, au motif que seule la personne au clavier peut l'atteindre. Une page web le peut aussi. La politique de navigateur qui s'y oppose tombe en une soixantaine de secondes.

Le 17 août, la CISA a ajouté une vulnérabilité à son catalogue des vulnérabilités activement exploitées et a donné aux agences fédérales jusqu'au 20 août pour la traiter. The Hacker News en a fait le compte rendu le lendemain matin. L'entrée est la CVE-2025-62593, une faille d'injection de code dans Ray, le framework de calcul distribué qu'une large part de l'industrie utilise pour entraîner et servir des modèles.

Le code s'exécute sur le portable du développeur lui-même. Quelqu'un faisait tourner Ray en local pour son travail, a ouvert une page web, et la page a exécuté une commande à sa place.

Trois faits ordinaires, mal agencés

L'avis de sécurité le note 9,4 et décrit une chaîne dont aucune étape n'a rien d'exotique.

L'API Jobs de Ray prend une commande shell. C'est le produit : vous faites un POST sur /api/jobs/ avec un point d'entrée et le cluster exécute la commande. Le tableau de bord écoute sur le port 8265, et ni lui ni les points d'entrée de jobs n'exigent d'authentification. C'était un choix délibéré des mainteneurs, dont la position est que la sécurité et l'isolation doivent être imposées en dehors du cluster Ray. Vous le placez quelque part où seuls des appelants de confiance peuvent l'atteindre, et l'interface de loopback d'un portable ressemble à un tel endroit.

Ray disposait bien d'un garde-fou contre les navigateurs. Il vérifiait si l'en-tête User-Agent commençait par Mozilla et refusait ces requêtes, en supposant qu'un navigateur ne peut pas mentir sur son propre User-Agent. Dans Firefox et Safari, l'API Fetch permet à une page de fixer cet en-tête à ce que bon lui semble.

Reste la politique d'origine unique, le seul contrôle qui se dresse entre une page sur internet et un service sur votre machine. Le DNS rebinding la démonte. L'attaquant sert la page depuis un domaine dont il contrôle les enregistrements DNS, avec une durée de vie très courte. La page se charge, puis résout à nouveau ce même domaine vers 127.0.0.1. Le navigateur continue de la traiter comme la même origine, même nom d'hôte et même port, alors que chaque requête part désormais vers votre machine. La preuve de concept publiée utilise singularity, un framework de rebinding public depuis des années.

Enchaînez le tout et une page malveillante, ou une publicité vérolée sur une page à laquelle vous faisiez confiance, poste un job à votre instance Ray avec une commande shell dedans. Chrome est protégé par un bug sans rapport ; Firefox et Safari ne le sont pas. Avi Lumelsky, chez Oligo, a théorisé le contournement du User-Agent ; Jonathan Leitschuh, alors chez Socket, a construit la chaîne de rebinding et la divulgation. Ray 2.52.0 corrige la faille et ajoute une authentification par jeton optionnelle, désactivée par défaut.

Comment une page devient un client pour votre portable1 · vous chargez la pageevil.example → 203.0.113.7TTL : 1 seconde2 · l'enregistrement changeevil.example → 127.0.0.1même origine, autre destination3 · le garde est un en-têtefetch(url, headers: UA)Firefox et Safari l'autorisent4 · POST /api/jobs/127.0.0.1:8265entrypoint = shellCE SUR QUOI CHAQUE COUCHE COMPTAITRay« le réseau autour de nous est sûr »la vérification User-Agent« un navigateur ne peut mentir là-dessus »la politique d'origine unique« un nom d'hôte désigne une adresse »Seule la troisième est un contrôle de sécurité, et le rebinding est plus ancien que la plupart des outils qui s'y fient.
La chaîne de rebinding. Chaque étape est un comportement documenté d'un outil bien entretenu : un TTL DNS très court, un en-tête Fetch que la spécification autorise une page à fixer, et un point d'entrée d'API dont la raison d'être est d'exécuter des commandes.

Les attaquants sont arrivés avant l'avis de sécurité. Le botnet RondoDox a ramassé la faille deux jours avant la divulgation publique, et la recherche ShadowRay 2.0 d'Oligo suit une campagne qui transforme les déploiements Ray en botnet auto-propagateur : les clusters compromis scannent à la recherche d'autres instances Ray et les infectent, une tâche cron tire de nouvelles charges depuis des dépôts contrôlés par l'attaquant toutes les quinze minutes, et la charge mine du Monero, ouvre des shells inversés et prend ce qu'elle trouve. Oligo a compté plus de 230 000 serveurs Ray exposés, une multiplication par dix depuis son premier rapport en 2024. Un seul cluster compromis a livré 240 Go de code source, de modèles et de jeux de données.

Ce qui écoute sur votre machine

Laissez Ray de côté et comptez autre chose : les serveurs HTTP non authentifiés liés à votre interface de loopback en ce moment même, et combien d'entre eux vous sauriez nommer sans regarder.

Lancez lsof -iTCP -sTCP:LISTEN -P après une semaine de travail et la liste est plus longue que vous ne l'imaginez. Vite sur 5173. Un service FastAPI sous uvicorn --reload sur 8000. Jupyter sur 8888, mlflow ui sur 5000, le port publié d'un conteneur Postgres, un serveur MCP sur 3000, un port de débogage laissé ouvert par un test qui a planté. Aucun d'eux ne demande qui vous êtes. Leurs auteurs ont fait l'hypothèse qu'ont faite les mainteneurs de Ray, et elle tient jusqu'au moment où un onglet dans une autre fenêtre se met à émettre des requêtes.

Les agents ont changé le volume. Un agent qui traite un ticket démarre des services comme allant de soi. Il lance le serveur de développement pour vérifier sa propre modification, monte une base de données pour jouer la migration, démarre le notebook pour regarder les données, lance l'API pour taper sur un endpoint. Il fait cela plusieurs fois par après-midi, sur plusieurs espaces de travail, et il ne nettoie pas, parce que rien ne le lui dit. Les processus survivent à la tâche. Le jeudi, l'interface porte une douzaine de services que vous n'avez pas lancés vous-même et que vous ne sauriez énumérer de mémoire, chacun se liant sans authentification parce que le guide de démarrage du framework dit que sur localhost tout va bien.

La position de Ray se lit différemment sur une machine qui fait tourner un agent de code : la sécurité et l'isolation doivent être imposées en dehors de la chose que vous avez lancée. Sur un portable où l'agent et le navigateur partagent un seul noyau, « en dehors » n'a nulle part où se poser.

Dans un espace de travail Bromure, 8265 n'est pas sur votre Mac

Bromure Agentic Coding fait tourner chaque espace de travail dans sa propre VM Ubuntu sur le framework Virtualization d'Apple. C'est toute la réponse à cette attaque, et elle tient sans que personne n'applique de correctif.

Quand l'agent lance ray start --head, le tableau de bord se lie à 127.0.0.1:8265 à l'intérieur de la VM. L'interface de loopback de votre Mac est une autre interface. Un fetch() relié depuis Safari sur l'hôte résout evil.example vers 127.0.0.1, se connecte au loopback de macOS, et n'obtient rien, parce que le service que la page est venue chercher se trouve un noyau plus loin. La chaîne publiée vise 127.0.0.1, l'adresse vers laquelle singularity se relie et l'adresse que nomme l'avis de sécurité, et dans un espace de travail Bromure il n'y a rien sur cette adresse.

Il en va de même pour la seconde moitié de l'avis, la partie sur l'usage d'un navigateur comme intermédiaire pour atteindre des instances Ray à l'intérieur d'un réseau d'entreprise que personne n'a mises sur internet. Les espaces de travail tournent en mode NAT par défaut, et le manuel est précis sur ce que cela apporte : les VM sont joignables depuis votre Mac mais ne sont pas exposées sur votre LAN physique, et les connexions entrantes venues d'ailleurs sont impossibles à moins de publier un service exprès. Publier, c'est un tunnel rapide Cloudflare par service que vous démarrez en appuyant sur un bouton.

Et vous obtenez l'inventaire. La carte Ports en écoute du tableau de bord de l'espace de travail interroge ss -tulnpH dans l'invité et liste chaque socket joignable de l'extérieur sous la forme IP-VM:port à laquelle vous vous connecteriez, avec le nom du processus qui la détient. La carte masque les sockets purement loopback, puisque rien à l'extérieur de la VM ne peut les atteindre. La même liste sort de bromure-cli vm <id> -L. Cette liste est le recensement des restes de l'agent, et macOS n'en tient aucun pour vous.

CLASSIQUE : une seule loopback, partagée par toutvotre Mac · 127.0.0.1:8265 ray · :5173 vite · :8888 jupyter · :3000 mcp:8000 uvicorn · :5000 mlflow · :5432 postgreslancés par l'agent, survivant à la tâche, aucun authentifiéun onglet dans une autre fenêtrese relie à 127.0.0.1et atteint tout celaBROMURE : la loopback de l'agent n'est pas la vôtreVM de l'espace de travail · son 127.0.0.1:8265 ray · :5173 vite · :8888 jupyterNAT : pas sur votre LAN physiquesockets joignables listés dans le tableau de bordvotre Mac · 127.0.0.1(rien de ce que l'agent a lancé)l'adresse que vise l'exploitle même onglet reliése connecte et trouveune interface vide
La même commande, deux agencements. Classiquement, tout ce que l'agent démarre rejoint le tas qu'une page web peut adresser. Dans Bromure, les services de l'agent vivent derrière une frontière réseau, et ce qui est joignable est une liste que vous pouvez lire.

Le rebond d'après

ShadowRay 2.0 vaut la lecture pour ce que fait ensuite un nœud compromis, parce que c'est la partie dont l'isolation doit répondre. Il scanne à la recherche d'autres instances Ray et les infecte. Il installe une tâche cron qui va chercher de nouvelles instructions toutes les quinze minutes. Il tend la main vers les identifiants et les jeux de données présents sur la machine.

Chacun de ces gestes est une connexion sortante, et un espace de travail Bromure confronte les connexions sortantes à des règles que vous avez écrites. L'éditeur Connexions sortantes du volet Guardrails contient un jeu de règles de style pf : autoriser ou refuser, par hôte, IP ou CIDR, protocole et port, évalué de haut en bas avec la première correspondance qui gagne, plus un contrôle pour le trafic qui ne correspond à rien.

allow web  api.github.com:443
allow tcp  registry.npmjs.org:443
deny  any  10.0.0.0/8
deny  any  192.168.0.0/16
default deny

Deux couches appliquent ce même jeu de règles. Le commutateur virtuel évalue chaque flux par IP de destination et par les noms d'hôtes qu'il a appris en espionnant les réponses DNS de l'invité, si bien qu'une règle écrite contre un nom continue de correspondre quand l'adresse bouge en dessous. Le proxy MITM évalue les mêmes règles par SNI TLS, et pour les règles web par méthode HTTP, ce qui vous permet d'écrire allow web api.example.com GET,HEAD. Bromure injecte un reset sur une connexion TCP refusée, si bien que la connexion échoue au lieu de rester suspendue.

Toute cette application tourne sur votre Mac, en dehors de la VM. L'interception transparente est active par défaut et détourne le HTTP et le HTTPS de l'invité vers Bromure même si quelque chose à l'intérieur efface HTTP_PROXY et HTTPS_PROXY. Un agent qui a lu la mauvaise page web, ou une charge arrivée avec un paquet, se trouve du mauvais côté des règles qu'il lui faudrait modifier. Bromure écrit chaque décision dans Fenêtre → Chronologie de sécurité sous forme de ligne Pare-feu : hôte, port, autorisé ou bloqué, dans un unique tableau chronologique aux côtés des décisions sur les identifiants, la chaîne d'approvisionnement et l'injection de prompt.

Le cron de quinze minutes a le même problème dans l'autre sens. Un espace de travail, c'est trois couches de stockage, et deux d'entre elles sont à vous pour être jetées : Effacer le home… ramène /home/ubuntu à son état d'après-clonage, Réinitialiser à la base… re-clone le disque système de l'espace de travail depuis l'image de base, et l'image de base reste en lecture seule à l'exécution. Une crontab dans le répertoire home ne survit à aucune des deux.

localhost était une convention

Les mainteneurs de Ray n'ont pas été négligents. Ils ont écrit un framework de calcul qui exécute des commandes, l'ont dit dans la documentation, et vous ont dit de mettre une frontière autour. Les gens qui le font tourner sur un portable ont pris l'interface de loopback pour cette frontière, ce qui est la conclusion à laquelle serveurs de développement, notebooks et tableaux de bord locaux invitent depuis vingt ans. C'était une convention plutôt qu'un contrôle. Elle tenait tant que les seules choses sur votre machine étaient des choses que vous aviez lancées vous-même, et elle a cessé de tenir dès que les navigateurs ont eu un moteur de script.

Un agent élargit l'écart chaque après-midi, un npm run dev à la fois. Le soin ne le referme pas, parce que le soin exige de savoir ce qui écoute, et la liste change à chaque commande que lance l'agent.

Cessez de les laisser partager une interface. Mettez le travail de l'agent dans sa propre machine, où les ports qu'il ouvre répondent à votre Mac et à rien d'autre, où les sockets joignables sont une liste que vous lisez plutôt qu'une hypothèse dont vous avez hérité, et où les connexions qui sortent sont confrontées à des règles que l'espace de travail ne peut pas modifier. Une page qui se relie à 127.0.0.1 trouve alors une interface vide et encaisse un refus.


Sources : CISA, « CISA Adds One Known Exploited Vulnerability to Catalog » (17 août 2026) · The Hacker News, « CISA Flags Actively Exploited Ray Flaw That Can Trigger Browser-Based RCE » (18 août 2026) · Avis GitHub GHSA-q279-jhrf-cc6v (CVE-2025-62593) · Oligo Security, « ShadowRay 2.0 »