CVE-2026-77262
CVSS 8.6 · Élevée CorrigéCVE-2026-77262 : lecture arbitraire de fichiers dans MCP Atlassian via confluence_upload_attachment
MCP Atlassian ouvrait tout file_path passé à son outil d’envoi Confluence : un appelant pouvait lire les fichiers de l’hôte et les exfiltrer en pièce jointe.
Pour décider
- Impact
- Un fichier lisible par le serveur MCP peut être envoyé comme pièce jointe vers Confluence.
- Conditions
- L’appelant doit pouvoir utiliser l’outil d’envoi et écrire vers la destination Confluence. Un agent manipulé est un scénario distinct.
- Action recommandée
- Installer MCP Atlassian 0.22.0 ou ultérieur et revoir les fichiers accessibles au processus MCP.
Fiche de l’avis
- Produit
- MCP Atlassian
- Paquet
- pypi · mcp-atlassian
- Versions affectées
- < 0.22.0
- Corrigé dans
- 0.22.0
- CVSS
- 8.6 Élevée
- Faiblesse
- CWE-22
- Accès requis
- Sans authentification
- Avis publié
- Crédit
- rootzero
Synthèse de l’enquête
L’outil confluence_upload_attachment de MCP Atlassian transmettait son argument file_path à open() sans contrôle de chemin, puis envoyait les octets vers Confluence. Un appelant ayant accès à cet outil pouvait envoyer tout fichier lisible par le processus vers une destination Confluence où il pouvait écrire. Le client réseau suppose un déploiement HTTP ; le scénario d’agent suppose en plus qu’il suive un contenu hostile, ce que la preuve de lecture de fichier ne teste pas. Le correctif précédent de GHSA-xjgw-4wvw-rgm4 ne protégeait que le sens du téléchargement. Corrigé en 0.22.0.
L’autre sens de la pièce jointe
Nous sommes revenus sur un correctif antérieur de MCP Atlassian : le téléchargement d’une pièce jointe validait désormais le chemin local avant d’y écrire. Nous avons vérifié l’opération symétrique, l’envoi. Dans attachments.py, ses appels à open() lisaient directement le file_path fourni à l’outil confluence_upload_attachment, sans la validation présente sur les chemins de téléchargement. L’avis précédent concernait une écriture arbitraire ; cette fois le risque était de lire un fichier local et de le pousser vers Confluence.
Nous avons lancé le serveur MCP non modifié avec un vrai client stdio et remplacé Confluence par un endpoint HTTP local d’observation. Un appel d’outil avec /etc/passwd, puis /etc/hostname, a placé le contenu de ces fichiers dans le corps multipart reçu par ce endpoint. Nous avons signalé cette lecture le 11 avril 2026. La validation a été ajoutée avec la PR #1448 et la version 0.22.0 le 10 juillet, en même temps que CVE-2026-77262. Un agent influencé par un document hostile est un scénario possible seulement si le client lui laisse choisir ce chemin et autorise l’appel ; notre preuve, elle, porte sur l’outil MCP.
Une pièce jointe franchit la frontière de l’hôte
MCP Atlassian expose les pièces jointes Confluence aux clients MCP. Le téléchargement écrit sur l’hôte ; l’envoi lit un fichier local pour le transmettre à Confluence. Nous avons comparé les validations de chaque côté de cette frontière.
Un serveur MCP occupe une place particulière. Dans un déploiement avec agent, un modèle peut rédiger les arguments des outils après avoir lu du texte non fiable : tickets Jira, pages Confluence, commentaires. Tout argument qui touche l’hôte, un chemin, une URL, une requête, doit être traité comme une entrée de l’attaquant, puisqu’une injection de prompt cachée dans un ticket peut amener l’agent à appeler l’outil avec les valeurs de son choix.
Les pièces jointes sont le point de contact évident entre Confluence et le système de fichiers de l’hôte. Le téléchargement écrit un fichier distant vers un chemin local, l’envoi lit un chemin local et l’expédie ailleurs. En février 2026, la version 0.17.0 a corrigé une écriture arbitraire de fichiers dans le sens du téléchargement (GHSA-xjgw-4wvw-rgm4, CVE-2026-27825) en introduisant une fonction validate_safe_path().
Les appels à open() du côté envoi
La revue des appels à open( dans src/mcp_atlassian/confluence/attachments.py a confirmé l’asymétrie. Les deux chemins de téléchargement (lignes 223 et 272) appellent validate_safe_path() avant d’écrire. Le chemin d’envoi ouvre ce qu’il reçoit. La définition de l’outil ne laissait aucun doute sur l’intention : le champ file_path indique qu’il accepte des chemins absolus et ne porte ni motif, ni validateur, ni appel à la fonction de contrôle.
Le défaut a ensuite été reproduit de bout en bout sur le serveur non modifié au commit d8bc786 : un vrai client MCP stdio (mcp.ClientSession) lançait mcp-atlassian, configuré vers un bouchon HTTP local jouant le rôle de Confluence, et appelait l’outil avec /etc/passwd. Le bouchon a reçu le fichier dans le corps multipart, et un second essai avec /etc/hostname a donné le même résultat.
Cause racine
L’outil MCP confluence_upload_attachment transmet son argument à ConfluenceFetcher.upload_attachment(). Avant la 0.22.0, le seul traitement était une conversion en chemin absolu et un test d’existence :
# src/mcp_atlassian/confluence/attachments.py (avant 0.22.0)
try:
# Convert to absolute path if relative
if not os.path.isabs(file_path):
file_path = os.path.abspath(file_path)
# Check if file exists
if not os.path.exists(file_path):
return {"success": False, "error": f"File not found: {file_path}"}
filename = os.path.basename(file_path)
attachment = self._upload_attachment_direct(
content_id, file_path, filename, comment, minor_edit
)
_upload_attachment_direct() ouvre ensuite le fichier et l’envoie dans une requête multipart vers le Confluence configuré :
# attachments.py:477 (commit d8bc786)
files = {"file": (filename, open(file_path, "rb"))}
...
response = self.confluence._session.put(
url, headers=headers, files=files, data=data
)
Côté téléchargement, dans le même fichier, le contrôle est bien présent :
# attachments.py:223 (téléchargement, corrigé en 0.17.0)
validate_safe_path(target_path)
...
with open(target_path, "wb") as f:
Il s’agit de CWE-22 (path traversal) sous sa forme la plus simple : aucune séquence ../ n’est même nécessaire, puisqu’un chemin absolu est accepté tel quel. Le décorateur @check_write_access posé sur l’outil n’a aucun rapport avec le système de fichiers : il refuse seulement l’appel lorsque READ_ONLY_MODE est activé, ce qui n’est pas le cas par défaut.
Exploitation
L’attaque tient en un appel d’outil. Avec le transport HTTP, après l’échange initialize habituel, c’est une requête JSON-RPC vers l’endpoint MCP :
POST /mcp HTTP/1.1
Host: mcp-atlassian.internal:8000
Content-Type: application/json
Accept: application/json, text/event-stream
Mcp-Session-Id: <identifiant de session reçu à l’initialize>
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"confluence_upload_attachment","arguments":{"content_id":"123456","file_path":"/etc/passwd","comment":"poc"}}}
Le serveur ne fait aucun contrôle au-delà de l’existence : il vérifie que /etc/passwd existe, l’ouvre et envoie un PUT vers <CONFLUENCE_URL>/rest/api/content/123456/child/attachment avec le fichier dans la partie file. L’outil répond par un message de succès. Lors de la reproduction, le bouchon a capturé un corps multipart de 3 339 octets contenant root:x:0:0:root:/root:/bin/bash et le reste du fichier.
Les octets deviennent une pièce jointe ordinaire sur une page Confluence. Les récupérer ne demande pas de contrôler Confluence : le même serveur expose confluence_download_attachment, qui renvoie le contenu d’une pièce jointe encodé en base64 via MCP, et toute personne qui peut lire la page dans Confluence peut l’y télécharger. Si l’attaquant contrôle l’URL Confluence configurée, le fichier lui arrive directement. La preuve de concept publique contient le client stdio, le bouchon Confluence et le script d’exécution.
Une voie par injection de prompt est possible si un agent lit un contenu Jira ou Confluence contrôlé par l’attaquant, suit l’instruction d’appeler l’outil d’envoi et dispose d’une destination inscriptible. La preuve publique de lecture de fichier ne démontre pas qu’un agent donné obéira à cette instruction.
Impact
Tout fichier lisible par l’utilisateur du serveur est exposé : clés SSH, identifiants cloud comme ~/.aws/credentials, fichiers .env qui peuvent contenir le jeton d’API Atlassian lui-même, jetons de compte de service Kubernetes. L’outil signale un succès et le trafic est un envoi normal vers l’hôte Confluence attendu : rien ne paraît anormal vu de l’extérieur.
Le vecteur publié, AV:N/PR:N avec changement de portée, décrit un déploiement HTTP : lorsque le transport sse ou streamable-http est choisi, le serveur écoute par défaut sur 0.0.0.0, et avant la 0.22.0 une requête non authentifiée était servie avec les identifiants Atlassian globaux de l’opérateur. Le write-up et l’avis présentent streamable-http comme transport par défaut. Dans le code examiné au commit d8bc786, la valeur par défaut de TRANSPORT est stdio, et les transports HTTP sont à activer explicitement. Pour un déploiement stdio, le rapport proposait lui-même un vecteur local (AV:L/PR:L, 6,6). Un déploiement stdio peut rester exposé indirectement via un agent qui suit du contenu hostile, mais cela exige un test distinct de l’agent ; le transport local du serveur ne le démontre pas.
Correctif
MCP Atlassian 0.22.0 corrige le problème dans la PR #1448, fusionnée sous le commit b041733, un durcissement de sécurité plus large du projet. La conversion par abspath() est remplacée par la fonction existante, dans le upload_attachment() de Confluence comme dans celui de Jira :
# src/mcp_atlassian/confluence/attachments.py (0.22.0)
# Confine the upload source to the workspace before it is read: reject
# traversal/absolute paths that escape CWD (arbitrary file read /
# exfiltration via a caller-supplied file_path).
file_path = str(validate_safe_path(file_path))
validate_safe_path() résout le chemin, liens symboliques compris, par rapport à un répertoire de base qui vaut par défaut le répertoire de travail courant, et lève une exception si le résultat en sort :
# src/mcp_atlassian/utils/io.py
resolved_base = Path(base_dir).resolve(strict=False)
p = Path(path)
if not p.is_absolute():
p = resolved_base / p
resolved_path = p.resolve(strict=False)
if not resolved_path.is_relative_to(resolved_base):
raise ValueError(
f"Path traversal detected: {path} resolves outside {resolved_base}"
)
Les variantes sont couvertes : upload_attachments() et le paramètre attachments de update_issue passent par la même fonction, et le paramètre content_file des outils de page, qui lisait un fichier et en publiait le contenu, est confiné de la même manière. Le message de commit cite GHSA-p6hp-93wp-fh6p parmi quatorze avis dont il ferme la partie envoi, plus la partie lecture d’un quinzième : le même point d’arrivée avait donc été signalé plusieurs fois. La PR renvoie aussi une 401 aux requêtes streamable-http non authentifiées, sauf si ALLOW_GLOBAL_CRED_FALLBACK est défini, et applique ENABLED_TOOLS au moment de l’appel, alors que ce réglage ne filtrait jusque-là que la liste des outils.
Une limite demeure, voulue : la frontière est le répertoire de travail. Un fichier situé dans ce répertoire, par exemple un .env placé à côté du serveur, peut toujours être envoyé.
Détection et mitigation
- Mettre à jour vers MCP Atlassian 0.22.0 ou une version ultérieure, et lancer le serveur depuis un répertoire de travail dédié qui ne contient aucun secret, puisque ce répertoire est désormais la frontière des envois.
- Sur les versions antérieures à 0.22.0, définir
READ_ONLY_MODE=truesi l’assistant n’a pas besoin d’écrire : l’outil d’envoi porte@check_write_accesset il est refusé dans ce mode. Le retirer deENABLED_TOOLSne suffit pas sur ces versions, car un outil masqué dans la liste restait appelable par son nom. - Ne pas exposer les transports
sseoustreamable-httpau-delà de la machine ou de l’équipe qui les utilise : écouter sur127.0.0.1avec--hostet placer un reverse proxy authentifiant devant lorsqu’un accès distant est nécessaire. - Passer en revue les pièces jointes Confluence envoyées par le compte de service, en cherchant des noms comme
passwd,id_rsa,credentials,.envoutoken, et rechercher dans les journaux du serveur la ligneUploading attachment fromsuivie d’un chemin extérieur au répertoire d’envoi prévu. - Faire tourner le serveur sous un utilisateur dédié ou dans un conteneur sans accès aux répertoires personnels, aux identifiants cloud ni aux jetons de compte de service montés.
Chronologie
| Date | Événement |
|---|---|
| 24/02/2026 | Publication de MCP Atlassian 0.17.0 avec le correctif côté téléchargement de GHSA-xjgw-4wvw-rgm4 |
| 11/04/2026 | Découverte du défaut côté envoi et signalement privé via un avis de sécurité GitHub |
| 10/07/2026 | Fusion de la PR #1448 (b041733) et publication de MCP Atlassian 0.22.0 |
| 10/07/2026 | Publication de l’avis GHSA-p6hp-93wp-fh6p, attribution de CVE-2026-77262 |
| 22/09/2026 | Ajout de l’avis à la GitHub Advisory Database |
Références
- Avis de l’éditeur · CVE-2026-77262 github.com/sooperset/mcp-atlassian/security/advisories/GHSA-p6hp-93wp-fh6p
- Fiche NVD · CVE-2026-77262 nvd.nist.gov/vuln/detail/CVE-2026-77262
- Fiche CVE.org · CVE-2026-77262 www.cve.org/CVERecord?id=CVE-2026-77262
- MITRE CWE-22 cwe.mitre.org/data/definitions/22.html
- Dépôt du projet github.com/sooperset/mcp-atlassian
- Preuve de concept et notes de reproduction github.com/romain-deperne/CVE-2026-77262
Citer cet avis
rootzero. « CVE-2026-77262 : lecture arbitraire de fichiers dans MCP Atlassian via confluence_upload_attachment ». Recherche de vulnérabilités, rootzero, 10 juillet 2026. https://rootzero.eu/research/cve/cve-2026-77262-mcp-atlassian-arbitrary-file-read/
#Recherche de vulnérabilités
Faites auditer votre code avant qu’un autre ne le fasse.
Votre équipe doit décider quoi corriger avant une livraison ou évaluer une dépendance critique. Nous définissons une mission autour de cette décision, avec des preuves et des priorités de correction expliquées.