// Engineering Log

HTTP, HTTPS y TLS: Parte 12 — Ataques a nivel HTTP: smuggling, suplantación del Host, CSRF, XSS, SSRF y envenenamiento de caché

Publicado el 21.10.2026

// Ruta rapida

Este articulo pertenece al tema Servidores e infraestructura.

HTTPS protege los datos en tránsito, pero no sabe nada sobre lo que hay dentro de la petición. La mayor parte de los ataques a sitios se realiza sobre conexiones completamente cifradas y aprovecha las peculiaridades del propio HTTP: cómo los proxies y la aplicación analizan de forma diferente una misma petición, cómo el navegador adjunta automáticamente cookies, cómo la aplicación confía en cabeceras. En esta parte se describen las clases principales de esos ataques — cómo funcionan y qué configurar para protegerse. Debes comprobar lo descrito solo en tus sistemas o con permiso escrito del propietario.

Request smuggling: desincronización entre proxy y aplicación

Casi siempre hay un proxy delante de la aplicación — Nginx, HAProxy, CDN. Si el proxy y la aplicación determinan de forma distinta dónde termina una petición y empieza la siguiente, un atacante puede “colar” dentro de su petición el comienzo de otra.

En HTTP/1.1 la longitud del cuerpo la definen dos cabeceras: Content-Length y Transfer-Encoding: chunked. El estándar exige que si están ambas se use Transfer-Encoding, pero las implementaciones fallan:

POST / HTTP/1.1
Host: example.ru
Content-Length: 13
Transfer-Encoding: chunked

0

SMUGGLED

Un proxy que mira Content-Length pasa todo el cuerpo a la aplicación. La aplicación, que mira Transfer-Encoding, ve el fin de la petición en 0, y SMUGGLED se considera el comienzo de la siguiente petición en la misma conexión. Esa “siguiente” petición se concatena con la petición de otro usuario que llegó por la misma conexión entre el proxy y la aplicación. Resultado — respuesta ajena, robo de cookies, evasión de reglas de acceso en el proxy.

Variaciones: Transfer-Encoding: xchunked o con un espacio antes de los dos puntos, que una parte ignora y otra no; discrepancias al traducir HTTP/2 a HTTP/1.1 en el proxy (H2.CL, H2.TE); Upgrade: h2c, que el proxy deja pasar a la aplicación.

Protección:

  • mantén el proxy y los servidores de aplicación actualizados — estos errores se corrigen en cada versión;
  • el proxy debe rechazar peticiones ambiguas con ambas cabeceras o al menos eliminar Content-Length y no reenviarlo; comprueba cómo se comporta tu conjunto, no solo el proxy;
  • entre el proxy y la aplicación usa HTTP/2 cuando sea posible — en él la longitud se define por frames y no hay ambigüedades;
  • no pases a la aplicación la cabecera Upgrade: h2c.

Falsificación de la cabecera Host

Host lo proporciona el cliente, y la aplicación que construye enlaces a partir de él confía en esa entrada del usuario. Un ejemplo clásico — restablecer la contraseña: la aplicación envía un correo con el enlace https://{Host}/reset?token=…. El atacante solicita el restablecimiento de la víctima con Host: attacker.example, la víctima recibe un correo real con un enlace a un sitio ajeno, entra — y el token de restablecimiento llega al atacante.

Protección:

  • en Nginx configura default_server, que responda a nombres desconocidos con el código 444 (cerrar la conexión), y pasa a la aplicación solo peticiones con server_name conocidos;
  • en la aplicación define nombres permitidos (en Django — ALLOWED_HOSTS) y construye enlaces absolutos desde la configuración, no desde la cabecera;
  • lo mismo aplica a X-Forwarded-Host: no lo aceptes de clientes.
nginx
server {
    listen 80 default_server;
    listen 443 ssl default_server;
    ssl_reject_handshake on;
    return 444;
}

Envenenamiento de caché y engaño de caché

Envenenamiento de caché (cache poisoning). Un CDN o proxy cachea una respuesta por clave — normalmente esquema, host y ruta. Si la respuesta depende de una cabecera que no entra en la clave (por ejemplo, X-Forwarded-Host se usa en las URLs de los scripts de la página), un atacante envía una petición con la cabecera falsificada, el caché guarda la página corrupta y la sirve a todos los visitantes.

Engaño de caché (cache deception). La situación inversa: el caché decide que /account/profile.css es un fichero estático por la extensión. La aplicación ignora el “sufijo” y sirve la página personal del usuario. Un atacante envía un enlace a la víctima, su página personal entra al caché compartido, y el atacante la lee desde ahí.

Protección:

  • en páginas personales usa Cache-Control: private o no-store; el CDN debe respetar las cabeceras de la aplicación y no cachear solo por extensión;
  • si la respuesta depende de una cabecera, añádela a Vary o a la clave del caché;
  • la aplicación debe responder 404 a rutas inexistentes como /account/profile.css, y no devolver la página /account/profile.

CSRF: petición en nombre del usuario

