CONFIGURACIÓN DE ENCABEZADOS HTTP PARA MEJORAR LA SEGURIDAD EN PHP: CSP, X-FRAME-OPTIONS Y HSTS

Los encabezados HTTP de seguridad son una de las medidas más efectivas y de menor costo para endurecer una aplicación web. Cuando se configuran correctamente, mitigan ataques como XSS, clickjacking y downgrade de HTTPS.

Content-Security-Policy (CSP)

CSP es el encabezado más potente y flexible. Define de forma declarativa de dónde el navegador puede cargar recursos, como scripts, estilos, imágenes, fuentes, frames, etc. Su objetivo principal es prevenir y mitigar ataques XSS y de inyección de contenido.

Directivas más usadas

  • "default-src": valor por defecto para el resto de directivas.
  • "script-src": controla de dónde se pueden cargar y ejecutar scripts.
  • "style-src": estilos.
  • "img-src", "font-src", "connect-src", "frame-src" / "child-src", "object-src", etc.
  • "frame-ancestors": controla quién puede embeber la página, reemplaza en gran medida a "X-Frame-Options".
  • "base-uri", "form-action", "upgrade-insecure-requests".

Ejemplo práctico en PHP

// En un archivo de bootstrap o middleware, antes de cualquier salida
$csp = [
    "default-src 'self'",
    "script-src 'self' 'nonce-" . $nonce . "' https://cdn.example.com",
    "style-src 'self' 'unsafe-inline'", // idealmente eliminar 'unsafe-inline'
    "img-src 'self' data: https:",
    "font-src 'self'",
    "connect-src 'self'",
    "frame-ancestors 'self'",
    "base-uri 'self'",
    "form-action 'self'",
    "object-src 'none'",
    "upgrade-insecure-requests"
];

header("Content-Security-Policy: " . implode('; ', $csp));

X-Frame-Options

Este encabezado protege contra clickjacking, ataques de interfaz de usuario. Indica si el navegador debe permitir que la página se muestre dentro de un "iframe>", "frame" u "object".

Valores posibles

  • "DENY": nunca permitir el embebido.
  • "SAMEORIGIN": solo permitir desde el mismo origen.
  • "ALLOW-FROM uri": obsoleto y poco soportado.

Implementación en PHP

header('X-Frame-Options: DENY');
// o
header('X-Frame-Options: SAMEORIGIN');
//Nota: La directiva CSP frame-ancestors es más flexible y tiene mejor soporte. Se recomienda usar ambas durante la transición

header('X-Frame-Options: DENY');
header("Content-Security-Policy: frame-ancestors 'none'");

Strict-Transport-Security (HSTS) HSTS fuerza al navegador a comunicarse siempre por HTTPS con el dominio durante un período determinado. Evita ataques de downgrade, que intentan forzar HTTP.

Parámetros principales

  • "max-age": tiempo en segundos que el navegador debe recordar la política, recomendado: 1 año = 31536000.
  • "includeSubDomains": aplica también a todos los subdominios.
  • "preload": permite incluir el dominio en la lista de precarga de HSTS de los navegadores, requiere cumplir requisitos estrictos.

Ejemplo en PHP

// Solo enviar este encabezado cuando la conexión ya es HTTPS
if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') {
    header('Strict-Transport-Security: max-age=31536000; includeSubDomains; preload');
}
Advertencia crítica: Una vez que se envía HSTS con un "max-age" alto, los navegadores recordarán la política. Si se comete un error, como certificado inválido, subdominios sin HTTPS, se puede bloquear el acceso al sitio. Es mejor probar primero con un "max-age" bajo, por ejemplo 300, y aumentarlo progresivamente.

Implementación recomendada en una aplicación PHP

La mejor práctica es centralizar el envío de encabezados de seguridad en un solo lugar, como unmiddleware, front controller o archivo de configuración inicial:


// security-headers.php

function sendSecurityHeaders(string $nonce = ''): void
{
    // HSTS (solo en HTTPS)
    if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
        header('Strict-Transport-Security: max-age=31536000; includeSubDomains; preload');
    }

    // X-Frame-Options + CSP frame-ancestors
    header('X-Frame-Options: DENY');

    // CSP
    $csp = "default-src 'self'; "
         . "script-src 'self' 'nonce-{$nonce}'; "
         . "style-src 'self'; "
         . "img-src 'self' data: https:; "
         . "font-src 'self'; "
         . "connect-src 'self'; "
         . "frame-ancestors 'none'; "
         . "base-uri 'self'; "
         . "form-action 'self'; "
         . "object-src 'none'; "
         . "upgrade-insecure-requests";

    header("Content-Security-Policy: {$csp}");

    // Otros encabezados útiles (recomendados)
    header('X-Content-Type-Options: nosniff');
    header('Referrer-Policy: strict-origin-when-cross-origin');
    header('Permissions-Policy: geolocation=(), microphone=(), camera=()');
}
//En el inicio de la aplicación
$nonce = base64_encode(random_bytes(16));
sendSecurityHeaders($nonce);