Loading...

Seguridad · 10 min de lectura

Cabeceras de seguridad HTTP: qué hace cada una y qué valores usar

Las cabeceras de seguridad HTTP son cabeceras de respuesta que le dicen al navegador con cuánto rigor tratar tus páginas. Hoy importan seis: Strict-Transport-Security, X-Content-Type-Options, X-Frame-Options o frame-ancestors de CSP, Referrer-Policy, Permissions-Policy y Content-Security-Policy. Las cinco primeras son una línea cada una; CSP necesita una prueba en modo report-only antes de aplicarla.

Actualizado

Cabeceras de seguridad HTTP: qué hace cada una y qué valores usar

Qué hacen las cabeceras de seguridad y qué no

Una cabecera de seguridad es una cabecera de respuesta que le pide al navegador más rigor con tu página del que aplicaría por defecto: usar solo HTTPS, no adivinar tipos de archivo, no dejarse enmarcar por otros sitios, compartir menos en el Referer, mantener la cámara apagada y ejecutar solo los scripts que has aprobado.

Son defensa en profundidad. Ninguna arregla una aplicación vulnerable: una inyección SQL o un login roto siguen igual de rotos con un juego de cabeceras perfecto. Lo que hacen es limitar el daño de los fallos que un navegador puede contener, como cross-site scripting, clickjacking, degradación de protocolo y fugas de datos por el referrer, para cada visitante y a cambio de unos cientos de bytes.

Además, solo funcionan en navegadores. Un cliente de API, un crawler o el script de un atacante las ignora todas; por eso la primera línea siguen siendo un WAF y el propio código de la aplicación.

Strict-Transport-Security (HSTS)

Strict-Transport-Security le dice al navegador que use HTTPS para tu hostname y que lo recuerde durante max-age segundos, de modo que ni siquiera una dirección http:// tecleada sale del dispositivo en claro. Es la cabecera con más efecto por menos trabajo, siempre que todas las URL del sitio funcionen ya por HTTPS.

Un comienzo seguro es max-age=31536000: un año, solo este hostname. includeSubDomains y preload llegan más lejos y son difíciles de deshacer. La guía de HSTS explica a qué te compromete cada una, cómo verificar la cabecera y cómo dar marcha atrás.

En CDN.com.tr, HSTS es un preset de la cuenta en Reglas de entrega, así que no hay que escribir ninguna línea para ella. Envía exactamente max-age=31536000 en las respuestas HTTPS y nada en HTTP plano.

X-Content-Type-Options: nosniff

Los navegadores solían adivinar el tipo de un archivo por su contenido cuando la cabecera Content-Type parecía incorrecta. Ese adivinar, llamado MIME sniffing, permitía que un archivo subido que parecía un script se ejecutara como tal. X-Content-Type-Options: nosniff lo desactiva: una hoja de estilos debe llegar como text/css y un script con un tipo JavaScript, o el navegador lo rechaza.

nosniff es el único valor, y la cabecera es segura en cualquier respuesta, sea HTML o no. Comprueba antes una sola cosa: que tu servidor etiqueta bien los archivos. Un script servido como text/plain deja de cargarse en cuanto pones la cabecera, que es justo el comportamiento que estás pidiendo.

X-Frame-Options y frame-ancestors: quién puede enmarcar tus páginas

El clickjacking carga tu página dentro de un marco invisible en otro sitio y alinea uno de sus botones con uno de los tuyos, de modo que el visitante pulsa "Eliminar cuenta" creyendo que pulsa "Reproducir". La defensa es decirle al navegador quién puede meter tu página en un marco.

X-Frame-Options tiene dos valores útiles: DENY para nadie y SAMEORIGIN para las páginas de tu propio origen. El antiguo valor ALLOW-FROM lo ignoran los navegadores actuales. Para permitir una lista de sitios se usa la directiva CSP frame-ancestors, por ejemplo frame-ancestors 'self' https://partner.example.

Cuando una respuesta lleva las dos, los navegadores que entienden frame-ancestors la siguen e ignoran X-Frame-Options. Enviar ambas es habitual e inofensivo, ya que la cabecera antigua cubre a los navegadores antiguos, siempre que digan lo mismo. Un límite: frame-ancestors solo funciona como cabecera HTTP, nunca en una etiqueta <meta>.

Referrer-Policy: qué sale con cada clic

