flawopen.com/Teardowns/cve-2026-71416-headroom-websocket-origin-hijacking
CVE-2026-71416 : détournement d'Origin WebSocket dans Headroom
Headroom ne validait pas Origin sur WebSocket ; un site visité pouvait relayer des requêtes LLM via le proxy. Passez à 0.35.0+.
Un réceptionniste laisse entrer n'importe qui dans un guichet privé parce qu'il ne vérifie pas le badge du visiteur. Origin est ce badge : le proxy doit le vérifier avant d'ouvrir un canal pouvant utiliser sa clé API stockée.
Concepts Clés et Termes
WebSocket- Connexion bidirectionnelle persistante qui commence par une requête HTTP de mise à niveau et transporte des messages dans les deux sens.
En-tête Origin- Schéma, hôte et port envoyés par le navigateur pour désigner la page à l'origine d'une requête. Pour le handshake WebSocket, le serveur doit appliquer sa propre politique de confiance ; CORS ne remplace pas ce contrôle.
CSWSH- Cross-Site WebSocket Hijacking : une page non fiable ouvre un WebSocket vers un service accessible qui ne vérifie pas l'origine initiatrice.
Identifiant ambiant- Secret déjà accessible au processus, par exemple une clé API dans une variable d'environnement, qui peut authentifier une requête sans identifiant fourni par le client.
CWE-1385- Absence de validation d'Origin dans WebSockets : le point de terminaison ne vérifie pas si l'origine de communication est autorisée.
Correspondances de référentiels
- Référence CWE pour la faiblesse décrite dans ce guide : CWE-1385
- OWASP GenAI 2025 — LLM06:2025 Agence excessive
Analyse de Cause Racine
Le gestionnaire WebSocket /v1/responses de Headroom acceptait la connexion du navigateur avant de vérifier Origin dans le handshake. Le proxy pouvait ensuite ajouter OPENAI_API_KEY depuis son propre environnement à la requête upstream. La politique de même origine du navigateur n'empêche pas à elle seule JavaScript d'ouvrir un WebSocket vers un autre hôte accessible ; le navigateur transmet normalement Origin, et le serveur doit rejeter les origines hors politique. L'exploitation nécessite donc que le navigateur de la victime puisse atteindre un proxy Headroom vulnérable et que celui-ci dispose d'identifiants ou d'intégrations utiles. L'avis indique que la connexion acceptée peut émettre des requêtes LLM arbitraires. L'exécution de commandes sur l'hôte dépend de la possibilité d'utiliser un outil configuré pour exécuter des commandes ; elle ne découle pas automatiquement de l'absence de vérification de l'en-tête. Le CNA indique que les versions antérieures à 0.35.0 sont affectées, tandis que les métadonnées de l'avis GitHub citent 0.23.0. Passez à 0.35.0 ou ultérieure et vérifiez la version installée.
Déroulement de l'Attaque Étape par Étape
Le proxy détient une autorité upstream
Un processus Headroom peut disposer d'une clé OpenAI dans OPENAI_API_KEY ; le client navigateur n'a pas besoin d'envoyer cette clé.
Un navigateur visite une page non fiable
Si le navigateur peut atteindre le point de terminaison WebSocket Headroom, le script de la page peut tenter de se connecter. Le navigateur inclut l'origine de la page dans le handshake.
Le gestionnaire vulnérable accepte sans politique d'origine
Dans le gestionnaire affecté, la requête atteint websocket.accept() sans rejet préalable d'un Origin non fiable. La politique de même origine n'est pas un contrôle d'autorisation WebSocket côté serveur.
Le proxy relaie une requête authentifiée
Headroom peut renseigner l'en-tête Authorization upstream manquant depuis son environnement. La page de l'attaquant peut alors provoquer des requêtes avec l'autorité upstream du proxy ; l'impact dépend des outils, des identifiants, de l'accès réseau et de l'interaction de l'utilisateur.
Le correctif rejette les origines interdites avant le routage
Le correctif upstream vérifie l'origine avant d'accepter et de relayer la session. Par défaut, les origines navigateur sont limitées à loopback, les origines explicites peuvent être configurées et une connexion interdite est fermée avec le code 1008.
Code Source : Vulnérable vs Sécurisé
L'avis désigne handle_openai_responses_ws() et l'absence de contrôle avant websocket.accept(). Le commit c632023 ajoute _is_allowed_websocket_origin() et rejette toute origine interdite avant le routage ; il conserve les clients natifs sans Origin, limite par défaut les origines de navigateur à loopback et permet les origines configurées via HEADROOM_WS_ORIGINS ou HEADROOM_CORS_ORIGINS. Ce code condensé isole la frontière de confiance modifiée ; les fonctions auxiliaires ne sont pas toutes reproduites. Les tests upstream couvrent Origin absent, loopback, origine distante, valeur mal formée et configuration explicite.
Extrait source: headroom/proxy/handlers/openai.py
async def handle_openai_responses_ws(self, websocket: WebSocket) -> None:
# Forme vulnérable : accepter la session du navigateur avant de valider l’origine.
await websocket.accept()
await self._relay_responses_session(websocket)
async def handle_openai_responses_ws(self, websocket: WebSocket) -> None:
ws_headers = dict(websocket.headers)
if not _is_allowed_websocket_origin(ws_headers):
logger.warning(
"event=websocket_origin_not_allowed origin=%r",
_header_get(ws_headers, "origin"),
)
await websocket.close(code=1008, reason="origin not allowed")
return
# Seule une session navigateur autorisée par la politique atteint le routage amont.
await websocket.accept()
await self._relay_responses_session(websocket)
Liste de Contrôle de Sécurité pour l'Ingénierie
- ✓Mettez Headroom à niveau vers 0.35.0 ou ultérieure et inventorie la version installée et l'image déployée, pas seulement le fichier de dépendances.
- ✓Validez
OriginWebSocket avantaccept()ou tout relais upstream ; refusez les origines non loopback, sauf autorisation explicite. - ✓Ne maintenez la compatibilité avec des clients natifs sans
Originque si leur type est vérifié indépendamment ; l'absence d'origine d'un navigateur n'est pas une autorisation. - ✓N'utilisez pas
*dans la configuration des origines autorisées d'un proxy doté d'identifiants ; énumérez exactement les origines fiables et testez la normalisation des ports par défaut. - ✓Dans la mesure du possible, liez le proxy de développement local à loopback et exigez une authentification ou des contrôles réseau si d'autres hôtes peuvent l'atteindre.
- ✓Utilisez des clés upstream à portée limitée et de courte durée si possible ; n'exposez pas shell ou d'autres outils à fort impact à des requêtes qui franchissent la frontière du navigateur.
- ✓Ajoutez des tests prouvant qu'une origine distante est fermée avec une violation de politique avant le routage upstream, tout en conservant les comportements prévus pour loopback, les origines configurées et les clients natifs approuvés.
- ✓Considérez l'exécution de commandes comme conditionnée par les capacités des outils configurés ; le défaut Origin n'entraîne pas automatiquement une RCE hôte.