La inmensa mayoría de las recomendaciones sobre seguridad en PHP se centran en el código fuente: la salida de datos, utilizar sentencias con PDO y validar rigurosamente cada parámetro de entrada. Todas son prácticas indispensables, pero operan exclusivamente en lo que ocurre dentro de la aplicación. Tal como nos compartía Chiyana en el artículo anterior, Innovar está bien. Mantener también, es por ello que existe una segunda línea de defensa crítica que vive en las cabeceras de respuesta HTTP que tu backend devuelve al cliente.
Estas cabeceras instruyen al navegador sobre cómo debe comportarse, qué recursos tiene permitido cargar, si la web puede ser incrustada en un iframe por un tercero, si debe fiarse del tipo MIME declarado o cuánta información de la URL puede filtrarse al hacer clic en un enlace externo.
Configuradas correctamente, convierten al navegador en un aliado activo capaz de neutralizar categorías completas de ataque, incluso cuando se cuela un error en el código de la aplicación. Si se omiten, el comportamiento por defecto en cualquier instalación estándar de PHP, el navegador asume un modo permisivo donde una sola vulnerabilidad queda completamente expuesta.
No sustituyen al código seguro (una política CSP estricta no elimina un fallo XSS, solo limita drásticamente lo que el atacante puede hacer con él), pero representan una capa de defensa en profundidad económica de implementar y con un impacto preventivo inmediato.
A continuación, analizamos las 10 cabeceras y directivas esenciales para una aplicación PHP moderna, su comportamiento verificado en red y los riesgos operativos a vigilar.
1. Content-Security-Policy (CSP)
Content-Security-Policy (CSP) es la cabecera más potente y, al mismo tiempo, la que exige mayor atención en su despliegue. Define de forma explícita desde qué orígenes puede el navegador cargar scripts, hojas de estilo, imágenes u otros recursos, impidiendo la ejecución de scripts en línea (inline scripts), el vector primario de los ataques XSS.
header("Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'");
default-src 'self': Por defecto, solo se cargan recursos procedentes del propio origen.script-src 'self': Restringe la ejecución de JavaScript al mismo origen. Al omitir'unsafe-inline', el navegador bloquea cualquier etiqueta<script>embebida en el HTML o manejadores de eventos en atributos comoonclick.object-src 'none': Deshabilita por completo plugins heredados (Flash, Java applets, etc.).
Si un atacante logra inyectar una etiqueta <script> aprovechando un fallo de escape en PHP, el navegador se niega a ejecutar el código inyectado y registra una violación de la directiva.
Advertencia operativa: Una política CSP mal configurada romperá funcionalidades legítimas de tu aplicación si utilizas scripts inline o librerías servidas desde CDNs externas no autorizadas en la lista blanca. En aplicaciones en producción, la migración debe ser progresiva: comienza desplegando la cabecera en modo reporte con Content-Security-Policy-Report-Only, analiza las alertas recibidas en tus logs, ajusta los orígenes permitidos y solo entonces activa el bloqueo estricto.2. Strict-Transport-Security (HSTS)
HTTP Strict Transport Security (HSTS) obliga al navegador a comunicarse exclusivamente mediante HTTPS durante un periodo determinado, incluso si el usuario teclea http:// en la barra de direcciones o sigue un enlace no seguro:
header("Strict-Transport-Security: max-age=31536000; includeSubDomains");
El valor max-age=31536000 fija la vigencia de esta regla en un año (en segundos), mientras que includeSubDomains extiende la obligación a cualquier subdominio del proyecto. Esto anula los ataques de degradación de protocolo (SSL-stripping), interceptando la conexión antes de que salga del cliente.
Advertencia operativa: HSTS se almacena de forma persistente en la caché del navegador del visitante. Si aplicas un max-age prolongado en un dominio que posteriormente necesita servir contenido por HTTP plano en algún subdominio no preparado, los usuarios quedarán bloqueados sin posibilidad de revertir la regla en sus navegadores. La estrategia recomendada es comenzar con periodos reducidos (horas o días), verificar la validez de los certificados en toda la topología y elevar progresivamente el valor hasta un año.3. X-Content-Type-Options
Una directiva sencilla, sin efectos colaterales y de adopción universal recomendada:
header("X-Content-Type-Options: nosniff");
Históricamente, los navegadores intentaban "ayudar" al servidor inspeccionando los primeros bytes de una respuesta para deducir el tipo de archivo real si consideraban que la cabecera Content-Type era incorrecta. Esto abría una brecha de seguridad: un archivo subido por un usuario como notas.txt servido con text/plain podía ser reinterpretado como HTML o JavaScript por el navegador, ejecutando código malicioso en el contexto del usuario. Con nosniff, el navegador está obligado a respetar estrictamente el tipo declarado por el backend.
4. X-Frame-Options y frame-ancestors
El ataque de clickjacking consiste en cargar tu aplicación dentro de un iframe transparente superpuesto en una web maliciosa, engañando al usuario para que pulse sobre botones críticos (transferencias, eliminación de cuentas, cambios de permisos) sin advertirlo.
Para bloquear el enmarcado de forma tajante:
header("X-Frame-Options: DENY");
DENY: Impide que la página se cargue dentro de un marco en cualquier sitio.SAMEORIGIN: Solo permite el enmarcado si el iframe reside en el mismo origen.
En estándares modernos, la directiva frame-ancestors dentro de CSP sustituye a X-Frame-Options y ofrece mayor granularidad:
header("Content-Security-Policy: frame-ancestors 'none';");
La recomendación habitual en arquitecturas de producción es enviar ambas directivas simultáneamente: X-Frame-Options: DENY para clientes heredados y frame-ancestors 'none' en la política CSP para navegadores actuales.
5. Referrer-Policy
Cuando un usuario hace clic en un enlace externo, el navegador suele enviar la URL completa de origen en la cabecera Referer. Si tus rutas internas manejan identificadores privados, tokens temporales o parámetros sensibles de búsqueda en el query string, esa información queda registrada en los logs de servidores de terceros.
header("Referrer-Policy: strict-origin-when-cross-origin");
El valor strict-origin-when-cross-origin constituye el estándar equilibrado más seguro:
- Envía la URL completa en peticiones dentro del mismo origen (preservando analíticas internas).
- Envía únicamente el dominio (https://tuweb.com, sin rutas ni parámetros) al navegar hacia un sitio externo bajo HTTPS.
- No envía ninguna información si la navegación se degrada de HTTPS a un destino HTTP.
6. Permissions-Policy
Permissions-Policy permite restringir qué características avanzadas del dispositivo o del navegador pueden ejecutarse en el documento, evitando que scripts inyectados o iframes autorizados activen sensores de forma silenciosa:
header("Permissions-Policy: camera=(), microphone=(), geolocation=()");
La sintaxis () define una lista de orígenes vacía, desactivando por completo el acceso a la cámara, el micrófono y la geolocalización. Si tu aplicación empresarial o SaaS B2B no requiere estas capacidades de hardware, deshabilitarlas por defecto reduce la superficie de ataque expuesta.
7. Content-Type con declaración explícita de charset
La omisión del juego de caracteres en las respuestas HTML es una fuente histórica de vulnerabilidades XSS por mutación:
header("Content-Type: text/html; charset=UTF-8");
Si el servidor no especifica la codificación, ciertos motores de renderizado intentan adivinarla. Un atacante puede manipular cadenas de entrada para que pasen los filtros de escape en la codificación asumida por el backend, pero se conviertan en caracteres ejecutables válidos bajo la codificación alternativa seleccionada por el navegador. Fijar charset=UTF-8 elimina cualquier ambigüedad interpretativa.
8. Blindaje de cookies de sesión: HttpOnly, Secure y SameSite
La seguridad de las galletas de sesión es crítica para neutralizar el secuestro de sesiones (Session Hijacking). Desde PHP 7.3+, la función nativa setcookie admite un array asociativo de opciones para fijar estas directivas de manera limpia:
setcookie('session_id', $valor, [
'httponly' => true, // Impide el acceso a la cookie desde JavaScript (document.cookie)
'secure' => true, // Fuerza su transmisión exclusiva bajo túneles HTTPS
'samesite' => 'Strict', // Inhabilita el envío de la cookie en peticiones cruzadas (mitiga CSRF)
'path' => '/',
]);
HttpOnly: Si un fallo XSS permite inyectar JavaScript, el atacante no puede leer el identificador de sesión para exfiltrarlo.Secure: Garantiza que la galleta nunca viaje en texto plano sobre conexiones no cifradas.SameSite=Strict: La galleta no se envía en peticiones originadas desde sitios externos (puedes usarLaxsi requieres que el usuario mantenga la sesión activa al llegar a tu web a través de un enlace externo).
9. El Prefijo __Host-: Garantía criptográfica forzada por el navegador
Una técnica avanzada de endurecimiento (cookie hardening) consiste en utilizar el prefijo __Host- en el nombre de la cookie:
setcookie('__Host-session_id', $valor, [
'httponly' => true,
'secure' => true,
'samesite' => 'Lax',
'path' => '/',
]);
Cuando un navegador detecta el prefijo __Host-, impone restricciones estrictas a nivel de cliente antes de aceptarla:
- La conexión debe ser HTTPS obligatoriamente.
- No puede contener el atributo
Domain, lo que garantiza que la cookie solo pertenece al host exacto y no puede ser leída ni sobreescrita por subdominios comprometidos (aislamiento frente a ataques en topologías multi-tenant o subdominios compartidos). - El atributo
pathdebe ser exactamente/.
Si la respuesta no cumple estos tres requisitos simultáneamente, el navegador descarta la cookie de forma automática.
10. Supresión de dirmas de infraestructura (Information Disclosure)
Por defecto, los servidores web y los intérpretes de PHP anuncian su presencia y versiones exactas mediante cabeceras como X-Powered-By: PHP/8.x y Server: Apache/...:
// Eliminación en tiempo de ejecución
header_remove("X-Powered-By");
La solución arquitectónica correcta debe gestionarse en la configuración del entorno (php.ini):
expose_php = Off
En el servidor web (Nginx / Apache), desactiva la emisión de versiones:
- En Nginx:
server_tokens off; - En Apache:
ServerTokens ProdyServerSignature Off
Aunque ocultar la versión del stack no detiene a un atacante que utilice análisis heurístico de comportamiento, elimina la divulgación gratuita de información que facilita la identificación automatizada de vulnerabilidades específicas en escaneos masivos de reconocimiento.
Implementación centralizada: middleware vs. servidor web
Invocar llamadas dispersas a header() en diferentes controladores introduce inconsistencias y riesgos de omisión en nuevos endpoints. La aplicación de estas cabeceras debe resolverse de forma centralizada a través de dos posibles capas:
- Capa de servidor web / proxy inverso (Nginx, Caddy, Cloudflare): Es el enfoque más eficiente, ya que asegura que tanto las respuestas generadas por PHP como los archivos estáticos queden protegidos sin incurrir en consumo de ciclos en el runtime de PHP.
- Capa de middleware (PSR-15 en Frameworks como Symfony, Laravel o Laminas): Permite gobernar las políticas desde el propio ciclo de vida del framework, facilitando la inyección dinámica de nonces criptográficos en políticas CSP complejas.
# Ejemplo de endurecimiento global en Nginx (/etc/nginx/conf.d/security_headers.conf)
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 Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
Conclusión
Las cabeceras de respuesta HTTP representan uno de los mecanismos con mejor relación coste-efectividad dentro de una estrategia de Defensa en Profundidad.
Directivas como nosniff, Referrer-Policy, las restricciones en cookies y la supresión de expose_php son ajustes sin impacto negativo que deberían formar parte del estándar base de cualquier despliegue en producción. Otras, como CSP y HSTS, requieren un despliegue gradual y monitorizado, pero constituyen la barrera definitiva que evita que un fallo puntual en el código se traduzca en una brecha de seguridad grave en el cliente.
Referencias:
· OWASP Foundation. Secure Headers Project & Cross-Site Scripting (XSS) Prevention Cheat Sheet.
· MDN Web Docs. Content Security Policy (CSP), HTTP Strict Transport Security (HSTS) and Cookie Prefixes.
· IETF RFC 6797 & RFC 6265bis. HTTP Strict Transport Security (HSTS) and Cookies: HTTP State Management Mechanism.
· ITDO Blog. Innovar está bien. Mantener también.
¿Qué te ha parecido?