Cuando un visitante sigue un enlace, o tu página carga una imagen, un script o una fuente de otro sitio, el navegador puede enviar la dirección de tu página en la cabecera Referer. Las URL completas filtran más de lo que parece: términos de búsqueda, números de pedido, el token de un enlace para restablecer la contraseña.

strict-origin-when-cross-origin es el valor sensato: la URL completa dentro de tu propio sitio, solo el origen (https://example.com/) hacia otros sitios, y nada cuando una página HTTPS enlaza a HTTP plano. Los navegadores actuales ya aplican esto, o algo más estricto, cuando la página no dice nada; fijarlo explícitamente mantiene el comportamiento sea cual sea el valor por defecto de cada navegador. Para una aplicación con URL sensibles, no-referrer no envía nada, a costa de perder datos de referencia en la analítica de otros sitios.

Permissions-Policy: apaga lo que no usas

Permissions-Policy decide qué funciones potentes del navegador pueden usar tu página y los marcos que contiene: cámara, micrófono, geolocalización, pagos, USB y otras. Una lista vacía () apaga una función para todos, (self) solo la permite a tu propio origen, y puedes nombrar los orígenes de los contenidos incrustados que de verdad la necesiten.

Una web corporativa o una tienda casi nunca necesita ninguna, así que camera=(), microphone=(), geolocation=() es un buen comienzo; añade más a medida que revises lo que incrustas. La ganancia es la contención: un script de terceros comprometido o un marco publicitario no puede pedirle la cámara al visitante. La aplican los navegadores basados en Chromium, así que tómala como un refuerzo sobre los demás controles, no como sustituto.

Content-Security-Policy: potente, y merece un plan

Una Content Security Policy le dice al navegador de dónde pueden venir los scripts, estilos, imágenes, fuentes, conexiones y marcos, y si puede ejecutarse código en línea. Bien hecha, convierte la mayoría de los fallos de cross-site scripting de "un atacante ejecuta código en las sesiones de tus usuarios" en "el navegador bloqueó un script y lo notificó".

También es la cabecera que rompe páginas. Bloques <script> en línea, atributos onclick, un gestor de etiquetas que inyecta más scripts, un endpoint de analítica del que nadie se acordaba: cada uno hay que permitirlo o reescribirlo. Así que despliégala en dos pasos. Primero envía la política como Content-Security-Policy-Report-Only: no se bloquea nada, y cada infracción aparece en la consola y, con un endpoint report-to o report-uri, en tus informes. Corrige o permite lo que muestren los informes y después envía la misma política como Content-Security-Policy.

La política de abajo es estricta pero legible. Para los scripts en línea que no puedas quitar, genera un nonce aleatorio en cada respuesta y ponlo tanto en la política como en la etiqueta <script>. Con 'strict-dynamic', los scripts que carga un script de confianza también son de confianza, lo que hace viables los gestores de etiquetas sin enumerar cada dominio que tocan. Evita 'unsafe-inline' para scripts: desactiva la mayor parte de la protección para la que existe la política.

Una política inicial estricta, en modo report-only

Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests

Valores seguros para copiar

La mayoría de los sitios puede empezar con este conjunto y ajustar solo las fuentes de la CSP y la lista de permisos. El orden da igual; lo que importa es que cada cabecera aparezca una vez.

X-XSS-Protection falta a propósito. Controlaba un filtro que los navegadores actuales han eliminado, y en los antiguos el propio filtro podía usarse en tu contra; no envíes nada o envía X-XSS-Protection: 0. Expect-CT está obsoleta por un motivo parecido y también puede desaparecer.

Cabeceras de respuesta para una página HTML

Strict-Transport-Security: max-age=31536000
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Content-Security-Policy-Report-Only: default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'

Configurarlas en CDN.com.tr

HSTS tiene su propio preset, descrito arriba. Las demás cabeceras de un solo valor van en una regla de entrega: en Reglas de entrega, edita la regla que sirve tus páginas, normalmente /, y añade una por línea en Encabezados personalizados con el formato Nombre-Cabecera valor (en la página de reglas con pestañas: Cabeceras → Añadir cabeceras de respuesta). Un valor con espacios va entre comillas dobles.

Una Content-Security-Policy completa no cabe ahí. Sus directivas se separan con punto y coma, y el panel y el edge rechazan ; en una línea de cabecera, porque un punto y coma cerraría la propia directiva de configuración del edge. Una directiva suelta no lleva punto y coma, así que frame-ancestors sola funciona en el edge. La política completa va en tu origen, en el servidor web o en la aplicación, donde además puede llevar un nonce por petición. También funciona una etiqueta HTML <meta http-equiv="Content-Security-Policy">, salvo para frame-ancestors, report-uri y sandbox, que los navegadores ignoran en una etiqueta meta.

Las cabeceras que añade una regla se aplican a las respuestas correctas y de redirección (2xx y 3xx), también a las servidas desde caché, en cuanto se publica la regla; una cabecera que deba aparecer también en las páginas de error va en el origen. Las cabeceras que envía tu origen se guardan con cada copia en caché, así que, después de cambiarlas, purga la caché del CDN de las páginas afectadas. Si tu origen ya envía alguna de estas cabeceras, selecciónala en Ocultar Encabezados en la misma regla, o los visitantes recibirán dos copias.

Encabezados personalizados en la regla que sirve tus páginas

X-Content-Type-Options nosniff
X-Frame-Options SAMEORIGIN
Referrer-Policy strict-origin-when-cross-origin
Permissions-Policy "camera=(), microphone=(), geolocation=()"
Content-Security-Policy "frame-ancestors 'self'"

Cómo comprobarlas

Empieza con curl, que muestra exactamente lo que envía el servidor. Revisa una página, un archivo estático y una página que no existe: las cabeceras configuradas en un solo sitio suelen faltar en los otros dos.

Después abre las herramientas de desarrollo del navegador. La pestaña Network muestra las cabeceras tal como las recibió el navegador, y la consola informa de cada infracción de CSP y de cada marco rechazado, con la directiva que lo causó. Los escáneres en línea como el HTTP Observatory de Mozilla puntúan el conjunto completo y explican cada hallazgo, lo que sirve como segunda opinión.

Por último, prueba el comportamiento y no solo la presencia: incrusta la página en un iframe de otro origen y comprueba que el navegador lo rechaza, y vigila la consola unos días con la política report-only antes de aplicarla.

¿Qué envía de verdad el servidor?
curl -sI https://www.example.com/ | grep -i -E \
  'strict-transport|x-content-type|x-frame|referrer-policy|permissions-policy|content-security'

# a static file and a missing page, too
curl -sI https://www.example.com/assets/app.css | grep -i x-content-type
curl -sI https://www.example.com/no-such-page | grep -i -E 'x-frame|content-security'

Preguntas frecuentes sobre cabeceras de seguridad

¿Qué cabeceras de seguridad debería enviar cualquier web?

HSTS, cuando todo el sitio funcione por HTTPS; X-Content-Type-Options: nosniff; X-Frame-Options: SAMEORIGIN o frame-ancestors en CSP; Referrer-Policy: strict-origin-when-cross-origin; y una Permissions-Policy que apague las funciones que no usas. Añade una Content-Security-Policy después de una prueba report-only.

¿Está obsoleta X-Frame-Options?

Superada más que eliminada. frame-ancestors de CSP hace el mismo trabajo con más control, y los navegadores que la admiten ignoran X-Frame-Options cuando llegan las dos. Enviar SAMEORIGIN o DENY a su lado sigue protegiendo a los navegadores antiguos; lo único muerto es ALLOW-FROM.

¿Sigo enviando X-XSS-Protection?

No. El filtro que controlaba se ha eliminado de los navegadores actuales, y en los antiguos podía usarse contra la propia página. No la envíes o envía 0, y confía en una Content-Security-Policy.

¿Puedo poner Content-Security-Policy a través de CDN.com.tr?

Una directiva suelta, sí: por ejemplo frame-ancestors 'self' en los Encabezados personalizados de una regla de entrega. Una política completa necesita punto y coma entre sus directivas, y las reglas del edge no lo aceptan; ponla en tu servidor web o en tu aplicación, o en una etiqueta meta para todo salvo frame-ancestors, report-uri y sandbox.

¿Afectan las cabeceras de seguridad al SEO o a la velocidad?

No de forma apreciable. Añaden unos cientos de bytes, y HTTP/2 y HTTP/3 comprimen las cabeceras repetidas. No son un factor de posicionamiento documentado. El único riesgo SEO es una CSP que bloquee tus propios scripts y rompa cómo se muestra la página, y el paso report-only lo detecta.

¿Las imágenes, el CSS y el JavaScript también necesitan estas cabeceras?

nosniff y HSTS son útiles en cualquier respuesta. Las demás, CSP, la protección de marcos, Referrer-Policy y Permissions-Policy, actúan sobre documentos, así que lo que importa son las páginas HTML. Enviarlas en todo no hace daño.