VDB
KO
MEDIUM 6.5

GHSA-489g-7rxv-6c8q

MCP Atlassian: DNS-rebinding TOCTOU bypass of the SSRF fix (CVE-2026-27826)

Details

### Summary GHSA-7r34-79r5-rcc9's fix added `validate_url_for_ssrf`, which resolves the attacker-controlled `X-Atlassian-{Jira,Confluence}-Url` header host **once at middleware time** and trusts the result. But the outbound request is later built with the **raw hostname** and **re-resolves at connect time with no IP pinning**. An attacker-controlled rebinding DNS name returns a public IP on the guard's lookup (validation passes) and `169.254.169.254` / an internal IP on the request's lookup (the socket connects there) → unauthenticated SSRF to cloud metadata / internal services on the **patched** build.

### Relationship to CVE-2026-27826 / GHSA-7r34-79r5-rcc9 (incomplete fix — please read first) This is an **incomplete-fix sibling** of the published `GHSA-7r34-79r5-rcc9` (the `X-Atlassian-*-Url` header SSRF). That fix (PR #986/#1005) added a single middleware-time resolve + allowlist DNS-skip, but **does not pin the validated IP to the connection** — the fetcher re-resolves the raw hostname at connect time, so the documented SSRF mitigation is incomplete against DNS-rebinding. The other advisory `GHSA-xjgw-4wvw-rgm4` (file-write) is unrelated. Verified live (2026-06-27): neither advisory, nor any open PR/issue (`rebind`/`TOCTOU`/`getaddrinfo`/`pin` → 0), covers connect-time re-resolution. Filing as an incomplete-fix of GHSA-7r34 (not a standalone fresh SSRF).

### Affected `src/mcp_atlassian/utils/urls.py` + `servers/main.py` + `servers/dependencies.py`, HEAD `ba72540` (PyPI `mcp-atlassian`, patched ≥0.17.0). **CWE-918** (SSRF) via **CWE-367** (TOCTOU).

### Vulnerable code `utils/urls.py` `validate_url_for_ssrf` (≈184-205) resolves + validates, then returns a **string verdict, not a pinned IP**: ```python def validate_url_for_ssrf(url: str) -> str | None: # returns an error string or None — NO IP is pinned ... # resolves the host, checks each resolved IP is global, then DISCARDS the IP ``` `servers/main.py:526,534` calls it once in middleware. `servers/dependencies.py:544-561` then builds the fetcher with `url = <raw header hostname>` (no pinned IP, no custom resolver / cached-getaddrinfo adapter), so the actual request re-resolves the name.

### PoC (executed — boundary demonstration) The PoC loads the **real** `urls.py` by path (`importlib`, `sha256` printed) and drives the genuine `validate_url_for_ssrf`, simulating the two resolutions via `getaddrinfo`: ``` [CHECK ] validate_url_for_ssrf('http://rebind.attacker.example') -> None (getaddrinfo#1 = 93.184.216.34 global -> guard PASSED) [CONNECT] getaddrinfo call #2 returned 169.254.169.254 -> the socket connects HERE [PROOF ] guard validated IP 93.184.216.34 but connection targets 169.254.169.254 => SSRF on the PATCHED build [CONTROL] if guard SAW 169.254.169.254 at check time -> blocks it correctly [PIN ] validate_url_for_ssrf returns a verdict (None), NOT an IP; dependencies.py builds url=raw hostname -> NO pin ALL PoC ASSERTIONS PASSED — DNS-rebind TOCTOU bypass demonstrated. ``` **Honest scope of the PoC:** this is a **boundary** demonstration — it proves the structural TOCTOU (the guard validates an IP it then discards; the connection re-resolves an unpinned hostname). It does **not** demonstrate a live end-to-end SSRF on a running server; that additionally requires an attacker-controlled fast-rebinding authoritative DNS responder winning the resolve→connect window. Flagging this explicitly rather than overclaiming.

### Impact Same as parent GHSA-7r34 (unauth read of cloud-metadata IAM creds / internal-service reach), reachable again on the patched version. The `X-Atlassian-*-Url` headers are processed in `UserTokenMiddleware` before fetcher creation, so an unauthenticated/low-priv caller controls the host.

### Severity **High — CVSS v3.1 `AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:L/A:N` ≈ 7.x**, aligned to the parent (8.2) with `AC:H` for the rebinding-race precondition. Honest caveat (above): the executed PoC proves the missing IP-pin structurally; a live exploit additionally needs an attacker rebinding-DNS. Not Critical.

### Remediation Pin the connection to the IP that `validate_url_for_ssrf` validated: use a custom resolver / cached-`getaddrinfo` `requests`-adapter (or pass the validated IP with a `Host` header), so the connect cannot re-resolve to a different address.

### Dedup / freshness (re-verified live 2026-06-27) Advisories `GHSA-7r34-79r5-rcc9` (original header SSRF this bypasses) + `GHSA-xjgw-4wvw-rgm4` (file-write, unrelated). Neither covers connect-time re-resolution / rebinding. PR [#986](https://github.com/sooperset/mcp-atlassian/pull/986)/[#1005](https://github.com/sooperset/mcp-atlassian/pull/1005) (the fix) add a single middleware-time resolve + allowlist DNS-skip, no pinning. `gh search prs/issues` for rebind/TOCTOU/getaddrinfo/pin → 0. First-party code. **Fresh** at HEAD `ba72540`.

Are you affected?

Enter the version of the package you're using.

Affected packages

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

References