FlawOpen

flawopen.com/Teardowns/cve-2026-105699-langflow-mcp-cross-project-file-disclosure

● CVE-2026-105699 · CVSS v4 7.1 · Hoch
Sicherheitsforschung · FlawOpen

CVE-2026-105699: MCP-Dateizugriff in Langflow

Langflow authentifizierte die MCP-Verbindung, autorisierte aber nicht jede angeforderte Datei. Ein Benutzer konnte Flow-Dateien anderer Nutzer lesen; Version 1.9.1 behebt die Eigentümerprüfung.[1], [2], [5]

CVSS-VektorCVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N
💡 Einfache Erklärung (ELI5)

Stell dir ein Hotel vor, das deinen Schlüssel prüft und dir dann das Gepäck aus jedem Zimmer gibt, sobald du dessen Anhänger nennst. Der Schlüssel beweist, dass du ins Hotel darfst, nicht dass der Koffer eines anderen Gasts dir gehört. Langflows MCP-Lesezugriff muss Besitzer und Projekt prüfen, bevor die Datei geöffnet wird.[1]

Kernkonzepte & Begriffe

Model Context Protocol (MCP)
Protokoll, über das eine KI-Anwendung Werkzeuge und Ressourcen von einem Server anfordert. Der Server muss jedes angeforderte Objekt weiterhin autorisieren.[1]
Projektgebundener MCP-Endpunkt
Eine Langflow-MCP-Verbindung, die einem Projekt zugeordnet ist. Der Zugriff auf diesen Endpunkt erlaubt keinen Zugriff auf Dateien aller Projekte.[1]
resources/read
MCP-Operation, mit der der Server eine durch eine URI bezeichnete Ressource zurückgeben soll.[1]
Objektbezogene Autorisierung
Prüfung, ob der angemeldete Benutzer auf genau den angeforderten Datensatz oder die Datei zugreifen darf, nicht nur auf den Endpunkt.[1], [6]
CWE-639
Autorisierungsumgehung über einen benutzergesteuerten Schlüssel: Eine geänderte Kennung führt zum Datensatz eines anderen Benutzers.[6], [2]

Framework-Zuordnungen

  • CWE-Referenz für die in diesem Leitfaden beschriebene Schwachstelle: CWE-639

Ursachenanalyse

In den betroffenen Langflow-Versionen 1.6.8 bis 1.9.0 authentifizierte der projektgebundene MCP-Transport den Zugriff auf die Projekt-ID in der Verbindungs-URL. Anschließend nahm der resources/read-Handler eine Ressourcen-URI entgegen, extrahierte Flow-ID und Dateinamen und übergab sie an storage_service.get_file(), ohne zu prüfen, ob der Flow dem angemeldeten Benutzer oder dem aktuellen Projekt gehörte. Der Speicher liest anhand von Namespace und Dateiname; Langflows Benutzer- und Projektberechtigungen kennt er nicht. Deshalb konnte ein angemeldeter Benutzer mit Zugriff auf den MCP-Endpunkt seines eigenen Projekts eine Datei im Flow-Namespace eines anderen Benutzers anfordern. Das separat dokumentierte globale Verhalten von resources/list und tools/list konnte Kennungen offenlegen und das Auffinden erleichtern, war für den Lesezugriff aber nicht erforderlich. Es handelt sich um eine authentifizierte Vertraulichkeitsverletzung zwischen Benutzern, nicht um anonymen Zugriff oder Codeausführung. Der CVE-Datensatz weist CVSS 4.0 mit 7,1 High und CWE-639 aus. Langflow 1.9.1 behebt den Pfad: handle_read_resource() ermittelt den angemeldeten Benutzer und sucht den Flow mit Flow.user_id == current_user.id sowie bei projektgebundenem Transport zusätzlich Flow.folder_id == project_id; nicht autorisierte Namespaces werden vor dem Speicherzugriff abgewiesen. Der Patch weist außerdem Trennzeichen und Traversal-Sequenzen zurück, prüft Pfadbegrenzungen bei Lesen/Schreiben/Löschen und beschränkt globale Listen auf den aktuellen Benutzer. Der Regressionstest des Maintainers verweigert das Lesen eines fremden Flows.[1], [2], [3], [5]

