Tu servicio tiene una API key para tu almacenamiento en la nube. Un usuario envía una petición. Tu servicio escribe un archivo obedientemente. El archivo termina en el bucket del atacante, no en el tuyo, y ahora ellos tienen tus datos. Acabas de convertirte en un cómplice inconsciente.
Esto es un ataque de confused deputy. El atacante no puede escribir en el bucket objetivo por sí mismo. Le falta el permiso. Pero puede engañar a tu servicio, que sí tiene el permiso, para que lo haga por él. Tu servicio es el «deputy». Está «confused» sobre para quién es realmente la action.
Cómo se ve realmente un ataque de Confused Deputy
El ejemplo clásico es un compiler. El compiler se ejecuta con los privilegios de tu usuario, así que puede leer sus archivos fuente y escribir archivos objeto en su directorio home. Un usuario malicioso alimenta al compiler con un archivo fuente que contiene directivas para escribir la salida en /etc/passwd en lugar del directorio de build. El compiler tiene la autoridad. El usuario no. El compiler está confused sobre la intención de quién está ejecutando.
Los servicios web modernos recrean este patrón constantemente. Considera un servicio de subida de archivos que permite a los usuarios proporcionar una callback URL:
# The user provides a URL. We fetch it and store it.
def upload_from_url(user_id: str, source_url: str, dest_path: str):
response = requests.get(source_url, timeout=30)
s3.put_object(Bucket="my-app-bucket", Key=dest_path, Body=response.content)
audit_log.info(f"User {user_id} uploaded to {dest_path}")
Esto parece inofensivo. El usuario proporciona una URL. Tú la descargas. La guardas en tu bucket. ¿Pero qué pasa si source_url es http://169.254.169.254/latest/meta-data/iam/security-credentials/? Ahora estás exfiltrando tus propias credenciales de AWS y escribiéndolas en una ruta que el atacante nombró. Tu rol de IAM tiene la autoridad. El atacante no. Tú eres el confused deputy.
El ataque se generaliza a cualquier sistema donde un componente privilegiado realiza una action en nombre de un solicitante con menos privilegios, y el solicitante puede influir en los parámetros de esa action.
Por qué las comprobaciones de identidad no son suficientes
La solución instintiva es comprobar quién es el solicitante. «¿Este usuario está autenticado?» Sí. «¿Este usuario está autorizado para subir archivos?» Sí. Ambas comprobaciones pasan, y el ataque sigue funcionando.
El problema es que las comprobaciones de autorización verifican si el solicitante puede realizar una action. No verifican si el objetivo de la action es apropiado para ese solicitante. Se permite al usuario subir archivos. No se le permite hacer que tu servicio descargue sus propios metadatos y los almacene bajo su control. El modelo de permisos confunde «puede usar el endpoint de subida» con «puede hacer que el servicio realice acciones privilegiadas arbitrarias».
Este es el modo de fallo principal: el deputy posee una capability (credenciales de AWS, acceso al sistema de archivos, acceso de escritura a la base de datos) y la usa basándose en instrucciones de otra persona. Sin vincular la capability a la intención específica del titular original de la autoridad, la confusión es inevitable.
Seguridad basada en Capabilities: Vincular la Autoridad a la Intención
La seguridad basada en capabilities soluciona esto haciendo que la autoridad sea infalsificable y explícita. En lugar de preguntar «¿quién eres?» y luego buscar qué se te permite hacer, una capability es un token que transmite directamente el derecho a realizar una action específica sobre un recurso específico.
En un sistema de capabilities, el servicio de subida no tendría una S3 write key general. Tendría una capability para escribir en rutas específicas, limitada a usuarios específicos. La petición del usuario incluiría un capability token que nombra el destino exacto. El servicio verificaría la firma criptográfica del token y su scope, no la identidad del usuario contra una tabla global de permisos.
Aquí hay un ejemplo simplificado usando scoped tokens:
import hashlib
import hmac
import secrets
SECRET = secrets.token_bytes(32)
def mint_capability(principal: str, action: str, resource: str) -> str:
"""Create an unforgeable capability token scoped to a specific action and resource."""
payload = f"{principal}:{action}:{resource}"
sig = hmac.new(SECRET, payload.encode(), hashlib.sha256).hexdigest()[:16]
return f"{payload}:{sig}"
def verify_capability(token: str, expected_action: str, expected_resource: str) -> bool:
"""Verify a capability token matches the expected action and resource."""
try:
principal, action, resource, sig = token.rsplit(":", 3)
except ValueError:
return False
payload = f"{principal}:{action}:{resource}"
expected_sig = hmac.new(SECRET, payload.encode(), hashlib.sha256).hexdigest()[:16]
return hmac.compare_digest(sig, expected_sig) and action == expected_action and resource == expected_resource
# The user receives a capability that only allows writing to their own prefix.
user_id = "alice"
scoped_token = mint_capability(user_id, "write", f"uploads/{user_id}/report.pdf")
# Later, the service verifies the capability before acting.
if not verify_capability(scoped_token, "write", "uploads/alice/report.pdf"):
raise PermissionError("Capability does not match requested action or resource")
s3.put_object(Bucket="my-app-bucket", Key="uploads/alice/report.pdf", Body=data)
La idea clave: el capability token scoped_token no puede reutilizarse para escribir en uploads/bob/salary.xlsx. Está vinculado a un recurso específico en el momento de su creación. El servicio no decide qué está permitido basándose en quién pregunta. Decide basándose en lo que el token autoriza explícitamente.
Cuándo los Sistemas de Capabilities son Imprácticos
La mayoría de nosotros no estamos construyendo sistemas operativos con arquitecturas de capabilities puras. Estamos llamando a APIs de AWS, hablando con PostgreSQL y escribiendo HTTP handlers. La seguridad basada en capabilities completa es una filosofía de diseño, no una biblioteca drop-in.
El punto medio práctico es aplicar el pensamiento de capabilities a los límites de tu servicio. En lugar de darle a tu servicio un único rol de IAM con amplios permisos de S3, usa scoped presigned URLs. En lugar de dejar que los usuarios nombren destinos arbitrarios, valida y sanitiza el recurso objetivo contra lo que ese usuario tiene permitido tocar. El objetivo no es un capability OS formal. El objetivo es eliminar la autoridad implícita.
Aquí está el mismo endpoint de subida con scoped validation en lugar de confianza general:
import re
# A regex that constrains what a user can name as a destination.
ALLOWED_DEST_PATTERN = re.compile(r"^uploads/(?P<user_id>[a-z0-9_-]+)/[a-zA-Z0-9_.-]+$")
def upload_from_url(user_id: str, source_url: str, dest_path: str):
match = ALLOWED_DEST_PATTERN.match(dest_path)
if not match or match.group("user_id") != user_id:
raise ValueError("Invalid destination path")
# The source URL is also restricted. No internal IPs, no metadata endpoints.
parsed = urlparse(source_url)
if parsed.hostname in ("169.254.169.254", "localhost", "127.0.0.1"):
raise ValueError("Forbidden source URL")
response = requests.get(source_url, timeout=30)
s3.put_object(Bucket="my-app-bucket", Key=dest_path, Body=response.content)
Esto no es un sistema de capabilities en el sentido formal. Pero aplica el mismo principio: la autoridad del servicio no es un cheque en blanco. Está limitada a un conjunto específico de acciones que el solicitante tiene permitido invocar.
Por qué este Patrón Sigue Apareciendo
Las vulnerabilidades de confused deputy aparecen en flujos de OAuth, funciones serverless, webhook handlers y pipelines de supply-chain. Cada vez que un componente con privilegios elevados procesa input de usuario y actúa sobre él, el riesgo está presente.
El patrón de SSRF-to-metadata-exfiltration es tan común que los proveedores de nube ahora bloquean la IP del IMDS por defecto en las VPCs. Eso es una mitigación para un síntoma, no para la enfermedad. La enfermedad es un componente privilegiado que no puede distinguir su propia intención de la intención del solicitante.
Previniendo Ataques de Confused Deputy en la Práctica
No necesitas reescribir tu arquitectura. Tres hábitos cubren la mayoría de los casos.
Limita el scope de tus credenciales. Si tu servicio habla con S3, dale una política de IAM que solo pueda escribir en un prefijo específico. Si habla con una base de datos, usa row-level security o credenciales separadas por tenant. Cuanto menor sea el blast radius de un confused deputy, menos daño puede causar.
Valida el objetivo, no solo el actor. Las comprobaciones de autorización deberían responder dos preguntas: ¿se permite al solicitante realizar esta action, y está el objetivo de la action dentro del scope del solicitante? Un usuario puede subir archivos. ¿Puede subir archivos al directorio de otro usuario? ¿Puede hacer que tu servicio llame a URLs arbitrarias? La segunda pregunta es donde viven los confused deputies.
Evita la autoridad ambiente. La autoridad ambiente es cuando un proceso tiene permisos simplemente por lo que es, no por lo que está haciendo. Ejecutarse como root. Usar una master API key. Mantener una conexión de superusuario de base de datos. Son convenientes y peligrosos. Divide tus privilegios. Usa credenciales temporales con expiración explícita. Cuanto más granular sea tu autoridad, más difícil será confundirla.
Preguntas Frecuentes
¿Es CSRF un ataque de confused deputy?
Sí, en cierto sentido. El navegador del usuario es el deputy. Posee una session cookie (la capability) y realiza una action que cambia el estado porque un sitio malicioso se lo ordenó. El navegador está confused sobre si la petición representa la intención del usuario. Los CSRF tokens funcionan vinculando la capability (la cookie) a un contexto de action específico, que es exactamente la solución basada en capabilities.
¿Se aplica esto a microservicios?
Absolutamente. Las llamadas entre servicios a menudo usan una shared service account con permisos amplios. Si el Servicio A llama al Servicio B con un parámetro proporcionado por el usuario, y el Servicio B usa su cuenta privilegiada para actuar sobre ese parámetro, el Servicio B es el confused deputy. Usa scoped service tokens o autorización específica por petición.
¿Qué hay de la inyección SQL?
La inyección SQL es un ataque de confused deputy sobre tu base de datos. La base de datos es el deputy. Tiene la autoridad para leer y escribir tablas. El atacante le alimenta con instrucciones disfrazadas de input. Las queries parametrizadas solucionan esto separando la autoridad de la base de datos (la estructura de la query) del input del usuario (los parámetros).
Audita tus Deputies
Revisa tus servicios e identifica los que poseen privilegios que los usuarios no tienen. Para cada uno, pregúntate: ¿podría un usuario engañar a este servicio para que use su autoridad de una manera que beneficie al atacante? Si la respuesta es sí, tienes un confused deputy. La solución rara vez es exótica. Suele ser cuestión de limitar el scope de lo que el servicio está dispuesto a hacer, y rechazar las peticiones que caen fuera de ese scope.
Tu servicio no debería ser un cómplice. Asegúrate de que sepa las órdenes de quién está siguiendo.