El navegador adjunta automáticamente las cookies del sitio a las peticiones, desde donde sea que provengan. Si el usuario ha iniciado sesión en bank.example.ru y luego abre una página de un atacante con un formulario que se envía automáticamente a https://bank.example.ru/transfer, la petición irá con la cookie de sesión del usuario. Esto es falsificación de petición entre sitios (CSRF).

Protección:

  • SameSite en las cookies. Con SameSite=Lax (valor por defecto en los navegadores modernos) las cookies no se envían en peticiones POST desde otros sitios. No pongas SameSite=None sin necesidad;
  • token CSRF — un valor aleatorio en el formulario que el servidor compara con la sesión. Está presente en todos los frameworks populares;
  • comprobación de Origin en peticiones que modifican estado: debe coincidir con tu sitio;
  • no cambies datos por GET: SameSite=Lax permite transiciones por enlaces, y un GET que cambie datos funcionaría.

CORS no protege contra CSRF: impide que la página lea la respuesta, pero la petición del formulario sale y se ejecuta.

XSS y Content-Security-Policy

Cross-site scripting (XSS) — inyección de JavaScript ajeno en una página de tu sitio: vía un comentario, nombre de usuario, parámetro de búsqueda mostrado sin escape. El script se ejecuta a nombre del sitio: lee la página, envía peticiones con las cookies del usuario, modifica formularios.

La protección es en varias capas:

  • escapar toda la entrada de usuario al mostrarla; los motores de plantillas lo hacen por defecto, peligrosos son los lugares donde el escape está desactivado (|safe, v-html, dangerouslySetInnerHTML, innerHTML);
  • cookie de sesión con HttpOnly — el script no podrá leerla;
  • Content-Security-Policy — aunque se inyecte un script, el navegador no lo ejecutará si la fuente no está permitida:
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-r4nd0m'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'

Los scripts inline se permiten solo con un valor único nonce, que el servidor genera por cada respuesta y coloca en <script nonce="r4nd0m">. 'unsafe-inline' en script-src prácticamente anula la protección contra XSS. Implementa CSP primero con Content-Security-Policy-Report-Only, recopilando informes de violaciones, y activa el bloqueo cuando dejen de llegar informes.

SSRF: peticiones a nombre del servidor

Si la aplicación descarga algo según una URL proporcionada por el usuario — una imagen para la vista previa, un webhook, una importación por enlace — un atacante puede indicar una dirección interna: http://127.0.0.1:8080/admin, http://10.0.0.5:9200/, la dirección de metadatos de la nube http://169.254.169.254/. La petición saldrá desde dentro de tu red, donde puede no haber firewall y a menudo no hay autenticación. Mediante los metadatos de la nube se pueden obtener claves temporales de acceso a la cuenta en la nube.

Protección:

  • verifica la dirección después de resolver el nombre: prohíbe rangos privados, locales y reservados (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, ::1, fc00::/7);
  • verifica también cada redirección: una URL externa puede redirigir a una interna;
  • protégete contra cambio de DNS entre la verificación y la petición (DNS rebinding): conéctate a la IP que verificaste;
  • en nubes activa el modo protegido de metadatos (en AWS — IMDSv2, que requiere token en la cabecera);
  • lo mejor — aislar esas peticiones en un servicio o proxy separado sin acceso a la red interna.

Inyección CRLF y splitting de respuesta

Las cabeceras en HTTP/1.1 se separan con \r\n. Si la aplicación inserta entrada del usuario en una cabecera sin validar — por ejemplo, en Location al redirigir — el atacante envía %0d%0a y adjunta sus propias cabeceras o incluso el cuerpo de la respuesta: establecer cookie, inyectar script, envenenar el caché. Los frameworks modernos prohiben saltos de línea en cabeceras, pero código casero y bibliotecas antiguas son vulnerables.

En Nginx hay una trampa propia: $uri está decodificado, y en return 302 https://$host$uri; a través de %0d%0a se puede inyectar una cabecera. Usa $request_uri — se pasa en su forma original y codificada.

Redirecciones abiertas

Una URL del tipo https://example.ru/login?next=https://attacker.example que después del login redirige al usuario a un sitio ajeno. En sí no es un hack, pero el enlace empieza con tu dominio y genera confianza en correos de phishing, y en OAuth una redirección así puede filtrar el token.

Protección: permite en next solo rutas relativas dentro de tu sitio o direcciones de una lista. La comprobación “empieza por /” no es suficiente: //attacker.example es la URL de otro sitio.

Clickjacking

Un atacante embeberá tu sitio en un iframe transparente encima de su propia página, y el usuario, creyendo pulsar un botón en la página ajena, pulsa “Eliminar cuenta” o “Confirmar pago” en la tuya. Protección — prohibir embebido:

Content-Security-Policy: frame-ancestors 'none'
X-Frame-Options: DENY

Auto-chequeo en 10 minutos

Los comandos abajo verifican los problemas más frecuentes. Ejecútalos solo contra tus sitios; example.ru — tu dominio, 192.0.2.10 — la IP de tu servidor.

