{"id":"GHSA-6529-c226-h328","summary":"MCP Atlassian: SSRF redirect protection missing for basic-auth and OAuth authentication branches","details":"### Summary\n\n`_make_ssrf_safe_hook()` blocks HTTP redirects to private/internal IPs by validating the `Location` header before the client follows a `3xx` response. The problem is that this hook is only attached in one of three authentication branches — the header-PAT path. Basic auth and OAuth branches skip it entirely, so if the connected Atlassian server returns a redirect to something like `http://169.254.169.254/`, the `requests` session follows it without complaint.\n\nThis is an incomplete fix for GHSA-7r34-79r5-rcc9. The hook works fine when it's there — it just isn't there for most production auth configurations.\n\n### Details\n\nIn `src/mcp_atlassian/servers/dependencies.py`, three branches construct a fetcher and call `_create_and_validate()`. Only Branch 1 passes `attach_ssrf_hook=True`:\n\n```python\n# Branch 1 (header PAT) — hook attached\nreturn _create_and_validate(request, spec, header_config, \"header_pat\",\n                             attach_ssrf_hook=True)\n\n# Branch 2 (basic auth) — hook missing\nreturn _create_and_validate(request, spec, user_config, \"basic\",\n                             user_email=user_email)\n\n# Branch 3 (OAuth/PAT) — hook missing\nreturn _create_and_validate(request, spec, user_config, \"oauth_pat\",\n                             user_email=user_email)\n```\n\n`attach_ssrf_hook` defaults to `False`, so branches 2 and 3 silently skip the protection. The hook itself (`_make_ssrf_safe_hook`) is straightforward — it checks `response.is_redirect`, grabs the `Location` header, and calls `validate_url_for_ssrf()` to reject private IPs. It works correctly when present.\n\nTypical attack flow:\n\n1. Attacker controls or compromises an Atlassian instance (Cloud or Server)\n2. MCP server connects using basic auth or OAuth credentials (most production setups)\n3. Atlassian returns `302 Location: http://169.254.169.254/latest/meta-data/iam/security-credentials/`\n4. The unprotected session follows the redirect\n5. AWS IAM credentials (or other internal service data) are returned to the attacker\n\n### PoC\n\nTested on commit `d8bc786` (v0.21.1). No real credentials needed.\n\n```python\nfrom unittest.mock import MagicMock\nimport requests\n\nfrom mcp_atlassian.servers.dependencies import _make_ssrf_safe_hook\nfrom mcp_atlassian.utils.urls import validate_url_for_ssrf\nfrom mcp_atlassian.jira import JiraFetcher\nfrom mcp_atlassian.jira.config import JiraConfig\n\nconfig = JiraConfig(\n    url=\"https://attacker.atlassian.net\",\n    auth_type=\"basic\",\n    username=\"victim@example.com\",\n    api_token=\"victim-token\",\n)\nfetcher = JiraFetcher(config=config)\nsession = fetcher.jira._session\n\nhooks = session.hooks.get(\"response\", [])\nprint(\"hooks on basic-auth session:\", [h.__name__ for h in hooks] or \"none\")\n\nfake_redirect = MagicMock(spec=requests.Response)\nfake_redirect.is_redirect = True\nfake_redirect.headers = {\"Location\": \"http://169.254.169.254/latest/meta-data/\"}\n\nblocked = False\nfor h in hooks:\n    try:\n        h(fake_redirect)\n    except ValueError as e:\n        blocked = True\n        print(\"blocked:\", e)\n\nif not blocked:\n    print(\"redirect to 169.254.169.254 not blocked on basic-auth session\")\n\n# show header-PAT branch does block it\nhook = _make_ssrf_safe_hook(validate_url_for_ssrf)\ntry:\n    hook(fake_redirect)\nexcept ValueError as e:\n    print(\"header-PAT branch blocks:\", e)\n```\n\nOutput:\n\n\u003cimg width=\"2490\" height=\"214\" alt=\"image\" src=\"https://github.com/user-attachments/assets/c7d9d7e2-4c37-4abd-95a3-4ddd9f6bd735\" /\u003e\n\n```\n$ uv run python3 /tmp/test.py\nhooks on basic-auth session: none\nredirect to 169.254.169.254 not blocked on basic-auth session\nheader-PAT branch blocks: Redirect blocked (SSRF): Blocked IP address: 169.254.169.254 (non-global)\n```\n\n### Impact\n\nBasic auth and OAuth cover most production Atlassian Cloud deployments, so this affects the majority of HTTP-mode multi-user setups. An attacker with control over the Atlassian server can redirect MCP server requests to internal infrastructure — cloud metadata endpoints, internal Kubernetes API, databases, or any service reachable from the MCP server's network.\n\nThe fix is one line per affected branch: pass `attach_ssrf_hook=True` to `_create_and_validate()` in branches 2 and 3, the same way branch 1 already does.","aliases":["CVE-2026-77261"],"modified":"2026-09-22T21:00:04.603039173Z","published":"2026-09-22T20:35:13Z","database_specific":{"github_reviewed_at":"2026-09-22T20:35:13Z","nvd_published_at":"2026-09-22T18:17:18Z","cwe_ids":["CWE-918"],"severity":"HIGH","github_reviewed":true},"references":[{"type":"WEB","url":"https://github.com/sooperset/mcp-atlassian/security/advisories/GHSA-6529-c226-h328"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-77261"},{"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-6529-c226-h328/GHSA-6529-c226-h328.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:L/A:N"}]}