Resumen
El servidor MCP (Model Context Protocol) de PraisonAI (praisonai mcp serve) registra por defecto cuatro herramientas de manejo de archivos: praisonai.rules.create, praisonai.rules.show, praisonai.rules.delete, y praisonai.workflow.show. Cada una acepta una cadena de ruta o nombre de archivo desde argumentos MCP tools/call y la une a ~/.praison/rules/ (o para workflow.show, acepta una ruta absoluta) sin verificación de contención. El despachador JSON-RPC pasa params["arguments"] directamente a cada manejador via **kwargs sin validar contra el esquema de entrada anunciado.
Detalles Técnicos
Dispatcher Acepta kwargs No Validados
El código en src/praisonai/praisonai/mcp_server/server.py:281-298 muestra que el despachador no aplica validación de esquema:
async def _handle_tools_call(self, params: Dict[str, Any]) -> Dict[str, Any]:
tool_name = params.get("name")
arguments = params.get("arguments", {})
tool = self._tool_registry.get(tool_name)
if tool is None:
raise ValueError(f"Tool not found: {tool_name}")
# Execute tool
if asyncio.iscoroutinefunction(tool.handler):
result = await tool.handler(**arguments) # ← sin validación de esquema
else:
result = tool.handler(**arguments)
Manejadores Sin Contención
Los cuatro manejadores registrados carecen de verificaciones de contención:
@register_tool("praisonai.rules.create")
def rules_create(rule_name: str, content: str) -> str:
rules_dir = os.path.expanduser("~/.praison/rules")
os.makedirs(rules_dir, exist_ok=True)
rule_path = os.path.join(rules_dir, rule_name) # ← sin validación realpath/contención
with open(rule_path, 'w') as f:
f.write(content)
return f"Rule created: {rule_name}"
Al establecer rule_name="../../<alguna-ruta>", un atacante puede salir del directorio de reglas y escribir cualquier archivo que el usuario pueda escribir.
Escalada a RCE via Archivos .pth de Python
CPython's Lib/site.py importa líneas que comienzan con import desde cada archivo .pth presente en site.getsitepackages() y site.getusersitepackages() en cada inicio de intérprete. El directorio site-packages del usuario siempre es escribible sin elevación. Un solo archivo .pth conteniendo import os; os.system("...") convierte la primitiva de escritura por path traversal en RCE en el próximo intérprete Python que inicie el usuario.
Transporte HTTP-stream Sin Autenticación por Defecto
La verificación de autenticación en mcp_server/transports/http_stream.py:191-198 está envuelta en if self.api_key: donde None omite todo el bloque. Configuración por defecto: praisonai mcp serve --transport http-stream vincula 127.0.0.1:8080/mcp sin autenticación.
Vectores de Ataque
Vector 1: LLM Conectado por MCP
Un LLM conectado por MCP (Claude Desktop, Cursor, Continue.dev, Claude Code) cuyo contexto está envenenado por contenido web controlado por atacante, documentos o emails. No se requiere clic del operador más allá del uso ordinario de "pide al LLM que resuma esta página".
Vector 2: HTTP-stream Local
praisonai mcp serve --transport http-stream sin --api-key (por defecto), alcanzable desde cualquier proceso local, pestaña de navegador con DNS-rebound, o vecino de contenedor compartiendo loopback.
Vector 3: Stdio MCP
Desde cualquier vector de inyección de prompt que alcance el LLM conectado.
Impacto
- Ejecución arbitraria de código en la máquina del usuario, con privilegios del usuario, en cualquier proceso Python subsecuente que inicien
- Lectura arbitraria de archivos de cualquier archivo que el usuario pueda leer, incluyendo
~/.ssh/,~/.aws/credentials,~/.config/praisonai/*.yaml - Escritura arbitraria de archivos en cualquier lugar donde el usuario pueda escribir para establecer persistencia
- Eliminación arbitraria de archivos para cadenas destructivas estilo ransomware
- Exfiltración de credenciales MCP: leer configuración del cliente MCP del usuario que lista todos los otros servidores MCP conectados con sus API keys/tokens OAuth/credenciales
- Exfiltración de credenciales de proveedor LLM: leer claves API de OpenAI/Anthropic/Google desde archivos de entorno
La configuración por defecto de praisonai mcp serve registra las cuatro herramientas vulnerables incondicionalmente; no se requiere mala configuración del operador. La entrega por inyección de prompt indirecta via contenido web/documentos/emails convierte esto en RCE de red para cualquier usuario con un LLM conectado por MCP y el servidor MCP praisonai instalado.
Mitigación Sugerida
-
Contención en cada manejador cli_tools: Reemplazar
os.path.joinsimple con validación explícita de prefijo usandopathlib.Path.resolve()y verificación de que la ruta resuelta permanece dentro del directorio designado -
Aplicación de esquema en el despachador: Validar
params["arguments"]contratool.input_schemausando un validador JSON-Schema antes detool.handler(**arguments) -
Reducir superficie de herramientas por defecto: Mover
rules.*yworkflow.showdetrás de un opt-in explícito--enable-fs-tools -
Requerir autenticación en enlaces HTTP-stream no-loopback:
praisonai mcp serve --transport http-streamdebería rechazar iniciar conhost != 127.0.0.1si--api-keyno está establecido
