Resumen
La herramienta fetch-apify-docs valida URLs contra una lista de dominios permitidos usando String.startsWith() en lugar de una comparación adecuada del hostname de la URL. Esto permite el bypass mediante subdominios controlados por atacantes (por ejemplo, https://docs.apify.com.evil.com/), habilitando a la herramienta para obtener y retornar contenido web arbitrario al LLM.
Detalles
Componente vulnerable
src/tools/common/fetch_apify_docs.ts, línea 51:
const isAllowedDomain = ALLOWED_DOC_DOMAINS.some((domain) => url.startsWith(domain));
src/const.ts, líneas 167-170:
export const ALLOWED_DOC_DOMAINS = [
'https://docs.apify.com',
'https://crawlee.dev',
] as const;
Cómo funciona el bypass
String.startsWith('https://docs.apify.com') coincide con cualquier cadena que comience con ese prefijo, incluyendo:
https://docs.apify.com.evil.com/payload- subdominio controlado por atacantehttps://docs.apify.com@evil.com/payload- componente userinfo en URL (el comportamiento del navegador varía, perofetch()en Node.js puede seguir esto)https://docs.apify.com.evil.com:8080/path- puerto personalizado en dominio del atacante
Todos estos pasan la verificación startsWith porque comienzan con la cadena exacta https://docs.apify.com.
El contenido obtenido se retorna al LLM
Después de que la verificación de la lista de permitidos pasa, la herramienta obtiene la URL y retorna el contenido completo de la página como markdown (fetch_apify_docs.ts:69-103):
const response = await fetch(url);
// ...
const html = await response.text();
markdown = htmlToMarkdown(html);
// ...
return buildMCPResponse({ texts: [`Fetched content from ${url}:\n\n${markdown}`], ... });
El HTML se convierte a markdown y se retorna textualmente al LLM. Esto crea un vector de inyección de prompts: la página del atacante puede contener instrucciones que el LLM puede seguir.
Mientras que herramientas como get-html-skeleton no tienen lista de dominios permitidos en absoluto (acepta cualquier URL), la herramienta fetch-apify-docs claramente estaba destinada a ser más restrictiva (solo documentación), pero la verificación startsWith derrota esa intención.
Prueba de concepto
{
"method": "tools/call",
"params": {
"name": "fetch-apify-docs",
"arguments": {
"url": "https://docs.apify.com.evil.com/prompt-injection-payload"
}
}
}
La URL pasa la verificación startsWith('https://docs.apify.com'), obtiene la página del atacante y retorna su contenido al LLM.
Impacto
- Inyección de prompts via contenido obtenido: El atacante hospeda una página en
docs.apify.com.evil.comconteniendo instrucciones para LLM. Cuando la herramienta obtiene y retorna este contenido, el LLM puede seguir las instrucciones inyectadas. - Violación de límites de seguridad: La lista de permitidos fue explícitamente diseñada para restringir la obtención a dominios de documentación confiables. El bypass derrota esta intención.
- SSRF (limitado): La herramienta puede obtener contenido de servidores controlados por atacantes, aunque el riesgo principal es el contenido retornado al LLM en lugar del acceso de red.
- Compromiso de cuenta via _meta.apifyToken: Las instrucciones de prompt inyectadas pueden dirigir al LLM a incluir un
_meta.apifyTokenespecífico (la característica de token por solicitud del servidor) en invocaciones subsecuentes decall-actor, redirigiendo operaciones facturables a la cuenta de una víctima o accediendo a sus Actors privados.
