CVE-2026-77249: MCP Atlassian: Incomplete fix for GHSA-7r34-79r5-rcc9: redirect-based SSRF via unhooked requests session in Jira user-permission lookup
One request path in mcp-atlassian before 0.22.0, the Jira assignee permission lookup, uses a bare `requests.get` that skips the redirect guard added for CVE-2026-27826. An attacker-run public server can redirect that call to an internal address. The SSRF is blind. Fixed in 0.22.0.
Published Sep 22, 2026 · Analysis Oct 22, 2026
What an attacker can do
The earlier fix for GHSA-7r34-79r5-rcc9 validated the X-Atlassian-Jira-Url header and attached _make_ssrf_safe_hook to the fetcher's session, f.jira._session. But JiraUserMixin._lookup_user_by_permissions in jira/users.py calls module-level requests.get against /rest/api/2/user/permission/search. That call does not use the hooked session and follows redirects by default. The equivalent call in development.py correctly uses self.jira._session.get.
The attack: in HTTP multi-tenant mode, which binds 0.0.0.0, the attacker sets the Jira URL header to a public server they run. It passes validate_url_for_ssrf(). That server answers the user search with an empty list, then answers the permission search with a 302 whose Location points at 169.254.169.254 or any internal host and port. The attacker triggers it with a tool that resolves an assignee, such as jira_create_issue or jira_update_issue. Credential validation also runs against the attacker's server, so no real Atlassian credentials are needed.
The result is a blind GET to internal hosts: port discovery, reachability of metadata endpoints, and internal endpoints that act on GET. The body is reflected only if the internal service returns JSON shaped like {"users": [...]}. This is weaker than the parent issue.
Who is affected
mcp-atlassian below 0.22.0, reported against 0.21.1, in HTTP or SSE multi-tenant mode where clients provide the Jira URL through headers. Stdio deployments with a fixed, trusted Jira URL are not exposed to header-supplied hosts.
pip show mcp-atlassianmcp-atlassian
- Ecosystem
- PyPI
- Affected
- < 0.22.0
- Fixed in
- 0.22.0
- Check
pip show mcp-atlassian
How to fix it
Upgrade to 0.22.0:
pip install --upgrade mcp-atlassian==0.22.0The reporter's suggested fix is to route the call through self.jira._session.get(...), which carries the hook and the authentication, and to audit for any other bare requests.* or httpx.* call that uses self.config.url. As interim protection, do not expose the HTTP transport to untrusted clients and block egress from the host to internal and link-local ranges.
How to tell if you were hit
Look for outbound requests from the MCP process to your internal ranges or 169.254.169.254, following a request to a public host that answered with a 302. Review MCP logs for jira_create_issue or jira_update_issue calls with assignee values that cannot be matched, coming from clients whose X-Atlassian-Jira-Url is not your Jira domain. Egress and DNS logs from the host will show the public redirecting domain.
Original advisory
From the GitHub Advisory Database (CC BY 4.0).
Summary
The fix for the SSRF vulnerability tracked as GHSA-7r34-79r5-rcc9 / CVE-2026-27826 is incomplete. That fix added two defenses: validate_url_for_ssrf() on the per-request X-Atlassian-Jira-Url / X-Atlassian-Confluence-Url headers (blocking a directly-internal base URL), and a redirect-validation hook (_make_ssrf_safe_hook) attached to the fetcher's HTTP session so that an attacker-controlled public host cannot redirect outbound requests to an internal address.
However, one outbound request path does not go through the hooked session. JiraUserMixin._lookup_user_by_permissions issues its request with the module-level requests.get instead of self.jira._session.get, so the redirect-validation hook never runs for it. An unauthenticated attacker (in the HTTP / multi-tenant transport mode that binds 0.0.0.0 with no credentials) can therefore set a public base URL that passes validate_url_for_ssrf(), then have their own server return an HTTP redirect to an internal address. The bare requests.get follows that redirect with no validation, resulting in a blind server-side request to an arbitrary internal host and port.
Affected component
src/mcp_atlassian/jira/users.py, method _lookup_user_by_permissions (the requests.get(...) call):
url = f"{self.config.url}/rest/api/2/user/permission/search" params = {"query": username, "permissions": "BROWSE"} ... response = requests.get( # module-level requests, NOT self.jira._session url, params=params, auth=auth, headers=headers, verify=self.config.ssl_verify, )
For comparison, the equivalent non-standard-endpoint call in src/mcp_atlassian/jira/development.py correctly uses the hooked session (self.jira._session.get(...)), and the SSRF redirect hook is attached only to that session in src/mcp_atlassian/servers/dependencies.py (get_session=lambda f: f.jira._session). The bare requests.get in users.py is the one outbound path the hook does not cover.
Details — why the existing defenses do not apply here
- self.config.url is attacker-controlled in the header-PAT branch of _get_fetcher (dependencies.py): header_config = spec.config_class(url=url_header_val, auth_type="pat", personal_token=token_header_val, ...).
- The middleware validates that header URL with validate_url_for_ssrf() before storing it, so it must resolve to a public address. The attacker simply points it at a public server they control — this passes validation.
- Credential validation (_create_and_validate → spec.validate_fn) runs against that same attacker-controlled server, so it returns success and no real Atlassian credentials are required.
- The redirect-validation hook is attached to f.jira._session. _lookup_user_by_permissions does not use that session — it uses bare requests.get, which follows redirects (allow_redirects=True by default) with no SSRF check.
Proof of Concept
Preconditions: the server is running in HTTP transport (streamable-http or sse) multi-tenant mode, as described in the GHSA-7r34-79r5-rcc9 advisory (binds 0.0.0.0, per-request header auth, no server-side credentials).
- Attacker stands up a public HTTP server http://attacker.example that:
- responds to GET /rest/api/2/user/search (and the atlassian client's user-find call) with 200 [] — an empty user list, forcing the direct-lookup fallthrough;
- responds to GET /rest/api/2/user/permission/search with 302 Location: http://169.254.169.254/latest/meta-data/ (or any internal host:port).
- Attacker sends a tool call to the MCP HTTP endpoint with headers:
- X-Atlassian-Jira-Url: http://attacker.example
- X-Atlassian-Jira-Personal-Token: anything and invokes a tool that resolves an assignee — e.g. jira_create_issue / jira_update_issue with assignee set to a value that the direct lookup cannot match.
- Flow: _get_account_id → _lookup_user_directly returns None (attacker returned []) → _lookup_user_by_permissions → requests.get("http://attacker.example/rest/api/2/user/permission/search?...") → attacker server returns 302 → bare requests follows the redirect to http://169.254.169.254/....
The server makes an outbound GET to the internal target, confirming SSRF.
Impact
Blind, unauthenticated server-side request forgery from the mcp-atlassian host. An attacker who can reach the HTTP transport endpoint can cause the server to issue GET requests to arbitrary internal hosts and ports (internal service reachability and port discovery, triggering of internal GET-actuated endpoints, reachability of cloud metadata endpoints). The response body is not reflected to the attacker except in the narrow case where an internal service returns a JSON object shaped like {"users": [...]}, so this is primarily a blind SSRF: weaker than the original CVE-2026-27826 (no general internal data exfiltration), but the redirect-based internal-reachability that GHSA-7r34-79r5-rcc9 intended to close remains exploitable through this code path in the latest release (v0.21.1).
Suggested Remediation
Route the request through the hooked session instead of the module-level requests, mirroring development.py:
response = self.jira._session.get( url, params=params, verify=self.config.ssl_verify, )
(The session already carries the appropriate authentication, so the manual auth / Authorization handling can be dropped.) More generally, auditing for any remaining bare requests.* / httpx.* calls that use self.config.url and routing them through the SSRF-hooked session would prevent recurrence.
Coordinated Disclosure
This appears to be an incomplete fix for GHSA-7r34-79r5-rcc9 (CVE-2026-27826). If you agree, I would kindly ask you to consider requesting a CVE ID for this residual instance through this repository's Security Advisory "Request CVE ID" workflow once confirmed, so that downstream users and distributions can track the additional fix. The severity here is lower than the parent advisory — it is a blind, redirect-only SSRF gated on the HTTP multi-tenant deployment mode — so a Medium rating seems appropriate, and I defer to your judgment on the final score. Thank you very much for your time and for your work on this project.
Credited to Yunkaiwjs.
Questions
- Is this the same as GHSA-7r34-79r5-rcc9?
It is a residual instance of it. That fix covered the header URL and the session hook, but this lookup used a separate unhooked requests call.
- Can the attacker read the internal response?
Mostly no. It is a blind SSRF, except where an internal service returns a JSON object shaped like users: [...].
- Do they need Atlassian credentials?
No. Validation runs against the attacker's own server, which returns success.
- Why is the severity medium?
The flaw is blind, redirect-only and limited to HTTP multi-tenant deployments. The CVSS 3.1 score is 5.3.
References
How this page is made: the data panel comes from the GitHub Advisory Database, CISA’s KEV catalog, the NVD and FIRST EPSS, and updates twice a day. The analysis is written by an AI model from the sources listed above and checked by automated rules before it is published. Check versions against the vendor's advisory before you act. Methodology
More Other MCP servers and tools vulnerabilities
All Other MCP servers and tools entriesCVE-2026-77361
obsidian-tc : Symlink read-ACL scope bypass via lexical path checks
medium4.7Oct 23, 2026CVE-2026-114126
Storybook dev server vulnerable to code injection from the local network
high8.7Oct 22, 2026CVE-2026-113731
djust: Arbitrary absolute-path file read in the find_handlers_for_template MCP tool
low3.3Oct 21, 2026CVE-2026-114125
Storybook: Missing origin validation in Storybook preview allows code injection from malicious websites
critical9.4Oct 21, 2026CVE-2026-111770
MCP TypeScript server SDK: Shared transport or server instances delivered one HTTP client's messages to another
high7.7Oct 20, 2026GHSA-953h-535j-jg2v
agent-router: MCP session resume does not verify the embedded anti-hijacking subject; default Helm seed makes session IDs forgeable
medium6.8Oct 19, 2026
