Saltar al contenido

SSRF: cómo una petición del servidor acaba leyendo las credenciales de tu nube

    SSRF:

    cómo una petición del servidor acaba leyendo las credenciales de tu nube

    Seguridad de aplicaciones web / Cloud  |  unfantasmaenelsistema.com

    Casi todas las aplicaciones web hacen, en algún momento, una petición de red por su cuenta: descargar la imagen de un avatar a partir de una URL que pega el usuario, comprobar un webhook, renderizar la vista previa de un enlace, importar un feed, convertir un documento remoto a PDF. Desde fuera parece inofensivo: el usuario da una dirección y el servidor va a buscarla. El problema es quién hace esa petición y desde dónde. El servidor está dentro de tu red, tiene acceso a servicios internos que no miran a internet y, si vive en la nube, lleva colgando unas credenciales que valen oro.

    Eso es SSRF (Server-Side Request Forgery, falsificación de petición del lado del servidor): conseguir que el servidor haga una petición que el atacante elige, a un destino que el atacante elige. Entró en el OWASP Top 10 en la edición de 2021 como categoría propia (A10) precisamente porque dejó de ser una curiosidad para convertirse en una de las vías más rentables hacia los datos. El caso más conocido, la brecha de Capital One en 2019, expuso datos de más de 100 millones de personas y empezó exactamente así. En este artículo vemos cómo funciona, por qué el endpoint de metadatos de la nube es un imán, cómo se saltan los filtros ingenuos y cómo se defiende en serio.

    Qué aprenderás en este artículo

    Qué es un SSRF y por qué el servidor es un origen de petición privilegiado · El endpoint de metadatos 169.254.169.254 y la diferencia entre IMDSv1 e IMDSv2 · Otros destinos internos jugosos: Redis, la API de Kubernetes, paneles de administración · Cómo los filtros por lista negra se saltan con redirecciones, DNS rebinding y representaciones raras de IP · Defensa en capas: allowlist de destinos, bloqueo de rangos internos, egress filtering y forzar IMDSv2

    1. Qué es exactamente un SSRF

    Un SSRF aparece siempre que la aplicación toma una entrada del usuario y la usa —entera o en parte— para construir la dirección de una petición de red que hace el propio servidor. La entrada no tiene por qué ser una URL evidente en un campo de texto: puede ser una cabecera, un parámetro que parece un nombre de host, un campo dentro de un XML o un JSON, o el valor de un webhook configurable.

    La gravedad depende de qué puede alcanzar el servidor y de cuánta información devuelve al atacante. Se distinguen dos variantes:

    • SSRF con respuesta (in-band): el contenido de la petición interna se refleja de vuelta al atacante —en el cuerpo de la respuesta, en un mensaje de error, en la vista previa generada—. Es el escenario más directo: se lee lo que devuelva el servicio interno.
    • SSRF ciego (blind): el servidor hace la petición pero no devuelve el contenido. Aún es explotable midiendo tiempos, provocando errores distintos según el destino, o apuntando a servicios que ejecutan una acción con solo recibir la petición (un endpoint que reinicia algo, que encola una tarea, etc.).

    En ambos casos el atacante ha convertido tu servidor en un proxy que habla desde dentro de tu perímetro. Todo lo que el cortafuegos perimetral bloquea desde fuera —la consola de administración, la base de datos, el orquestador— ahora es alcanzable a través de la aplicación.

    2. Por qué el servidor es un origen de petición privilegiado

    Cuando tú visitas una web, tu navegador sale a internet desde una red doméstica o corporativa sin nada especial detrás. Cuando la petición la hace un servidor de aplicación, ese servidor suele estar en una posición de red mucho más sensible:

    • Comparte segmento con bases de datos, colas de mensajes, cachés y APIs internas que no exigen autenticación porque «solo se llega desde dentro».
    • Puede resolver nombres DNS internos y llegar a paneles de administración publicados solo en la red privada.
    • Si corre en un proveedor de nube (AWS, Google Cloud, Azure, Oracle Cloud), tiene acceso a un servicio de metadatos local que entrega configuración de la instancia y, muy a menudo, credenciales temporales del rol asociado a esa máquina.
    • Tiene acceso a la red de contenedores: la API del kubelet, el socket de Docker si está mal expuesto, el servidor de la API de Kubernetes.

    El atacante no necesita entrar en el servidor. Le basta con dictarle a dónde llamar y leer la respuesta.

    3. El endpoint de metadatos: 169.254.169.254

    Todos los grandes proveedores de nube exponen un Instance Metadata Service (IMDS) en la dirección de enlace local 169.254.169.254. Es accesible solo desde la propia máquina, sin autenticación, y sirve para que la instancia se autoconfigure: nombre, región, claves SSH públicas, scripts de arranque (user-data) y, la joya, las credenciales del rol IAM que tenga adjuntado.

    Esas credenciales son un juego de clave de acceso, clave secreta y token de sesión con la vida corta (minutos u horas) que el SDK de la nube renueva solo. Con ellas, un atacante hereda todos los permisos del rol de la instancia: si ese rol puede leer buckets de almacenamiento, el atacante lee buckets; si puede lanzar máquinas o leer secretos, también. Aquí es donde un SSRF de «descárgame esta imagen» se transforma en una brecha de datos a gran escala.

    3.1 IMDSv1 frente a IMDSv2

    La versión original del servicio (IMDSv1) responde a una simple petición GET. Eso la hace trivial de alcanzar por SSRF: si el atacante logra que el servidor haga un GET a una URL que él controla, puede hacer que lo haga contra el endpoint de metadatos. La versión endurecida (IMDSv2) obliga a un flujo en dos pasos y añade defensas pensadas específicamente contra SSRF.

    AspectoIMDSv1IMDSv2
    Cómo se pideUn solo GET a la URL de metadatosPrimero un PUT para obtener un token de sesión, luego GET con ese token en una cabecera
    Resistencia a SSRFBaja: casi cualquier SSRF de GET la alcanzaAlta: la mayoría de SSRF no pueden emitir un PUT ni añadir cabeceras arbitrarias
    Límite de saltos de red (hop limit)No aplicaEl paquete de respuesta tiene TTL configurable (por defecto 1 o 2): no puede salir de la instancia ni llegar a un contenedor a través de un puente
    Cabecera de origenNo se compruebaSe rechazan peticiones que traen cabeceras típicas de proxy (X-Forwarded-For), señal de que vienen reenviadas
    Estado recomendadoDeshabilitadaObligatoria (http-tokens: required)

    Forzar IMDSv2 no elimina el riesgo por completo —un SSRF muy potente que permita controlar el método y las cabeceras aún podría llegar—, pero corta de raíz la inmensa mayoría de exploits reales, que se basan en un GET simple. Es la mitigación con mejor relación coste/efecto de toda esta lista.

    4. Más allá de la nube: otros destinos internos

    El endpoint de metadatos es el objetivo estrella, pero un SSRF sirve para hablar con cualquier cosa que el servidor pueda alcanzar. Algunos destinos habituales:

    Destino internoQué se buscaPor qué es alcanzable
    Servicio de metadatos (169.254.169.254)Credenciales del rol, user-data, clavesSin autenticación, pensado para acceso local
    Redis / Memcached (6379 / 11211)Leer o escribir datos de sesión y caché; en Redis, a veces ejecución de comandosProtocolo de texto sin autenticación por defecto, tolerante a basura al principio de la conexión
    API de Kubernetes / kubelet (6443 / 10250)Enumerar pods y secretos, ejecutar en contenedoresPublicada en la red del clúster; el kubelet histórico permitía acceso anónimo
    Bases de datos internasVolcado de datos, a veces vía funciones de la propia baseEn la misma VPC/segmento, filtradas solo por red
    Paneles de administración internosJenkins, consolas de CI/CD, dashboards sin login desde la red internaPublicados «solo en la intranet» y sin autenticación fuerte
    El propio localhost del servidorEndpoints de gestión (actuator, /debug, métricas) que exponen configuración y secretosEscuchan en 127.0.0.1 confiando en que nadie de fuera llega

    Un detalle importante: muchos de estos servicios hablan protocolos que no son HTTP, pero SSRF con esquemas como gopher://, dict:// o file:// (si la biblioteca cliente los admite) permite enviar bytes casi arbitrarios a un puerto, lo que abre la puerta a interactuar con Redis o SMTP. Restringir los esquemas permitidos a http y https es parte de la defensa.

    5. El caso Capital One (2019)

    Es el ejemplo canónico y merece contarse porque encadena todos los eslabones de este artículo. Un WAF mal configurado (un proxy inverso de la propia infraestructura) era vulnerable a SSRF. A través de él se alcanzó el endpoint de metadatos 169.254.169.254 —IMDSv1— y se obtuvieron las credenciales temporales del rol asignado a esa máquina. Ese rol tenía permiso para listar y leer buckets de almacenamiento. Con las credenciales en la mano, se enumeraron los buckets y se descargaron unos 100 GB de datos con información de más de 100 millones de solicitantes de tarjetas.

    Las lecciones se leen hacia atrás en la cadena: el rol de la instancia tenía más permisos de los necesarios (violación de mínimo privilegio); el servicio de metadatos era la versión insegura; y no había egress filtering que impidiera a esa máquina hablar con 169.254.169.254 o sacar gigabytes hacia fuera sin que saltara nada. Cualquiera de esas tres barreras habría contenido el incidente.

    6. Por qué las listas negras no funcionan

    La defensa ingenua es «si la URL apunta a 127.0.0.1 o a 169.254.169.254, la rechazo». No sirve, porque hay demasiadas formas de escribir la misma dirección y demasiados momentos en los que comprobarla. Las técnicas clásicas de evasión:

    TécnicaIdeaEjemplo ilustrativo
    Representaciones alternativas de IPLa misma dirección escrita en decimal, octal, hexadecimal o con ceros2130706433, 0177.0.0.1, 0x7f.0.0.1 equivalen a 127.0.0.1
    Direcciones que «parecen» externasDominios públicos que resuelven a IP internas, o el rango 169.254.x que no siempre se filtraUn nombre propio apuntando a 169.254.169.254
    Redirecciones abiertasLa URL inicial es externa y legítima; el servidor la sigue y termina en un 302 hacia el destino internohttps://sitio-legitimo/redirect?to=http://169.254.169.254/…
    DNS rebinding (TOCTOU)El nombre resuelve a una IP pública cuando se valida y a una interna un instante después, cuando se conectaRegistro DNS con TTL 0 que alterna entre dos IP
    Confusión de parser de URLValidador y cliente HTTP interpretan de forma distinta credenciales, arrobas o fragmentos en la URLhttp://esperado@169.254.169.254/ o http://169.254.169.254#.esperado/
    IPv6 y direcciones mapeadasNotación IPv6 de loopback o de IPv4 embebida en IPv6[::1], [::ffff:169.254.169.254]

    El patrón común es que validar una cadena de texto no es lo mismo que controlar a qué IP se conecta de verdad el socket. La defensa robusta actúa en el momento de la conexión, sobre la IP ya resuelta, y vuelve a comprobarla tras cada redirección.

    7. Defensa en capas

    Ningún control aislado cierra el SSRF. La combinación sí. De la aplicación hacia la infraestructura:

    CapaControlQué aporta
    AplicaciónAllowlist de destinos: solo dominios/hosts concretos que la función necesita de verdadConvierte el problema en «solo puede ir a donde le decimos», que es defendible
    AplicaciónValidar sobre la IP resuelta, no sobre la cadena; rechazar rangos privados, loopback, link-local (169.254.0.0/16), multicast y metadatosNeutraliza las representaciones raras de IP y los dominios que resuelven a interno
    AplicaciónProhibir esquemas distintos de http/https; no seguir redirecciones automáticamente, o revalidar el destino en cada saltoCierra gopher/dict/file y las redirecciones abiertas
    AplicaciónCliente HTTP dedicado para peticiones «salientes por usuario», con timeouts cortos, sin credenciales y sin cookies del dominioReduce el valor de una petición forzada aunque llegue a destino
    NubeForzar IMDSv2 (http-tokens: required) y hop limit 1; desactivar el servicio de metadatos donde no haga faltaElimina el exploit de GET simple contra 169.254.169.254
    Nube / IAMMínimo privilegio real en el rol de la instancia; nada de comodinesAunque se filtren las credenciales, el alcance es pequeño
    RedEgress filtering: la máquina de aplicación solo sale a los destinos que necesita; bloquear 169.254.169.254 desde procesos que no sean el agente de la nubeContiene tanto la llegada al metadata como la exfiltración posterior
    DetecciónAlertar sobre peticiones a rangos internos desde la app, uso de credenciales de rol desde IP ajenas a la instancia, picos de tráfico salienteDetecta el intento y el uso posterior de las credenciales

    Sobre esa última fila: los proveedores de nube registran de dónde se usa cada credencial. Una credencial de rol de instancia que de pronto se utiliza desde una IP que no es la de esa instancia es una señal muy fuerte de robo, y se puede alertar sobre ella.

    7.1 Comprobar tu propia configuración

    Comandos de lectura y auditoría sobre infraestructura propia, para verificar el estado de IMDS y cómo responde una función que hace peticiones salientes. No son pasos de intrusión: se ejecutan contra tus propias instancias.

    # --- Ver el modo de metadatos de tus instancias EC2 (solo lectura) ---
    aws ec2 describe-instances \
      --query 'Reservations[].Instances[].[InstanceId,MetadataOptions.HttpTokens,MetadataOptions.HttpPutResponseHopLimit]' \
      --output table
     
    # --- Forzar IMDSv2 y hop limit 1 en una instancia propia ---
    aws ec2 modify-instance-metadata-options \
      --instance-id i-0123456789abcdef0 \
      --http-tokens required \
      --http-put-response-hop-limit 1 \
      --http-endpoint enabled
     
    # --- Desde DENTRO de tu instancia: con IMDSv2 un GET pelado ya no basta ---
    curl -s http://169.254.169.254/latest/meta-data/            # -> 401 si v2 es obligatorio
    TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
      -H "X-aws-ec2-metadata-token-ttl-seconds: 60")
    curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
      http://169.254.169.254/latest/meta-data/

    Y un esqueleto defensivo del lado de la aplicación: resolver el host, comprobar que la IP no cae en un rango prohibido y solo entonces conectar. En producción conviene apoyarse en una biblioteca mantenida específica de anti-SSRF en lugar de mantener esta lógica a mano.

    import ipaddress, socket
    from urllib.parse import urlparse
     
    BLOQUEADOS = [
        ipaddress.ip_network('127.0.0.0/8'),   ipaddress.ip_network('10.0.0.0/8'),
        ipaddress.ip_network('172.16.0.0/12'), ipaddress.ip_network('192.168.0.0/16'),
        ipaddress.ip_network('169.254.0.0/16'),           # link-local + metadatos
        ipaddress.ip_network('::1/128'),       ipaddress.ip_network('fc00::/7'),
    ]
    ESQUEMAS_OK = {'http', 'https'}
     
    def destino_permitido(url: str) -> bool:
        p = urlparse(url)
        if p.scheme not in ESQUEMAS_OK or not p.hostname:
            return False
        # resolver TODAS las IP del host y rechazar si alguna es interna
        for fam, _, _, _, sockaddr in socket.getaddrinfo(p.hostname, p.port or 80):
            ip = ipaddress.ip_address(sockaddr[0])
            if any(ip in red for red in BLOQUEADOS) or not ip.is_global:
                return False
        return True
     
    # Nota: para cerrar el DNS rebinding hay que CONECTAR a la IP ya validada
    # (no volver a resolver el nombre) y revalidar tras cada redireccion.

    Marco legal: pruébalo solo contra lo que es tuyo

    Provocar peticiones SSRF, alcanzar servicios de metadatos o servicios internos y usar credenciales obtenidas así contra sistemas de terceros es delito en España: acceso ilícito a sistemas de información (art. 197 bis del Código Penal), facilitación de herramientas para ello (art. 197 ter) y daños informáticos (art. 264) · Las pruebas se hacen sobre infraestructura propia o con autorización escrita y por escrito del titular (un contrato de pentest con alcance definido) · Para practicar: aplicaciones deliberadamente vulnerables en un laboratorio aislado (OWASP Juice Shop, WebGoat, contenedores propios en el rango 192.168.56.x de la serie), nunca servicios reales ni el metadata de una cuenta de nube que no sea tuya

    Al revisar tu propio código, busca estos patrones

    Cualquier función que reciba una URL o un host del usuario y llame a requests / fetch / curl / http.Client con ese valor · Vistas previa de enlaces, importadores de imágenes/feeds, conversores de documentos, comprobadores de webhooks, integraciones con «pon aquí la URL de tu servidor» · Analizadores de XML con entidades externas habilitadas (XXE), que son una vía de SSRF por sí mismos · Clientes HTTP que siguen redirecciones por defecto sin revalidar el destino

    Conclusión

    El SSRF es un fallo de confianza mal colocada: la aplicación asume que una petición «la hace el servidor, así que es de fiar», cuando en realidad el destino lo elige quien envía la entrada. Y el servidor está en el peor sitio posible para que eso ocurra —dentro del perímetro, con acceso a servicios sin autenticar y, en la nube, con credenciales colgando de un endpoint sin contraseña en 169.254.169.254—.

    La buena noticia es que se defiende bien y con controles baratos: una allowlist de destinos, validación sobre la IP resuelta y no sobre el texto, forzar IMDSv2, aplicar mínimo privilegio al rol de la instancia y filtrar el tráfico saliente. Ninguno es difícil; el problema histórico ha sido no aplicar ninguno. El caso Capital One es la demostración de lo que cuesta esa omisión.

    Referencias y recursos

    OWASP Top 10:2021 A10 Server-Side Request Forgery — owasp.org · OWASP SSRF Prevention Cheat Sheet — cheatsheetseries.owasp.org · AWS: «Add defense in depth against open firewalls, reverse proxies, and SSRF» y la guía de transición a IMDSv2 · PortSwigger Web Security Academy — laboratorios gratuitos de SSRF para practicar en su entorno · Nuestros artículos de OWASP Top 10:2025, seguridad de APIs REST y el próximo sobre AWS IAM

    unfantasmaenelsistema.com | Seguridad, sistemas y todo lo que no debería funcionar (pero funciona)

    Etiquetas:
    Resumen de privacidad

    Esta web utiliza cookies para que podamos ofrecerte la mejor experiencia de usuario posible. La información de las cookies se almacena en tu navegador y realiza funciones tales como reconocerte cuando vuelves a nuestra web o ayudar a nuestro equipo a comprender qué secciones de la web encuentras más interesantes y útiles.