PYSEC-2026-4085

Vulnerability from pysec - Published: 2026-10-01 16:38 - Updated: 2026-10-01 17:10
VLAI
Details

Summary

_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.

This 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.

Details

In src/mcp_atlassian/servers/dependencies.py, three branches construct a fetcher and call _create_and_validate(). Only Branch 1 passes attach_ssrf_hook=True:

# Branch 1 (header PAT) — hook attached
return _create_and_validate(request, spec, header_config, "header_pat",
                             attach_ssrf_hook=True)

# Branch 2 (basic auth) — hook missing
return _create_and_validate(request, spec, user_config, "basic",
                             user_email=user_email)

# Branch 3 (OAuth/PAT) — hook missing
return _create_and_validate(request, spec, user_config, "oauth_pat",
                             user_email=user_email)

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.

Typical attack flow:

  1. Attacker controls or compromises an Atlassian instance (Cloud or Server)
  2. MCP server connects using basic auth or OAuth credentials (most production setups)
  3. Atlassian returns 302 Location: http://169.254.169.254/latest/meta-data/iam/security-credentials/
  4. The unprotected session follows the redirect
  5. AWS IAM credentials (or other internal service data) are returned to the attacker

PoC

Tested on commit d8bc786 (v0.21.1). No real credentials needed.

from unittest.mock import MagicMock
import requests

from mcp_atlassian.servers.dependencies import _make_ssrf_safe_hook
from mcp_atlassian.utils.urls import validate_url_for_ssrf
from mcp_atlassian.jira import JiraFetcher
from mcp_atlassian.jira.config import JiraConfig

config = JiraConfig(
    url="https://attacker.atlassian.net",
    auth_type="basic",
    username="victim@example.com",
    api_token="victim-token",
)
fetcher = JiraFetcher(config=config)
session = fetcher.jira._session

hooks = session.hooks.get("response", [])
print("hooks on basic-auth session:", [h.__name__ for h in hooks] or "none")

fake_redirect = MagicMock(spec=requests.Response)
fake_redirect.is_redirect = True
fake_redirect.headers = {"Location": "http://169.254.169.254/latest/meta-data/"}

blocked = False
for h in hooks:
    try:
        h(fake_redirect)
    except ValueError as e:
        blocked = True
        print("blocked:", e)

if not blocked:
    print("redirect to 169.254.169.254 not blocked on basic-auth session")

# show header-PAT branch does block it
hook = _make_ssrf_safe_hook(validate_url_for_ssrf)
try:
    hook(fake_redirect)
except ValueError as e:
    print("header-PAT branch blocks:", e)

Output:

image

$ uv run python3 /tmp/test.py
hooks on basic-auth session: none
redirect to 169.254.169.254 not blocked on basic-auth session
header-PAT branch blocks: Redirect blocked (SSRF): Blocked IP address: 169.254.169.254 (non-global)

Impact

Basic 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.

The 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.

Impacted products
Name purl
mcp-atlassian pkg:pypi/mcp-atlassian

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "mcp-atlassian",
        "purl": "pkg:pypi/mcp-atlassian"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.22.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ],
      "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"
      ]
    }
  ],
  "aliases": [
    "CVE-2026-77261",
    "GHSA-6529-c226-h328"
  ],
  "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 \u2014 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\u0027s there \u2014 it just isn\u0027t 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) \u2014 hook attached\nreturn _create_and_validate(request, spec, header_config, \"header_pat\",\n                             attach_ssrf_hook=True)\n\n# Branch 2 (basic auth) \u2014 hook missing\nreturn _create_and_validate(request, spec, user_config, \"basic\",\n                             user_email=user_email)\n\n# Branch 3 (OAuth/PAT) \u2014 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 \u2014 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 \u2014 cloud metadata endpoints, internal Kubernetes API, databases, or any service reachable from the MCP server\u0027s 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.",
  "id": "PYSEC-2026-4085",
  "modified": "2026-10-01T17:10:30.179158Z",
  "published": "2026-10-01T16:38:35.699701Z",
  "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"
    },
    {
      "type": "PACKAGE",
      "url": "https://pypi.org/project/mcp-atlassian"
    },
    {
      "type": "ADVISORY",
      "url": "https://github.com/advisories/GHSA-6529-c226-h328"
    }
  ],
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:L/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "MCP Atlassian: SSRF redirect protection missing for basic-auth and OAuth authentication branches"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline

Sightings and recorded dates, grouped by day · UTC

JSON
How durations are calculated

Following the VTEM framework (Black Hat USA 2015), time to patch starts when the vendor or a coordinator was informed, total time to patch at discovery, and disclosure to patch at public disclosure; vendor response time runs from the report to its acknowledgement. Time to exploit and time unpatched start at the earliest discovery or report, falling back to public disclosure. Unpatched time ends at the first recorded patch, or the calculation time when no patch is recorded. Exploitation to patch shows the interval between those two events; a published proof of concept is a public exploit, not exploitation.

Each result shows its source dates. A negative interval means the end event came first; when date-only inputs leave the order open, the result says so. Date-only inputs are approximate. The earliest evidence wins: KEV report dates and sightings are labelled as such. Records describing another vulnerability are listed but never calculated from, and a PoC alone does not establish exploitation. Missing patch evidence does not prove that no patch exists. Filters below change the event list only.

Events included in the timeline

Loading timeline…


    Loading…

    Loading…

    Loading…

    Related by attack behaviour

    Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.