PT-2026-105837 · Pypi · Mcp-Attlasian
Publicado
2026-10-01
·
Atualizado
2026-10-01
CVSS v3.1
5.5
Média
| Vetor | AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N |
Summary
The OAuth token fallback file storage in
OAuthConfig. save tokens to file() creates token files containing access tokens, refresh tokens, and cloud IDs with default filesystem permissions (typically 0644 on Linux, world-readable). Any local user on a shared system can read these files to obtain full Atlassian API credentials, enabling unauthorized access to the victim's Jira and Confluence data.Details
The vulnerability exists in
src/mcp atlassian/utils/oauth.py in the save tokens to file method.Step 1 -- Directory created without restrictive permissions:
At line 402-403, the token directory is created with
mkdir(exist ok=True) which uses the default umask (typically creating directories with mode 0755):python
# src/mcp atlassian/utils/oauth.py:402-403
token dir = Path.home() / ".mcp-atlassian"
token dir.mkdir(exist ok=True)Step 2 -- Token file written with default permissions:
At line 417-418, the token file containing sensitive credentials is written using
open() with no explicit mode, inheriting default umask permissions (typically 0644 on Linux):python
# src/mcp atlassian/utils/oauth.py:406-418
token path = token dir / f"oauth-{self.client id}.json"
if token data is None:
token data = {
"refresh token": self.refresh token,
"access token": self.access token,
"expires at": self.expires at,
"cloud id": self.cloud id,
"base url": self.base url,
}
with open(token path, "w") as f:
json.dump(token data, f)Step 3 -- The file contains full API credentials:
The token file contains:
access token: A valid OAuth access token for the Atlassian APIrefresh token: Can be exchanged for new access tokens indefinitelycloud id: Identifies the target Atlassian Cloud instancebase url: The target Data Center instance URL
No
os.chmod or os.fchmod is called anywhere after file creation.The primary storage via
keyring (line 373) is secure, but the fallback file storage at line 386 is always written in addition to keyring (line 386: self. save tokens to file(token data)). When keyring fails (common in headless/container/CI environments), the fallback becomes the only storage.PoC
bash
# Step 1: Victim runs mcp-atlassian with OAuth and completes the flow.
# This creates the token file.
# Step 2: As any other user on the same system, read the token file:
cat /home/victim/.mcp-atlassian/oauth-*.json
# Expected output (sensitive credentials in plaintext):
# {"refresh token": "eyJ...", "access token": "eyJ...", "expires at": 1741234567.0, "cloud id": "abc-123", "base url": null}
# Step 3: Verify the token works:
curl -H "Authorization: Bearer <stolen access token>"
"https://api.atlassian.com/ex/jira/<stolen cloud id>/rest/api/3/myself"
# Step 4: Use the refresh token to get a new access token:
curl -X POST "https://auth.atlassian.com/oauth/token"
-d "grant type=refresh token"
-d "client id=<from env>"
-d "client secret=<from env>"
-d "refresh token=<stolen refresh token>"Verify file permissions (on Linux/macOS):
bash
ls -la ~/.mcp-atlassian/
# drwxr-xr-x 2 user user 4096 Mar 10 12:00 .
# -rw-r--r-- 1 user user 256 Mar 10 12:00 oauth-abc123.json
# ^^ ^^ ^^
# world-readable!Impact
- Credential theft: Any local user can read the OAuth tokens and impersonate the victim on their Atlassian Cloud/Data Center instance.
- Persistent access: The refresh token allows the attacker to generate new access tokens indefinitely, even after the original access token expires.
- Full API access: The stolen tokens grant the same API permissions as the victim, including reading/writing Jira issues, Confluence pages, and potentially sensitive project data.
- Affected environments: Shared servers, CI/CD runners, multi-user workstations, and containerized deployments where the fallback file storage is used (keyring unavailable).
Recommended Fix
1. Set restrictive permissions on the directory and file:
python
# src/mcp atlassian/utils/oauth.py
import os
import stat
def save tokens to file(self, token data: dict | None = None) -> None:
"""Save the tokens to a file as fallback storage."""
try:
token dir = Path.home() / ".mcp-atlassian"
token dir.mkdir(exist ok=True, mode=0o700)
token path = token dir / f"oauth-{self.client id}.json"
if token data is None:
token data = {
"refresh token": self.refresh token,
"access token": self.access token,
"expires at": self.expires at,
"cloud id": self.cloud id,
"base url": self.base url,
}
# Open with restrictive permissions (owner-only read/write)
fd = os.open(
str(token path),
os.O WRONLY | os.O CREAT | os.O TRUNC,
stat.S IRUSR | stat.S IWUSR, # 0o600
)
try:
with os.fdopen(fd, "w") as f:
json.dump(token data, f)
except Exception:
os.close(fd)
raise
logger.debug(f"Saved OAuth tokens to file {token path} (fallback storage)")
except Exception as e:
logger.error(f"Failed to save tokens to file: {e}")2. Additionally, fix the directory permissions for existing installations:
python
# In init or from env, ensure existing directories are tightened
token dir = Path.home() / ".mcp-atlassian"
if token dir.exists():
os.chmod(str(token dir), 0o700)Correção
Encontrou algum problema na descrição? Tem algo a acrescentar? Fique à vontade para nos escrever 👾
Identificadores relacionados
Produtos afetados
Mcp-Attlasian