Reports
BAJA[SSRF]#3717354

Burp MCP Server: una coma en el nombre de host convierte un clic de aprobación en varias reglas automáticas

La extensión MCP de Burp Suite guardaba el host aprobado sin validarlo. Un host con comas, aprobado con un clic, se dividía en varias entradas de la lista blanca y permitía peticiones a localhost o redes internas sin volver a preguntar.

Resumen
Resumen en castellano de un reporte público, no una traducción literal. El código y los comandos se mantienen como en el original.

Resumen

La extensión Burp Suite MCP Server (BApp v1.2.1) permite que un cliente MCP —normalmente un asistente de IA como Claude Desktop, Cursor o Cline— envíe peticiones HTTP a través de Burp. Por defecto, cada petición a un host nuevo abre un diálogo de aprobación con la opción Always Allow Host, que añade ese host a una lista de destinos aprobados automáticamente para no volver a preguntar.

El problema está en cómo se guarda y se vuelve a leer esa lista. La función addAutoApproveTarget (McpConfig.kt:52) no valida el nombre de host recibido: solo comprueba que no esté vacío ni repetido. La lista se almacena como una única cadena en la que los destinos se unen con comas, y al leerla se vuelve a dividir por comas. Como el nombre de host llega sin filtrar desde el parámetro targetHostname de la llamada al cliente MCP, un atacante puede enviar un host que contenga comas: el usuario ve y aprueba un destino, pero el sistema acaba guardando varias entradas independientes.

El investigador lo enmarca en el modelo de amenaza propio de MCP: un asistente de IA que procesa contenido controlado por un atacante (una página web, un documento, un ticket) puede ser inducido a construir la llamada maliciosa. Según el reporte, esto reintroduce una debilidad que ya se había corregido en el informe previo #3176157.

Pasos de reproducción

Probado contra Burp Suite MCP Server BApp v1.2.1 en Kali Linux.

Condiciones previas:

  1. Extensión Burp MCP Server BApp v1.2.1 instalada y activada (Burp → pestaña MCP → Enabled).
  2. Opción "Require approval for HTTP requests" marcada (estado por defecto en una instalación nueva).
  3. Lista "Auto-Approved HTTP Targets" vacía (Burp → pestaña MCP → "Clear All").

Servicio local de prueba para demostrar la lectura de ficheros:

mkdir -p /tmp/internal && echo "INTERNAL_API_KEY=[REDACTADO]" > /tmp/internal/secret.txt
cd /tmp/internal && python3 -m http.server 8123 &

Paso 1 — Envenenar la lista de aprobación con una sola llamada. El cliente MCP hace una llamada a la herramienta send_http1_request con un targetHostname que contiene comas:

await s.call_tool("send_http1_request", {
    "content": "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n",
    "targetHostname": "example.com,127.0.0.1,*.attacker.com,169.254.169.254",
    "targetPort": 443,
    "usesHttps": True,
})

El diálogo de aprobación de Burp aparece mostrando todo el valor en una sola línea, empezando por el dominio legítimo:

An MCP client is requesting to send an HTTP request to:
Target: example.com,127.0.0.1,*.attacker.com,169.254.169.254:443

El usuario, que lee el dominio legítimo al principio, pulsa Always Allow Host.

Paso 2 — Comprobar el envenenamiento (lo aprobado ≠ lo persistido). La lista "Auto-Approved HTTP Targets" contiene ahora cuatro entradas independientes:

  • example.com
  • 127.0.0.1
  • *.attacker.com
  • 169.254.169.254

El usuario autorizó una cadena de destino; el sistema guardó cuatro. Ese es el salto del consentimiento.

Paso 3 — Peticiones sin más aprobaciones. A partir de ahí, el cliente MCP puede dirigir peticiones a esos destinos sin que vuelva a aparecer el diálogo: por ejemplo, leer http://127.0.0.1:8123/secret.txt, alcanzar cualquier subdominio que encaje con *.attacker.com, o sondear puertos de 127.0.0.1 (en la prueba: 22, 80, 443, 3306, 5432, 6379, 8080, 9200, 27017).

Causa raíz

El código que guarda el destino solo filtra cadenas vacías y duplicadas, sin validar el host (McpConfig.kt, líneas 52-60):

fun addAutoApproveTarget(target: String): Boolean {
    val currentTargets = getAutoApproveTargetsList()
    if (target.trim().isNotEmpty() && !currentTargets.contains(target.trim())) {
        val newTargets = currentTargets + target.trim()
        autoApproveTargets = newTargets.joinToString(",")
        return true
    }
    return false
}

El lector divide por la misma coma con la que se escribe, de modo que un único valor "a,b,c" se convierte en tres entradas (McpConfig.kt, líneas 73-79):

fun getAutoApproveTargetsList(): List<String> {
    return if (_autoApproveTargets.isBlank()) emptyList()
    else _autoApproveTargets.split(",").map { it.trim() }.filter { it.isNotEmpty() }
}

El nombre de host llega sin filtrar desde el parámetro JSON-RPC targetHostname hasta la capa de persistencia (HttpRequestSecurity.kt, líneas 44 y 49):

1 -> { config.addAutoApproveTarget(hostname); continuation.resume(true) }
2 -> { config.addAutoApproveTarget("$hostname:$port"); continuation.resume(true) }

Según el reporte, existe una función TargetValidation.isValidTarget en el propio código, pero nunca se llama en este punto. Además, el diálogo (Dialogs.kt) muestra el host tal cual en una sola línea, sin separar las comas, lo que hace que el usuario autorice algo distinto de lo que se guarda. Es esta discrepancia entre lo mostrado y lo persistido la que justifica la clasificación como CWE-451.

Impacto

Con la lista envenenada, el cliente MCP puede, sin nuevas aprobaciones:

  • Leer ficheros locales servidos en localhost (en la prueba, el contenido de /tmp/internal/secret.txt vía 127.0.0.1:8123).
  • Identificar y sondear servicios en localhost y puertos internos.
  • Alcanzar subdominios arbitrarios que encajen con una entrada comodín (*.attacker.com).
  • Llegar al endpoint de metadatos de la nube (169.254.169.254), relevante en instalaciones de Burp alojadas en la nube.

El investigador señala además que, tras un consentimiento adicional sobre otra herramienta (get_proxy_http_history), se podía extraer el historial de peticiones y respuestas de la navegación previa, lo que en sesiones autenticadas incluía credenciales y tokens. Según la política del programa, la lectura de ficheros locales y la extracción de datos entre dominios desde el sitemap de Burp se valoran como severidad media; el informe se cerró como Resolved.