Tu redirección HTTP → HTTPS cross-domain está mal

·

Ya son varios los SEO (que van de listos) que me han dicho que nuestra configuración de redirects era «ineficiente». Que hacer http://example.com → https://example.com → https://example.net generaba «latencia innecesaria» y «dilución de link juice». Me recomendó hacer un único http://example.com → https://example.net.

Le di las gracias y no le hice ni caso (thanks, but no thanks). No porque sea terco, sino porque lo que me pedía es exactamente lo que el RFC 6797 prohíbe.

Voy a explicarte por qué. Con referencias. Con código. Con la parte aburrida de los MUST y SHOULD. Porque a veces los RFC tienen razón, aunque un SEO con cuarenta mil backlinks en su perfil de Fiverr piense lo contrario.

¿Qué hace un navegador cuando recibe un redirect?

Cuando un servidor responde con un código 301, 302, 307 o 308, el navegador recibe la respuesta y hace una nueva petición al destino que indica el header Location:. Eso lo sabe todo el mundo.

Pero hay un caso especial: cuando el navegador ya sabe que un dominio requiere HTTPS.

Imagina que visitas example.com por primera vez en tu vida. Tu navegador hace una petición HTTP normal. El servidor responde 301 Location: https://example.com. Tu navegador sigue el redirect y listo.

Ahora imagina que vuelves a escribir http://example.com en la barra. ¿Qué pasa?

Depende. Si example.com tiene un header Strict-Transport-Security, tu navegador lo recordará. Y aquí es donde las cosas se ponen interesantes.

Según la sección 8.3 del RFC 6797, el navegador reescribe internamente la URL antes de hacer ninguna conexión:

Whenever the UA prepares to ‘load’ any ‘http’ URI, the UA MUST first determine whether a domain name is given in the URI and whether it matches a Known HSTS Host. […] If a congruent match is found, the UA MUST replace the URI scheme with ‘https’.

Traducido: si example.com es un Known HSTS Host, el navegador coge http://example.com y lo convierte en https://example.com en memoria, sin hacer ninguna petición de red. Esto se manifiesta como un redirect 307 en las DevTools, pero no es un redirect real. No hay round-trip. No hay latencia. Es un cambio interno del navegador.

Así que la cadena:

http://example.com → → https://example.com → 301 → https://example.net

solo genera un redirect real por red en la segunda flecha. El primer salto no existe en términos de latencia.

Si tu dominio está en la preload list de Chrome (y por extensión de Firefox, Safari y Edge), esto pasa incluso sin haber visitado el sitio antes. El navegador ya sabe que example.com requiere HTTPS desde que se instaló.

¿Qué dice exactamente el RFC sobre cómo un host debe responder a peticiones HTTP?

La sección 7.2 del RFC 6797 (HTTP Request Type) es bastante clara. Copio textualmente:

If an HSTS Host receives an HTTP request message over a non-secure transport, it SHOULD send an HTTP response message containing a status code indicating a permanent redirect, such as status code 301, and a Location header field value containing either the HTTP request’s original Effective Request URI altered as necessary to have a URI scheme of ‘https’, or a URI generated according to local policy with a URI scheme of ‘https’.

Dos opciones: redirigir al mismo URI con el esquema cambiado, o redirigir a otro sitio con esquema HTTPS. Aquí es donde alguien podría argumentar: «mira, el RFC dice que puedes redirigir a otro dominio siempre que sea HTTPS». Y tiene razón. El RFC permite redirigir a otro dominio, pero…

Más abajo en la misma sección:

An HSTS Host MUST NOT include the STS header field in HTTP responses conveyed over non-secure transport.

Es un MUST NOT. No es opcional. Esto significa que en el redirect HTTP→HTTPS que va por HTTP, no puedes enviar el header Strict-Transport-Security. No puedes informar al navegador de que tu dominio tiene política HSTS.

