Resumen
Se ha identificado una vulnerabilidad de inyección DQL en varias herramientas de lectura del paquete @dynatrace-oss/dynatrace-mcp-server. Esta vulnerabilidad permite a un llamador eludir el ámbito de campos documentado, los límites de ventana temporal y los topes de visualización definidos por las herramientas, mediante la inyección de etapas de pipeline DQL a través de parámetros tipados como identificadores simples.
Detalles técnicos
Varias herramientas interpolan parámetros suministrados por el llamador directamente en cadenas de consulta DQL sin aplicar entrecomillado ni escapado. Los parámetros afectados están documentados en sus esquemas Zod como identificadores o valores abreviados con restricciones (por ejemplo, valores de marco temporal como "24h" o UIDs de Kubernetes), no como expresiones DQL. Esta interpolación directa permite a un llamador salir de literales de cadena, añadir etapas de pipeline DQL arbitrarias y utilizar comentarios de línea // (documentados en la referencia del lenguaje DQL de Dynatrace) para descartar el resto de la consulta original prevista.
El techo de privilegio marginal es bajo porque el token del operador también expone execute_dql con acceso DQL completo. Lo que la inyección otorga es la capacidad de eludir el contrato de las herramientas afectadas: la anotación readOnlyHint: true que los clientes MCP pueden tratar como señal para aprobar automáticamente, la selección de campos por herramienta (por ejemplo, | fields id, name, type), los topes de visualización (maxProblemsToDisplay, maxVulnerabilitiesToDisplay, maxEntitiesToDisplay) y los límites de la ventana temporal.
Las interpolaciones vulnerables identificadas son las siguientes:
| Archivo | Línea | Parámetro | Interpolación |
|---|---|---|---|
src/capabilities/find-monitored-entity-by-name.ts | 23 | entityNames[] | `fetch ${entityType} | search "*${entityNames.join('*" OR "*')}*" | fieldsAdd entity.type | expand tags` |
src/capabilities/find-monitored-entity-by-name.ts | 41 | entityNames[] | `smartscapeNodes "*" | search "*${entityNames.join('*" OR "*')}*" | fields id, name, type` |
src/capabilities/list-problems.ts | 27 | timeframe | `fetch dt.davis.problems, from: now()-${timeframe}, to: now()` |
src/capabilities/list-vulnerabilities.ts | 19 | timeframe | `fetch security.events, from: now()-${timeframe}, to: now()` |
src/capabilities/list-exceptions.ts | 11 | timeframe | `fetch user.events, from: now()-${timeframe}, to: now()` |
src/capabilities/get-events-for-cluster.ts | 20 | timeframe | `fetch events, from: now()-${timeframe}, to: now()` |
src/capabilities/get-events-for-cluster.ts | 27 | clusterId, kubernetesEntityId | `| filter k8s.cluster.uid == "${clusterId}" or dt.entity.kubernetes_cluster == "${kubernetesEntityId}"` |
Todos los esquemas Zod para estos parámetros utilizan z.string() o z.array(z.string()) sin ninguna validación de patrón.
Prueba de concepto
Escape por comilla y comentario en clusterId. Con el valor clusterId = 'x" or 1==1 //', la consulta construida resulta en:
| filter k8s.cluster.uid == "x" or 1==1 //" or dt.entity.kubernetes_cluster == ""
El primer " cierra el literal de cadena, or 1==1 neutraliza el filtro para que coincida con todas las filas, y // descarta el resto de la línea incluyendo la restricción sobre kubernetesEntityId.
Inyección de etapa de pipeline en entityNames. Con el valor entityNames = ['svc" | fields id, name, tags //'], la consulta smartscape construida resulta en:
smartscapeNodes "*" | search "*svc" | fields id, name, tags //*" | fields id, name, type
Tras el comentario de línea //, la consulta efectiva es smartscapeNodes "*" | search "*svc" | fields id, name, tags. La etapa original | fields id, name, type queda suprimida y reemplazada por la selección de campos del atacante. La herramienta devuelve el conjunto de campos que el atacante solicite, incluyendo aquellos no contemplados en el contrato de salida documentado.
Inyección de prefijo en timeframe. Con el valor timeframe = '30d, to: now() | fieldsAdd internal_secret //', la consulta de list-problems resulta en:
fetch dt.davis.problems, from: now()-30d, to: now() | fieldsAdd internal_secret //, to: now()
| filter isNull(dt.davis.is_duplicate) OR not(dt.davis.is_duplicate)
...
Se inyecta una nueva etapa de pipeline antes del | filter previsto por la herramienta, y el resto de la consulta queda comentado.
La propia herramienta verify_dql del servidor puede utilizarse para confirmar que cualquier payload de inyección específico se analiza como DQL válido.
Impacto
Un llamador, típicamente mediante inyección de prompt en un LLM que tenga acceso a las herramientas afectadas, puede eludir el ámbito de campos, la ventana temporal y los topes de visualización definidos por las herramientas.
Las herramientas afectadas están anotadas con readOnlyHint: true, lo que algunos clientes MCP interpretan como señal para aprobar automáticamente su ejecución. La inyección convierte una herramienta de lectura considerada segura en una superficie de ejecución DQL arbitraria.
No se produce acceso a datos nuevos más allá de lo que execute_dql ya proporciona. El impacto marginal reside en la vía de aprobación automática y en el contrato de herramienta roto, no en una escalada de privilegios.
