
Proof-of-concept di exploit per l'iniezione KQL in adx-mcp-server tramite il parametro table_name, che consente l'esfiltrazione di dati e comandi distruttivi in Azure Data Explorer.
Gravità: Alta (CVSS 8.3)
CWE: CWE-943 — Neutralizzazione impropria di elementi speciali nella logica delle query sui dati
Interessato: adx-mcp-server <= 1.1.0
Avviso: GHSA-vphc-468g-8rfp
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-33980
Riconoscimento: Romain Deperne
Tre strumenti MCP in adx-mcp-server interpolano il parametro table_name direttamente nelle query KQL (Kusto Query Language) tramite f-string. Un attaccante o un agente AI soggetto a prompt injection può leggere qualsiasi tabella nel cluster Azure Data Explorer, eseguire comandi di gestione (.drop table) o eseguire query analitiche arbitrarie — aggirando il confine di fiducia tra gli strumenti di metadati "sicuri" e lo strumento grezzo execute_query.
Stavo esaminando la lista awesome-mcp-servers cercando specificamente server MCP che fungono da wrapper per piattaforme di dati cloud — questi sono ad alto rischio perché sono progettati per dare agli agenti AI accesso diretto al database, e i parametri degli strumenti MCP sono completamente controllabili dall'attaccante (qualsiasi LLM che elabora dati non attendibili può essere soggetto a prompt injection per passare argomenti malevoli agli strumenti).
Esaminando come i parametri degli strumenti MCP raggiungevano le query di Azure Data Explorer è emerso che in server.py erano presenti tre interpolazioni dirette: f"{table_name} | getschema", f"{table_name} | sample {sample_size}" e f".show table {table_name} details".
La parte interessante è il bypass del confine di fiducia: il server espone sia uno strumento grezzo execute_query (per il quale i client MCP potrebbero richiedere l'approvazione umana) sia questi strumenti "sicuri" di ispezione dei metadati (che vengono approvati automaticamente). Iniettando attraverso gli strumenti di metadati è possibile aggirare qualsiasi politica di approvazione applicata dal client. È questo che ha trasformato il problema da "bug di iniezione" a una vera e propria violazione del confine di sicurezza.
La sintassi dei commenti KQL (//) consente a un payload di sopprimere la parte restante della query originale.
File: 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"
Tutti e tre passano table_name dagli argomenti dello strumento MCP direttamente a client.execute(config.database, query) senza alcuna validazione.
KQL consente di concatenare operatori di query con | e di eseguire comandi di gestione con il prefisso .. Il carattere // avvia un commento di riga. L'interpolazione diretta con f-string consente quindi l'iniezione di query:
f"{table_name} | getschema" → aggiungere | project Secret // per commentare | getschemaf".show table {table_name} details" → iniettare \n.drop table per concatenare un comando di gestionePerché questo è rilevante al di là di uno strumento execute_query grezzo: i client MCP spesso distinguono tra strumenti di sola lettura "sicuri" (approvati automaticamente) e strumenti di esecuzione grezzi (richiedono conferma). L'iniezione prende di mira gli strumenti di metadati "sicuri", aggirando il confine di approvazione.
Vedere poc.py per una dimostrazione completa. Payload principali:
# 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"
Chiamate agli strumenti 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 malevoli, trasformando il prompt injection in accesso completo ai dati