CVE-2026-77262: MCP Atlassian: Path Traversal / Arbitrary File Read in confluence_upload_attachment MCP tool (incomplete fix of GHSA-xjgw-4wvw-rgm4)
## Summary `mcp-atlassian` exposes an MCP tool `confluence_upload_attachment` whose `file_path` argument is passed directly to `open(file_path, "rb")` without any path validation. An attacker able to invoke the tool can read arbitrary files readable by the server process and exfiltrate them into a multipart upload directed at an attacker-controlled Confluence host. In the default `streamable-http` transport the server binds `0.0.0.0` with no built-in authentication, making this remotely exploitable without credentials. This is the **read-side symmetric twin** of GHSA-xjgw-4wvw-rgm4 / CVE-2026-27825 (fixed in v0.17.0). The v0.17.0 patch only covered the download/write path; the upload path that reads local files was left unguarded. ## Details ### Vulnerable sink `src/mcp_atlassian/confluence/attachments.py:477` ```python with open(file_path, "rb") as fp: files = {"file": (filename, fp, content_type)} response = self.confluence.session.post(url, files=files, ...) ``` `file_path` is attacker-controlled end-to-end. ### Taint source `src/mcp_atlassian/servers/confluence.py:1290-1369`, tool definition at `:1307`: ```python file_path: Annotated[str, Field(description="Absolute path to the file to upload")] ``` No Pydantic `pattern=`, no validator, no `validate_safe_path()` call. ### Call chain 1. MCP client invokes `confluence_upload_attachment(page_id, file_path, ...)` 2. Server handler forwards to `ConfluenceFetcher.upload_attachment(file_path)` 3. `_upload_attachment_direct(file_path)` calls `open(file_path, "rb")` 4. File bytes are streamed in the multipart body of `POST /wiki/rest/api/content/{page_id}/child/attachment` to the configured Confluence base URL — which the attacker also controls (they provided `CONFLUENCE_URL` via env/config or target a server they already control). ### Asymmetry with the patched download path - `attachments.py:223` (download) — calls `validate_safe_path(local_path)` before `open(..., "wb")` - `attachments.py:272` (download) — calls `validate_safe_path(local_path)` before `open(..., "wb")` - `attachments.py:477` (upload) — **no validation** The `check_write_access` decorator does not help: it only gates `READ_ONLY_MODE` (default `false`) and is unrelated to filesystem path safety. ### Default exposure `src/mcp_atlassian/__init__.py:151` and `:360` — default transport is `streamable-http` binding `HOST=0.0.0.0` with no auth layer. Any network-reachable attacker can call MCP tools directly. ## Severity **Primary (default `streamable-http` deployment)** - Vector: `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N` - Score: **9.3 Critical** - Rationale: network-reachable, unauthenticated, scope-changed because files outside the MCP server's intended resource boundary (Confluence attachments) are exfiltrated. **Alternative (stdio-only deployment, conservative)** - Vector: `CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N` - Score: **6.6 High** - Rationale: local attacker controlling the MCP client context. Maintainer should pick the vector that reflects the documented default deployment. ## CWE CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') ## Affected - Product: `sooperset/mcp-atlassian` - Affected versions: **>= 0.17.0, <= HEAD (`d8bc78698a63cb6b321c7ca796d6329d448f7f6d`)** - Note: v0.17.0 is the fix commit for GHSA-xjgw-4wvw-rgm4 but only addressed the write-side. This read-side twin has existed since that release and remains unpatched on `main`. - Patched versions: **none at time of disclosure** ## Proof of Concept Fully reproduced twice end-to-end against a local stdlib HTTP stub acting as the Confluence API, driven by a real MCP stdio client (`mcp.ClientSession` + `stdio_client`) spawning the unmodified `mcp-atlassian` server at HEAD. ### Permalinks (commit-pinned) - Sink: https://github.com/sooperset/mcp-atlassian/blob/d8bc78698a63cb6b321c7ca796d6329d448f7f6d/src/mcp_atlassian/confluence/attachments.py#L477 - Source (tool def): https://github.com/sooperset/mcp-atlassian/blob/d8bc78698a63cb6b321c7ca796d6329d448f7f6d/src/mcp_atlassian/servers/confluence.py#L1307 - Safe download comparison: https://github.com/sooperset/mcp-atlassian/blob/d8bc78698a63cb6b321c7ca796d6329d448f7f6d/src/mcp_atlassian/confluence/attachments.py#L223 - Default transport bind: https://github.com/sooperset/mcp-atlassian/blob/d8bc78698a63cb6b321c7ca796d6329d448f7f6d/src/mcp_atlassian/__init__.py#L151 ### Reproduction 1. Start mock Confluence stub: `python mock_confluence.py` (binds `127.0.0.1:8443`, logs all multipart bodies) 2. Launch MCP client against real `mcp-atlassian` server over stdio with `CONFLUENCE_URL=http://127.0.0.1:8443` 3. Call tool: ```json { "name": "confluence_upload_attachment", "arguments": { "page_id": "123456", "file_path": "/etc/passwd", "comment": "poc" } } ``` ### Run 1 — `/etc/passwd` - MCP response: `isError=False`, `{"message": "Attachment uploaded successfully"}` - Stub captured 3339-byte multipart body containing: `root:x:0:0:root:/root:/bin/bash` (and full passwd contents) ### Run 2 — `/etc/hostname` - MCP response: `isError=False`, same success envelope - Stub captured 380-byte body containing: `ang3l-pc` Both runs used unmodified server code at commit `d8bc78698a63cb6b321c7ca796d6329d448f7f6d`. PoC artifacts (`mock_confluence.py`, `mcp_client.py`, `poc_run1.sh`, `poc_run2.sh`, `asymmetry.txt`, `ENVIRONMENT.md`) available on request to maintainers via this advisory thread. ## Impact - Arbitrary file read of anything readable by the server process UID: `/etc/passwd`, `/etc/shadow` (if running as root in container), `~/.aws/credentials`, `~/.ssh/id_rsa`, `.env` files, kube service-account tokens at `/var/run/secrets/kubernetes.io/serviceaccount/token`, application source, database dumps, private keys. - Exfiltration is covert: file bytes transit to the attacker's configured Confluence host inside a normal-looking multipart upload. No error surface; the tool returns success. - In the default `streamable-http` 0.0.0.0 deployment, no credentials are required. - Chains trivially with any AI agent that exposes this MCP server to untrusted prompt input — a prompt-injected assistant can be coerced into calling the tool with a sensitive path. ## Relationship to GHSA-xjgw-4wvw-rgm4 (CVE-2026-27825) GHSA-xjgw-4wvw-rgm4 (CVSS 9.1, fixed in v0.17.0) addressed an **arbitrary file write** in the same `attachments.py` module: attacker-controlled paths reaching `open(..., "wb")` on the download side. The fix introduced `validate_safe_path()` and applied it at lines 223 and 272. **The upload-side counterpart at line 477 was not updated.** Same module, same maintainer, same class of bug (unchecked path → `open()`), opposite direction (read vs write). This advisory reports the incomplete-fix twin. ## Remediation ### Required Call `validate_safe_path(file_path)` at the top of `ConfluenceFetcher.upload_attachment` and `_upload_attachment_direct` in `src/mcp_atlassian/confluence/attachments.py`, mirroring the download path at lines 223 and 272. Reject absolute paths outside a configurable allow-listed upload directory and reject any path containing `..` after normalization. ### Defense in depth Tighten the Pydantic tool schema at `src/mcp_atlassian/servers/confluence.py:1307`: ```python file_path: Annotated[ str, Field( description="Relative path within the configured upload directory", pattern=r"^(?!/)(?!.*\.\.)[\w\-./]+$", ), ] ``` This blocks absolute paths and `..` at the MCP schema layer before the handler is even entered. ### Additional hardening (out of scope but recommended) - Default `streamable-http` to `127.0.0.1` instead of `0.0.0.0`, or require an auth token when bound to a non-loopback interface. - Document that `file_path` must be confined to an operator-chosen directory and expose that directory via config.
Recommended action
Recommended action
Upgrade affected packages to a patched version: mcp-atlassian 0.22.0.
Technical details
- Vendor
- Not specified
- Product
- mcp-atlassian
- Exploitation
- none known
- Evidence
- official
Evidence and sources
This record is attributed to GitHub Advisories. Exploitation status and remediation guidance are kept separate from the vulnerability's technical severity.
Open primary source