Ahora, si haces el redirect http://example.com → https://example.net, la respuesta viaja completamente en texto plano. Cualquiera en la red puede verla. No es solo el redirect inicial. Es que no hay cifrado en ningún punto.

Lo que el navegador hace con los headers STS por HTTP

Esto es lo más importante de todo.

La sección 8.1 del RFC 6797 (Strict-Transport-Security Response Header Field Processing) dice:

If an HTTP response is received over insecure transport, the UA MUST ignore any present STS header field(s).

MUST. No SHOULD. No MAY. MUST.

Esto significa que si por algún motivo tu servidor enviara un header Strict-Transport-Security en una respuesta HTTP (algo que el propio RFC prohíbe en el lado del servidor, pero que podría pasar por un proxy mal configurado o un error), el navegador lo descartaría automáticamente.

¿Y por qué existe esta regla? La sección 14.3 del RFC lo explica. Básicamente, si el navegador aceptara headers STS por HTTP, un atacante que pudiera hacer un MITM (hombre en el medio) podría:

  1. Interceptar la petición HTTP a example.com
  2. Devolver un header Strict-Transport-Security: max-age=0 (que elimina la política HSTS)
  3. Dejar al navegador vulnerable, porque ya no exigiría HTTPS para example.com

O peor: devolver un max-age=31536000; includeSubDomains sobre HTTP, y si el navegador lo aceptara, hacer que el navegador exija HTTPS para todos los subdominios de example.com, aunque algunos solo ofrezcan HTTP. Eso sería un ataque de denegación de servicio.

El RFC llama a esto «Ramifications of HSTS Policy Establishment Only over Error-Free Secure Transport» (Sección 14.3). La política HSTS solo puede establecerse sobre un canal TLS verificado y sin errores. Es una decisión de diseño deliberada para cerrar esta clase de ataques.

La vulnerabilidad real del primer salto

La sección 14.6 del RFC se llama «Bootstrap MITM Vulnerability». Específicamente:

Bootstrap MITM vulnerability is a vulnerability that users and HSTS Hosts encounter in the situation where the user manually enters, or follows a link, to an unknown HSTS Host using an ‘http’ URI rather than an ‘https’ URI.

Cuando un usuario escribe http://example.com en la barra, no hay forma de asegurarle que llega al lugar correcto. Un atacante en la red puede interceptar esa primera petición y responder con lo que quiera: una copia fake del sitio, un redirect a otro dominio, o simplemente capturar cookies.

El modelo de seguridad de HSTS dice: «primero establece el canal seguro, luego confía en lo que te diga el servidor». Si haces http://example.com → https://example.net, estás confiando en que el contenido de ese redirect HTTP es legítimo. Pero acaba de viajar en texto plano. No hay autenticación. No hay integridad.

El redirect de https://example.com → https://example.net sí es de confianza, porque viaja sobre TLS. El servidor de example.com ha demostrado su identidad mediante su certificado. Su respuesta no puede ser modificada por un atacante en la red. Pero en el primer salto, example.com no ha demostrado nada.


Quinto: lo que pasa con la preload list

Voy a ser honesto: hasta hace unos años no entendía bien cómo funcionaba la preload list. Pensaba que era como una cookie gigante que guardaban los navegadores. No es exactamente así.

La preload list de HSTS es una lista de dominios que los navegadores incluyen «de fábrica». Cuando instalas Chrome, Firefox o Safari, ya llevan esta lista dentro. Si example.com está en la preload list, tu navegador sabe desde el primer segundo que example.com requiere HTTPS. No necesita haberlo visitado antes.

Esto significa que si example.com está en la preload list y alguien intenta hacer un redirect http://example.com → https://example.net, ese redirect nunca se ejecutará. El navegador convierte internamente http://example.com a https://example.com antes de hacer la petición. Tu configuración de redirect directo es irrelevante.

No es que el redirect no funcione. Es que el navegador nunca llega a pedirlo.

Entonces, ¿el SEO tenía razón sobre algo?

