
Эксплойт для проверки концепции произвольного чтения файлов в mcp-atlassian через path traversal в confluence_upload_attachment, с анализом и скриптами воспроизведения.
Серьёзность: Высокая (CVSS 8.6)
CWE: CWE-22 — Path Traversal
Затронуто: sooperset/mcp-atlassian < 0.22.0
Исправлено в: 0.22.0
Консультация: GHSA-p6hp-93wp-fh6p
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-77262
Автор: Romain Deperne
Инструмент MCP confluence_upload_attachment передаёт свой аргумент file_path напрямую в open(file_path, "rb") без какой-либо проверки пути. Злоумышленник, который может вызвать этот инструмент, читает произвольные файлы из файловой системы сервера и выводит их через multipart-загрузку на контролируемую злоумышленником конечную точку Confluence. Транспорт streamable-http по умолчанию привязывается к без аутентификации, что делает эту уязвимость эксплуатируемой удалённо без учётных данных.
0.0.0.0Это симметричный близнец со стороны чтения ранее исправленного GHSA-xjgw-4wvw-rgm4 — исправление в v0.17.0 покрывало только путь записи/загрузки. Путь загрузки остался незащищённым.
mcp-atlassian уже получил исправление path traversal в v0.17.0 (GHSA-xjgw-4wvw-rgm4), которое закрыло путь записи — загрузку вложений на локальный диск. Моя гипотеза: когда исправление применяется к одному направлению симметричной операции, другое направление часто упускается из виду.
Проверка мест вызова open( в attachments.py показала, что путь загрузки вызывал validate_safe_path(local_path) перед открытием файла, а путь выгрузки — нет. В двух направлениях была несогласованная проверка пути.
Определение инструмента подтвердило это: file_path: Annotated[str, Field(description="Absolute path to the file to upload")] без ограничения pattern=, без валидатора, ничего. Поле буквально документировано как принимающее абсолютный путь без каких-либо ограничений.
Я воспроизвёл это от начала до конца: запустил реальный процесс сервера mcp-atlassian, управлял им через MCP stdio-клиент (mcp.ClientSession), направил его на локальный mock-заглушку HTTP Confluence и вызвал confluence_upload_attachment с file_path=/etc/passwd. Mock-сервер зарегистрировал полное содержимое /etc/passwd в теле multipart. Два полных прогона воспроизведения, оба зарегистрированы в файлах PoC.
Привязка HOST=0.0.0.0 по умолчанию без аутентификации делает эту уязвимость эксплуатируемой удалённо без учётных данных в конфигурации по умолчанию.
Файл: src/mcp_atlassian/confluence/attachments.py, строка 477
with open(file_path, "rb") as fp: # ← file_path контролируется злоумышленником
files = {"file": (filename, fp, content_type)}
response = self.confluence.session.post(url, files=files, ...)
Определение инструмента (src/mcp_atlassian/servers/confluence.py:1307):
file_path: Annotated[str, Field(description="Absolute path to the file to upload")]
# Нет pattern=, нет валидатора, нет validate_safe_path()
Асимметрия с исправленным путём загрузки:
# attachments.py:223 — ИСПРАВЛЕНО (путь загрузки)
validate_safe_path(local_path) # ← защита добавлена в v0.17.0
open(local_path, "wb")
# attachments.py:477 — УЯЗВИМО (путь выгрузки)
open(file_path, "rb") # ← нет защиты, пропущено в v0.17.0
Исправление v0.17.0 для GHSA-xjgw-4wvw-rgm4 добавило вызовы validate_safe_path() на стороне записи (загрузка вложений на локальный диск), но не проверило сторону чтения (выгрузка локальных файлов в Confluence). Декоратор check_write_access не связан с этим — он только ограничивает READ_ONLY_MODE.
Сетевая доступность по умолчанию (src/mcp_atlassian/__init__.py:151):
HOST = "0.0.0.0" # привязка ко всем интерфейсам
# нет слоя аутентификации в транспорте streamable-http
Полностью воспроизведено от начала до конца против локальной HTTP-заглушки, имитирующей API Confluence. См. mcp_client.py, mock_confluence.py и poc_run1.sh.
# poc_run1.sh — читает /etc/passwd через confluence_upload_attachment
# 1. Запуск mock-конечной точки Confluence
python mock_confluence.py &
# 2. Вызов инструмента MCP с payload path traversal
python mcp_client.py \
--tool confluence_upload_attachment \
--page-id 123456 \
--file-path /etc/passwd \
--filename passwd.txt
# → содержимое /etc/passwd появляется в логе mock_confluence.py
/etc/passwd, SSH-ключи, .env, секреты приложения)streamable-http по умолчанию привязывается к 0.0.0.0, без аутентификации; любой злоумышленник с сетевым доступом может вызывать инструменты MCP напрямую