Host desconocido. Una petición con un nombre ajeno a la IP de tu servidor no debe llegar a la aplicación:

bash
curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: evil.example' http://192.0.2.10/
# bien: 000 (Nginx cerró la conexión con return 444) o 4xx; mal: 200 con la página del sitio

curl -sk -o /dev/null -w '%{http_code}\n' --resolve evil.example:443:192.0.2.10 https://evil.example/
# bien: 000 — el handshake fue rechazado (ssl_reject_handshake)

Reflejo de X-Forwarded-Host. Un nombre falsificado no debe aparecer en enlaces de la página:

bash
curl -s -H 'X-Forwarded-Host: evil.example' https://example.ru/ | grep -c evil.example   # debe ser 0

Flags de cookie. La cookie de sesión debe tener Secure, HttpOnly y SameSite:

bash
curl -sI https://example.ru/login | grep -i '^set-cookie'

Cabeceras de seguridad:

bash
for h in strict-transport-security content-security-policy x-content-type-options \
         referrer-policy x-frame-options permissions-policy; do
  printf '%-28s ' $h; curl -sI https://example.ru/ | grep -qi "^$h:" && echo HAY || echo NO
done

Redirección abierta. Sustituye los parámetros que tu sitio usa para volver tras el login (next, return, redirect, url):

bash
curl -s -o /dev/null -w '%{http_code} -> %{redirect_url}\n' 'https://example.ru/login?next=//evil.example'
# mal: redirección a evil.example

Inyección CRLF:

bash
curl -sI 'https://example.ru/%0d%0aX-Injected:%20yes' | grep -ci '^x-injected'   # debe ser 0

Método TRACE debe estar desactivado:

bash
curl -si -X TRACE https://example.ru/ | head -1   # 405 o 403

Páginas personales y caché. Abre una página accesible solo tras iniciar sesión dos veces con la cookie de sesión y una sin ella:

bash
curl -sI -b 'session=TU_SESION' https://example.ru/account | grep -iE '^(cache-control|x-cache|cf-cache-status|age)'

En la respuesta debe aparecer Cache-Control: private o no-store, y en el CDN un fallo o bypass del caché (MISS, BYPASS, DYNAMIC), sin la cabecera Age.

SSRF. Si el sitio tiene funciones de descarga por URL (avatar desde dirección, importación, webhooks), pásales las direcciones http://127.0.0.1:22/ y http://169.254.169.254/latest/meta-data/. La aplicación debe rechazarlas, no devolver el contenido ni un error de conexión con detalles.

Request smuggling es difícil de comprobar manualmente. Para eso hay la extensión HTTP Request Smuggler para Burp Suite y la utilidad smuggler.py; ejecútalas en un entorno de prueba con la misma cadena de proxies que en producción.

Qué comprobar

  • Proxy y servidores de aplicación actualizados; peticiones con Content-Length y Transfer-Encoding simultáneos no llegan a la aplicación de forma ambigua.
  • Existe default_server que cierra peticiones con Host desconocido.
  • Páginas personales no son cacheadas por el CDN.
  • Cookies de sesión: Secure; HttpOnly; SameSite=Lax.
  • Existe CSP sin 'unsafe-inline' para scripts y con frame-ancestors.
  • Las peticiones del servidor a direcciones proporcionadas por usuarios no llegan a la red interna.
  • Los parámetros de redirección se validan contra una lista.

Para comprobación automática sirven OWASP ZAP y Burp Suite: contienen escáneres para todas las clases de ataques citadas. Ejecútalos en un entorno de prueba — el escaneo activo crea registros, envía formularios y puede dañar datos.

// Tarea parecida

Si estas resolviendo algo parecido

Este articulo pertenece a uno de los temas principales de trabajo. Puedes seguir leyendo sobre el tema, ir a la pagina principal para entender a que me dedico o abrir directamente los servicios.

Tema del articulo

Servidores e infraestructura

VPS, Linux, stack web, migraciones, hosting, bases de datos y operacion base.

Tareas frecuentes de esta tema

  • Migrar un sitio o servicio a un nuevo servidor
  • Configurar Linux, Nginx, base de datos y copias de seguridad
  • Entender por que el sistema funciona de forma inestable

// Siguiente paso

Si necesitas ayuda con este tema y no solo otro articulo, es mejor ir directo a la pagina del servicio. La pagina principal y la seleccion de materiales quedan como rutas secundarias.

Abrir servicios

// Reviews

Reseñas relacionadas

apande

apande

Configuración de Nginx y OpenCart

07.09.2024 · ★ 5/5

Un comprador muy poderoso
kireevk

kireevk

Diagnóstico de Nginx Proxy Manager en un contenedor Docker y solución del problema

15.03.2024 · ★ 5/5

Comprador acostumbrado

// Contact

¿Necesitas ayuda?

Escríbeme y te ayudaré a resolver el problema

Respondo en un día laborable (03:00-13:00 GMT)

Или оставьте заявку здесь:

Confirme que no es un bot.

Escribir y recibir una respuesta rápida