No. Pero voy a ser justo: su confusión es comprensible.

El argumento de la latencia se basa en la idea de que un redirect genera round-trips adicionales. En el caso de HSTS para un Known HSTS Host, no hay ningún round-trip adicional en el primer salto. Es una reescritura interna.

El argumento del link juice tampoco se sostiene. Googlebot indexa contenido tras seguir redirects, y el PageRank fluye a través de cadenas de redirects. Un 307 interno (HSTS) no consume link equity de forma perceptible. Si tu preocupación es el crawl budget, un redirect de más en una cadena no cambia nada; Googlebot sigue todos los redirects.

Lo que sí podría argumentarse es que si example.com nunca ha establecido política HSTS (no ha enviado STS header a ningún navegador), el navegador hace la petición HTTP real al servidor. En ese caso, tienes un redirect HTTP real. Pero este redirect debe ser a https://example.com, no directamente a https://example.net, por las razones que he explicado.

¿Y si no puedo permitirme el certificado en example.com?

Esto pasa. Hay escenarios donde alguien quiere apagar example.com y concentrar todo en example.net. No quiere mantener un certificado en example.com solo para hacer un redirect.

Tienes opciones:

  1. example.com redirige HTTP→HTTPS a example.net. Necesitas un certificado en example.com. Puedes usar Let’s Encrypt, es gratuito. El certificado solo necesita cubrir example.com, no example.net.
  2. example.com simplemente deja de existir. Quitas el registro DNS. No es una opción elegante, pero funciona.
  3. Usar un CDN o proxy que gestione el TLS por ti. Cloudflare, Fastly, o cualquier provider que te permita configurar un redirect desde sus servidores con su propio certificado. Tu servidor no maneja TLS, pero el redirect llega por HTTPS desde el punto de vista del usuario.
  4. No uses HSTS en example.com. Si example.com no tiene policy HSTS, no tiene que hacer el salto interno a HTTPS. Pero entonces pierde toda la protección HSTS para ese dominio, y los navegadores no aprenderán nunca que example.com requiere HTTPS.

La opción que no tienes es hacer un redirect HTTP directo a otro dominio sin antes establecer TLS en el dominio de origen. No porque yo lo diga, sino porque el RFC 6797 dice que el canal debe ser seguro para establecer confianza.

El resumen, resumido

  1. Cuando un navegador conoce la política HSTS de un dominio, el redirect HTTP→HTTPS del mismo dominio es gratis en latencia (0ms, reescritura interna).
  2. Un redirect HTTP→HTTPS a otro dominio no puede incluir el header STS, con lo que no establece política HSTS de ningún tipo.
  3. El primer salto HTTP es vulnerable a ataques MITM. Redirigir sobre HTTP a otro dominio no mitigael problema; lo empeora.
  4. La política HSTS solo se establece sobre transporte seguro y sin errores (RFC 6797 §14.3).
  5. Si el dominio está en la preload list, tu configuración de redirect directo es irrelevante.

La cadena correcta existe por seguridad, no por burocracia. El comité que escribió el RFC 6797 (Jeff Hodges, Collin Jackson, Adam Barth) lleva años protegiendo a los usuarios de exactamente este tipo de configuraciones. No lo hicieron para complicarte la vida.

Y sí, tengo mis propias dudas sobre lo engorroso que puede ser configurar esto en nginx o en el reverse proxy de turno. He tenido que pelearme con configuraciones de redirect en Angie más de una vez. Pero el estándar es el estándar, y cuando un «experto SEO» te dice que lo hagas de otra manera, pregúntale qué dice el RFC 6797 sección 7.2. Si no sabe responder, ya tienes tu respuesta.

La próxima vez que alguien te diga que tu configuración de redirects es «ineficiente», recuérdale que los estándares de seguridad existen porque alguien, en algún momento, pensó en lo que pasaría si un atacante pudiese manipular el primer mensaje.

Spoiler: no termina bien.

Comments

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *