Fortalecimiento de cabeceras de seguridad HTTP: una guía de implementación paso a paso
De una calificación F a una A reforzada: despliegue HSTS, nosniff, protección de marcos y Permissions-Policy de inmediato, luego implemente Content-Security-Policy en tres fases seguras, usando configuraciones de nginx y Apache.
Ejecuta la misma comprobación ahora — gratis
Introduce tu dominio y obtén una auditoría de 9 puntos — DNS, SPF/DKIM/DMARC, SSL, cabeceras de seguridad, exposición en dark web — en 60 segundos.
Sin registro · <60s · Procesamiento GDPR/UE
Las cabeceras de seguridad HTTP son el control de seguridad más económico que jamás implementará: un puñado de cabeceras de respuesta, configuradas una sola vez en el servidor web o CDN, que instruyen a cada navegador visitante a rechazar clases enteras de ataques: clickjacking, degradación de protocolo, MIME sniffing, filtración de datos entre sitios y gran parte del cross-site scripting. Sin embargo, la mayoría de los sitios web aún se publican sin ellas, o con valores copiados de una publicación de blog que rompen silenciosamente la funcionalidad.
Esta guía acompaña a un administrador de TI a través de un fortalecimiento completo y por etapas de las cabeceras de un sitio web de producción: qué hace cada cabecera, los valores exactos a implementar, cómo desplegar Content-Security-Policy sin romper el sitio, la configuración del servidor web para nginx y Apache, y cómo verificar el resultado. Reserve una jornada laboral de esfuerzo distribuida en dos o tres semanas (el período de report-only de CSP representa la mayor parte del tiempo).
Cómo protegen las cabeceras a los usuarios
Cada cabecera en esta guía es una instrucción de su servidor al navegador del visitante. El navegador la aplica localmente; no se requiere ningún dispositivo, agente o cambio de código:
Su servidor Navegador del visitante
----------- -----------------
HTTP/1.1 200 OK
Strict-Transport-Security ------> "No volver a cargar este sitio mediante HTTP"
Content-Security-Policy ------> "Solo ejecutar scripts de estos orígenes"
X-Frame-Options ------> "Rechazar la renderización de este sitio en un iframe"
X-Content-Type-Options ------> "Nunca adivinar tipos de archivo"
Referrer-Policy ------> "No filtrar URLs completas a otros sitios"
Permissions-Policy ------> "Este sitio no puede usar cámara/micrófono/ubicación"
Debido a que la aplicación ocurre en el navegador, las cabeceras protegen a sus usuarios incluso cuando el ataque no toca su servidor en absoluto; por ejemplo, una página de phishing que enmarca su formulario de inicio de sesión, o un script publicitario inyectado que exfiltra datos de formularios.
Paso 1 — Línea base: mida antes de cambiar nada
Ejecute su sitio a través de un escáner de cabeceras: el verificador de cabeceras de seguridad gratuito de FortifyNet califica cada cabecera y muestra una solución de una línea, y securityheaders.com es una buena segunda opinión. Guarde el resultado. Un sitio típico no fortalecido obtiene una F con solo Server y X-Powered-By presentes; ambos filtran información en lugar de proteger algo.
También inventaríe lo que carga realmente el sitio. Abra la pestaña Red de las herramientas de desarrollador del navegador en sus cinco páginas más importantes y anote cada origen de terceros: analíticas, fuentes, administradores de etiquetas, widgets de chat, iframes de pago, videos embebidos. Esta lista se convertirá en su lista de permitidos (allowlist) de CSP en el Paso 4.
Paso 2 — Despliegue las cinco cabeceras seguras de inmediato
Cinco cabeceras están efectivamente libres de riesgos para casi todos los sitios y deberían incluirse en la primera ventana de cambios.
Strict-Transport-Security (HSTS)
Fuerza HTTPS para cada visita futura, derrotando ataques de SSL-stripping en Wi-Fi público.
Strict-Transport-Security: max-age=31536000; includeSubDomains
Comience con max-age=31536000 (un año) solo si todos los subdominios ya sirven HTTPS; de lo contrario, comience con max-age=86400 y sin includeSubDomains, luego expanda. Una vez estable, considere HSTS preload: estar codificado en los navegadores elimina incluso la ventana de la primera visita, pero es efectivamente irreversible, así que trate el preload como una puerta de un solo sentido.
X-Content-Type-Options
X-Content-Type-Options: nosniff
Evita que los navegadores "olfateen" (sniffing) los tipos de archivo, lo que impide que un archivo subido por un usuario y servido como texto sea ejecutado como script. No hay ninguna razón legítima para omitir esto.
X-Frame-Options
X-Frame-Options: DENY
Bloquea el clickjacking: un atacante que superpone su sitio real enmarcado con botones invisibles. Use SAMEORIGIN en su lugar si su propio sitio se enmarca legítimamente a sí mismo (algunos paneles de administración y funciones de vista previa lo hacen). El CSP moderno frame-ancestors reemplaza a esta cabecera, pero mantenga ambas para navegadores antiguos.
Referrer-Policy
Referrer-Policy: strict-origin-when-cross-origin
Asegura que los enlaces a sitios externos revelen solo su origen (https://example.com) en lugar de la URL completa, que puede contener términos de búsqueda, rutas de cuenta o tokens de restablecimiento. Este valor es también el predeterminado en navegadores modernos, pero configurarlo explícitamente lo garantiza en todas partes.
Permissions-Policy
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
Declara qué funciones potentes del navegador puede usar el sitio (y cualquier script inyectado). Deniegue todo lo que no use; un script de terceros comprometido no podrá solicitar la cámara silenciosamente. Vuelva a agregar funciones (p. ej., payment=(self "https://js.stripe.com")) solo cuando sea realmente necesario.
Paso 3 — Elimine las cabeceras que filtran información
Mientras edita la configuración, elimine los valores predeterminados innecesarios: las cadenas de versión Server y X-Powered-By. No ofrecen ninguna función y entregan a los atacantes sus versiones exactas de software para la coincidencia de CVE. En nginx: server_tokens off;. En Apache: ServerTokens Prod y ServerSignature Off. Para PHP: expose_php = Off.
Paso 4 — Content-Security-Policy, la forma por etapas
CSP es la cabecera más poderosa, y la única que puede romper su sitio si se despliega sin cuidado. Nunca copie un CSP de otro sitio. Siga este proceso de tres etapas en su lugar.
Etapa A: Report-Only (1-2 semanas)
Despliegue la política en modo Content-Security-Policy-Report-Only. Los navegadores la evalúan e informan de violaciones, pero no bloquean nada:
Content-Security-Policy-Report-Only: default-src 'self';
script-src 'self' https://www.googletagmanager.com https://js.stripe.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
img-src 'self' data: https:;
frame-src https://js.stripe.com https://www.youtube-nocookie.com;
connect-src 'self' https://www.google-analytics.com;
frame-ancestors 'none';
report-uri /csp-report
Construya la lista de permitidos a partir de su inventario del Paso 1. Apunte report-uri a un endpoint pequeño (o un servicio alojado) y deje que el tráfico real pruebe cada página, incluyendo el pago y las páginas de cuenta poco visitadas, durante al menos una semana.
Etapa B: Corrija las violaciones
Revise los informes y la consola de herramientas de desarrollo. Los hallazgos habituales:
| Violación | Causa | Solución |
|---|---|---|
| script inline bloqueado | manejadores onclick=, etiquetas script inline | mover a archivos externos, o añadir nonces por respuesta |
| eval bloqueado | bibliotecas antiguas, algunos administradores de etiquetas | actualizar biblioteca; evitar 'unsafe-eval' |
| origen desconocido | un script cargando un script | añadir el origen, o eliminar el widget |
| data: font | fuentes de iconos embebidas en CSS | permitir font-src data: o cambiar a iconos SVG |
El estándar de oro para los scripts son los nonces: el servidor genera un valor aleatorio por respuesta, lo añade a la cabecera (script-src 'nonce-R4nd0m') y a cada etiqueta script legítima. Los scripts inyectados carecen del nonce y mueren. 'strict-dynamic' (CSP Nivel 3) permite entonces que un script aprobado por nonce cargue sus propias dependencias; este es el patrón recomendado por Google y reduce drásticamente la lista de permitidos.
Acepte 'unsafe-inline' para estilos si eliminarlo supone un esfuerzo desproporcionado; el riesgo práctico del CSS inline es mucho menor que el del JS inline. No acepte 'unsafe-inline' para scripts; esto deshabilita el valor principal de la cabecera.
Etapa C: Ejecutar
Cambie el nombre de la cabecera a Content-Security-Policy, mantenga el endpoint de informes activo permanentemente y observe las primeras 48 horas. Cualquier regresión está a una línea de configuración de distancia de revertir a Report-Only.
Paso 5 — Configuración del servidor web
Establezca las cabeceras en un lugar central (el servidor web o CDN), no por aplicación, para que cada respuesta (incluyendo páginas de error y activos estáticos) esté cubierta.
nginx (dentro del bloque server; always es necesario para que las cabeceras también se adjunten a las respuestas 4xx/5xx):
server_tokens off;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Content-Security-Policy "default-src 'self'; ..." always;
Cuidado con la trampa clásica de nginx: las directivas add_header no se heredan si un bloque location declara su propio add_header. Manténgalas todas a nivel de servidor o repita el conjunto completo.
Apache (en el vhost o .htaccess, con mod_headers habilitado):
ServerTokens Prod
ServerSignature Off
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "DENY"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
Header always set Content-Security-Policy "default-src 'self'; ..."
En CDNs (Cloudflare, CloudFront, Vercel, Netlify) utilice sus transformaciones de cabeceras de respuesta para que el borde aplique el mismo conjunto a las respuestas cacheadas.
Paso 6 — Verifique y siga verificando
Después de cada cambio: actualice fuertemente con las herramientas de desarrollador abiertas y lea las cabeceras de respuesta reales; luego vuelva a ejecutar el verificador de cabeceras y compárelo con la línea base del Paso 1. Un sitio bien fortalecido alcanza la calificación A.
Las cabeceras se degradan. Un rediseño añade un widget que el CSP bloquea; una migración a un nuevo balanceador de carga elimina silenciosamente todo el conjunto. Programe la verificación: el monitoreo continuo de FortifyNet vuelve a comprobar las cabeceras y alerta sobre cualquier cambio, lo cual es exactamente el modo de falla de "deriva de configuración" que los procesos manuales omiten.
Fortalecimiento de cookies: las cabeceras de respuesta olvidadas
Las cookies de sesión también se establecen mediante cabeceras de respuesta, y un conjunto de cabeceras fortalecido está incompleto sin ellas. Tres atributos en cada Set-Cookie:
Set-Cookie: session=abc123; Secure; HttpOnly; SameSite=Lax; Path=/
- Secure: la cookie nunca se envía a través de HTTP plano. Con HSTS, esto cierra el ciclo por completo.
- HttpOnly: JavaScript no puede leer la cookie, por lo que incluso un XSS exitoso no puede exfiltrar el token de sesión directamente.
- SameSite=Lax (o Strict): el navegador retiene la cookie en solicitudes entre sitios, neutralizando la mayoría de los ataques CSRF a costo cero de código. Use SameSite=None; Secure solo para cookies que realmente deben viajar entre sitios (widgets embebidos, flujos SSO).
Considere los prefijos de cookie para defensa en profundidad: una cookie llamada __Host-session solo es aceptada por los navegadores si es Secure, no tiene atributo Domain y Path=/; esto hace imposible que un subdominio comprometido la sobrescriba.
Aislamiento de origen cruzado: COOP, CORP y sensatez de CORS
Tres cabeceras más nuevas controlan cómo interactúa su sitio con otros orígenes a nivel de navegador:
| Cabecera | Valor recomendado | Qué evita |
|---|---|---|
| Cross-Origin-Opener-Policy | same-origin | otros sitios con referencia ejecutable a su ventana (tabnabbing, XS-Leaks) |
| Cross-Origin-Resource-Policy | same-origin (o same-site) | otros orígenes embebiendo sus recursos (robo de datos por inclusión) |
| Cross-Origin-Embedder-Policy | require-corp (avanzado) | cargar recursos de otro origen que no se han optado voluntariamente |
COOP: same-origin y CORP: same-origin son seguros para la mayoría de los sitios. COEP es más estricto (rompe cualquier imagen o script de otro origen que no envíe cabeceras CORP), así que despliéguelo solo si necesita aislamiento de origen cruzado (ej. para SharedArrayBuffer) y pruebe primero en Report-Only.
Mientras está aquí, verifique la sensatez de CORS: Access-Control-Allow-Origin: * en una API que usa cookies o devuelve datos de usuario es una vulnerabilidad real. Permita orígenes específicos y nunca refleje la cabecera Origin de la solicitud sin validación.
Caché de respuestas sensibles
El fortalecimiento de cabeceras incluye asegurarse de que los datos privados no se almacenen donde no pueda eliminarlos. Para cualquier respuesta autenticada o de datos personales:
Cache-Control: no-store
no-store supera a las combinaciones antiguas de Pragma y Expires y evita tanto el caché del navegador como el de intermediarios. El fallo clásico: un botón de cierre de sesión que funciona, pero el botón de retroceso sigue mostrando la página de cuenta desde el caché; no-store en páginas autenticadas lo soluciona. Mantenga un caché de larga duración (Cache-Control: public, max-age=31536000, immutable) para activos estáticos con huella digital; ambas políticas coexisten por ruta.
Notas para SPAs y frameworks
Las aplicaciones de una sola página (SPA) cambian ligeramente el cálculo de CSP:
- El shell HTML se sirve una vez, por lo que los nonces deben generarse por respuesta en el borde o el servidor; un generador de sitios estáticos que hornea un nonce en la compilación no tiene valor de seguridad. Si el sitio es totalmente estático, use hashes (sha256-…) de los scripts inline específicos en lugar de nonces.
- Los frameworks de hidratación (Next.js, Nuxt) incluyen el estado como etiquetas script; ambos soportan la propagación de nonce desde el middleware; use la integración de CSP documentada del framework en lugar de luchar contra ella.
- connect-src es la directiva más activa de una SPA: cada origen de API, endpoint de websocket y sumidero de telemetría debe estar listado. Las entradas faltantes fallan silenciosamente para los usuarios (solicitudes bloqueadas, funciones muertas); otra razón por la que la etapa Report-Only no es negociable.
- Para APIs consumidas por navegadores, aplique las mismas cabeceras base a las respuestas de API: nosniff, no-store donde sea relevante, y un CSP restrictivo (default-src 'none') en cualquier endpoint que pudiera abrirse directamente en una pestaña del navegador.
Verificación desde la línea de comandos
Los escáneres son convenientes, pero también debería poder verificar las cabeceras directamente, especialmente al depurar por qué una cabecera está presente en una ruta pero falta en otra. Use curl con el indicador de solicitud de cabecera (I mayúscula), o el panel Red en las herramientas de desarrollador del navegador, y lea las cabeceras de respuesta sin procesar para tres URLs: la página de inicio, una ruta de área autenticada y una página que no existe. Una respuesta saludable se ve así:
HTTP/2 200
content-security-policy: default-src 'self'; ...
permissions-policy: camera=(), microphone=(), geolocation=()
referrer-policy: strict-origin-when-cross-origin
strict-transport-security: max-age=31536000; includeSubDomains
x-content-type-options: nosniff
x-frame-options: DENY
Compruebe los tres casos: la página de inicio, una ruta autenticada y un 404. Repita la verificación tanto sobre HTTP como HTTPS, y en las variantes www y dominio desnudo si ambos se sirven; los redireccionamientos suelen provenir de un bloque de configuración diferente que no lleva nada del fortalecimiento, y un ataque de SSL-stripping comienza exactamente en ese primer salto desprotegido. La página de error es la otra brecha clásica; si el 404 carece de las cabeceras que tiene la página de inicio, el conjunto no se aplica de forma suficientemente centralizada; muévalo a la capa del servidor o del borde.
Dos hábitos más que vale la pena automatizar:
- Verificación de CI: un script de cinco líneas en el pipeline de despliegue que haga curl a la URL de staging y falle la compilación si falta una cabecera esperada.
- Alertas de cambio en producción: monitoreo externo que vuelve a leer el conjunto de cabeceras en vivo de forma programada y alerta sobre cualquier diferencia; esto es parte del monitoreo continuo de FortifyNet.
Mitos y errores comunes
- "Tenemos un WAF, las cabeceras son redundantes." Un WAF inspecciona el tráfico hacia su servidor; las cabeceras controlan lo que hace el navegador con sus páginas. Son capas complementarias.
- "X-XSS-Protection debería estar habilitado." No; la cabecera está obsoleta; el auditor que marca su ausencia está desactualizado. Los navegadores modernos eliminaron el auditor XSS y, en navegadores antiguos, introducía vulnerabilidades. Establézcalo en 0 u omítalo por completo.
- "CSP rompió el sitio una vez, así que lo eliminamos." El fallo fue saltarse Report-Only, no el CSP en sí.
- "La calificación A significa seguro." Las cabeceras son una capa. Un sitio con calificación A y un CMS sin parches sigue estando a un CVE de compromiso, razón por la cual el escaneo de cabeceras es una prueba entre varias en una auditoría completa de FortifyNet.
Lista de verificación de despliegue
- Escaneo de línea base guardado; inventario de orígenes de terceros completo
- HSTS, nosniff, X-Frame-Options, Referrer-Policy, Permissions-Policy desplegados
- Filtración de versiones de Server/X-Powered-By eliminada
- CSP ejecutándose en Report-Only con endpoint de informes
- Violaciones triadas; nonces usados para scripts; sin 'unsafe-inline' para script-src
- CSP aplicado; frame-ancestors configurado; informes mantenidos activos
- Cabeceras configuradas centralmente (servidor/CDN), uso de always/Header always
- El re-escaneo muestra calificación A; monitoreo continuo habilitado
Fuentes
Verifica tu dominio ahora
Ejecuta una auditoría gratuita y comprueba si tu dominio tiene los problemas descritos.