Schritt-für-Schritt Angriffsablauf

Schritt 1

Ein Benutzer meldet sich an seinem eigenen Projekt-MCP-Endpunkt an

Der Angreifer benötigt ein Langflow-Konto und Zugriff auf mindestens eine projektgebundene MCP-Verbindung. Die Schwachstelle umgeht keine Anmeldung.[1]

Schritt 2

Der Client fordert eine Ressourcen-URI an

Die MCP-Anfrage resources/read enthält eine URI, deren letzte Pfadbestandteile einen Flow-Namespace und eine Datei bezeichnen.[1]

Schritt 3

Der anfällige Handler vertraut den Kennungen

handle_read_resource() extrahierte ID und Namen, ordnete den Flow aber weder dem Benutzer noch dem Projekt des Endpunkts zu.[1], [3]

Schritt 4

Der Speicher liest anhand des Namespace

storage_service.get_file() gab die Datei des angegebenen Namespace zurück. Dem Speicher fehlte der Kontext zum Benutzerbesitz.[1]

Schritt 5

Die Antwort überschreitet die Benutzergrenze

Der Anfragende konnte eine Flow-Datei eines anderen Benutzers erhalten. Globale Listen konnten Kennungen offenlegen, waren für die fehlende Autorisierung jedoch nicht nötig.[1], [2]

Quellcode: Verwundbar vs. Sicher

Dies sind gezielte Python-Auszüge des dokumentierten Pfads, keine eigenständige Anwendung. Der anfällige Pfad behandelte den vom Client gelieferten Namespace als ausreichende Berechtigung. Die Korrektur in 1.9.1 löst zuerst den Flow im Bereich des angemeldeten Benutzers und bei projektgebundenem MCP-Transport zusätzlich im aktuellen Projekt auf; erst dann darf der Speicher lesen. Der Patch validiert auch Dateinamen und erzwingt Pfadbegrenzungen in der Speicherschicht, damit andere Lese-, Schreib- oder Löschpfade die Prüfung nicht umgehen. Die Autorisierung gehört in den Dienst, der Benutzer und Projekt kennt; ein Speicheradapter kann den Besitz nicht ableiten. Umfang des Ausschnitts: Der echte Handler erlaubt eine Datei aus dem Benutzerbereich außerdem nur dann, wenn der Namespace der ID des angemeldeten Benutzers entspricht und der Server nicht auf ein Projekt beschränkt ist. Dieser separate Zweig fehlt im Ausschnitt; daraus darf kein Zugriff auf beliebige Namespaces abgeleitet werden. Die Upstream-Regressionstestsuite enthält test_handle_read_resource_denies_other_users_flow und prüft damit die Ablehnung des benutzerübergreifenden Lesezugriffs.[1], [3], [4], [7]

Quelltextauszug: mcp_utils.py — gezielte Quellcodeauszüge

VERWUNDBARE IMPLEMENTIERUNG
# Auszug aus dem betroffenen Leseweg: Geparste IDs wurden direkt an den Speicher übergeben.
content = await storage_service.get_file(
    flow_id=flow_id,
    file_name=filename,
)
GEHÄRTETER SICHERHEITS-PATCH
# Quellnahe Reduktion der Autorisierungsgrenze aus Version 1.9.1.
current_user = current_user_ctx.get()
query = select(Flow).where(
    Flow.id == namespace_id,
    Flow.user_id == current_user.id,
)
if project_id is not None:
    query = query.where(Flow.folder_id == project_id)
flow = (await session.exec(query)).first()
if flow is None:
    raise ValueError("Resource not found or access denied")
content = await storage_service.get_file(
    flow_id=namespace_id,
    file_name=filename,
)

Checkliste für Engineering & Systemsicherheit

Quellen