Situación actual: PHP2Shell. Dos vulnerabilidades críticas de PHP (CVSS 9.2) permiten ejecutar código malicioso sin autenticación. Parches desde el 21/10/2026.Qué hacer ahora →

Protección CSRF en aplicaciones PHP

Cómo evitan los desarrolladores el Cross-Site Request Forgery en aplicaciones PHP: tokens para cada petición que modifique datos, comprobación del origen en la cabecera y cookies SameSite como capa adicional.

Actualizado 6 min de lecturaDesarrollo seguro

En resumen: para evitar CSRF en PHP, toda petición que modifique algo debe llevar un token secreto vinculado a la sesión, que usted comprueba en el servidor con hash_equals(). Además, compruebe las cabeceras Origin o Sec-Fetch-Site y establezca las cookies de sesión con SameSite. Las peticiones GET nunca deben modificar datos, y el formulario de inicio de sesión también necesita protección.

Un clic que nadie nota

Una empleada ha iniciado sesión en el backend de la tienda y abre en una segunda pestaña una página ajena. Esa página envía en segundo plano un formulario al backend, por ejemplo para cambiar una dirección de correo o crear una cuenta. El navegador adjunta automáticamente la cookie de sesión. Para la aplicación, la petición parece una más del propio equipo.

Eso es el Cross-Site Request Forgery, abreviado CSRF: una petición que un usuario autenticado envía sin saberlo a una aplicación en la que confía. El atacante no ve la respuesta, pero tampoco la necesita. Basta con que la acción se ejecute.

CSRF no aparece en los titulares como PHP2Shell, pero es un hallazgo frecuente en aplicaciones PHP de desarrollo propio. Frameworks como Symfony y Laravel traen una protección; las brechas aparecen allí sobre todo donde se ha desactivado para rutas concretas. Los desarrollos propios sin framework a menudo no tienen ninguna.

CSRF en PHP: qué peticiones hay que proteger

Necesitan protección todas las peticiones que modifican un estado: guardar, borrar, pedir, asignar permisos, cambiar ajustes, subir archivos, cerrar sesión. Las llamadas de lectura no necesitan token mientras de verdad solo lean.

De ahí se deduce la primera regla: ningún cambio mediante GET. Enlaces como loeschen.php?id=… son especialmente fáciles de activar desde páginas ajenas, por ejemplo con una imagen incrustada. Las acciones que modifican pertenecen a POST, PUT, PATCH o DELETE.

La segunda regla: proteger también el inicio de sesión. En el llamado CSRF de login, una víctima inicia sesión sin darse cuenta en una cuenta del atacante. Si introduce allí datos de pago o documentos, acaban en manos del atacante. Un token en el formulario de login y un nuevo ID de sesión tras un inicio de sesión correcto lo impiden.

El patrón del token sincronizador en PHP

Para aplicaciones clásicas con sesiones, OWASP recomienda el patrón del token sincronizador: el servidor genera un token aleatorio, lo guarda en la sesión y lo espera de vuelta en cada formulario que modifique datos. Es importante que el token se envíe en el formulario o en una cabecera propia, no en una cookie.

<?php
declare(strict_types=1);

function csrfToken(): string
{
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['csrf_token'];
}

function csrfPruefen(): void
{
    $gesendet = $_POST['csrf_token'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
    if (!is_string($gesendet) || !hash_equals(csrfToken(), $gesendet)) {
        http_response_code(403);
        exit('Anfrage abgelehnt.');
    }
}

if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
    csrfPruefen();
}
?>
<form method="post" action="/konto/email">
    <input type="hidden" name="csrf_token"
           value="<?= htmlspecialchars(csrfToken(), ENT_QUOTES, 'UTF-8') ?>">
    <!-- más campos -->
</form>

hash_equals() compara en tiempo constante, de modo que el tiempo de respuesta no revela nada sobre el valor correcto. Tras el inicio de sesión, debería renovar el ID de sesión con session_regenerate_id(true) y generar un token nuevo. Por qué random_bytes() es la fuente correcta y qué funciones no son adecuadas lo explica Números aleatorios y tokens seguros en PHP.

Un token por sesión basta para la mayoría de las aplicaciones. Los tokens por formulario o por petición aumentan algo la protección, pero provocan problemas con varias pestañas abiertas y con el botón de retroceso. Más importante que la frecuencia del cambio es que el token nunca aparezca en URL, donde puede acabar en logs, en el historial del navegador y en las indicaciones de referer a sitios ajenos.

Para aplicaciones sin estado y sin sesión, OWASP cita como alternativa la cookie de doble envío firmada: un token vinculado a la sesión mediante HMAC. La variante simple, sin firmar, expresamente no se recomienda.

Segunda capa: Origin, Sec-Fetch-Site y SameSite

Los navegadores modernos envían en las peticiones información sobre su origen. La cabecera Origin indica la página que llama; Sec-Fetch-Site dice si la petición procede de la misma página (same-origin), de una relacionada (same-site) o de una ajena (cross-site). De ahí puede construirse una sencilla comprobación adicional:

<?php
$erlaubteHerkunft = 'https://www.example.de';
$fetchSite = $_SERVER['HTTP_SEC_FETCH_SITE'] ?? null;
$origin    = $_SERVER['HTTP_ORIGIN'] ?? null;

$verdaechtig = ($fetchSite !== null && $fetchSite !== 'same-origin')
    || ($fetchSite === null && $origin !== null && $origin !== $erlaubteHerkunft);

if ($_SERVER['REQUEST_METHOD'] !== 'GET' && $verdaechtig) {
    http_response_code(403);
    exit;
}

OWASP subraya dos puntos: el origen debe compararse por completo, para que no se cuelen dominios de sonido parecido, y una cabecera ausente o el valor null no deben valer como origen propio. Como los navegadores antiguos no siempre envían las cabeceras, el token sigue siendo la defensa principal.

La tercera capa son las cookies de sesión con SameSite. PHP admite session.cookie_samesite desde la versión 7.3; el valor predeterminado está vacío. Establezca como mínimo Lax; para backends puros suele ser posible Strict. Los demás ajustes de cookies los describe la guía Proteger las sesiones en PHP.

Si usa un CMS, normalmente trae sus propias herramientas. WordPress, por ejemplo, trabaja con los llamados nonces, que los plugins integran mediante funciones como wp_nonce_field() y check_admin_referer(). En la práctica, las brechas aparecen sobre todo en extensiones que olvidan esas comprobaciones en acciones concretas. Revise por ello especialmente los plugins escritos a medida o poco extendidos.

En interfaces que los navegadores usan desde otros dominios, decide además la configuración CORS. Si el servidor devuelve cualquier origen solicitante como permitido y a la vez admite cookies, anula él mismo la protección del navegador. OWASP recomienda abrir CORS solo para orígenes expresamente de confianza; una autorización general de todos los subdominios se convierte en problema en cuanto uno de ellos cae en manos ajenas.

Lista de comprobación para aplicaciones existentes

En proyectos que han ido creciendo, ayuda una revisión sistemática:

  1. Registrar todas las rutas y scripts que modifican datos, también los endpoints Ajax y las subidas de archivos.
  2. Comprobar si para ello se usa GET y pasar a POST o a métodos adecuados.
  3. Demostrar para cada ruta que modifica datos que se comprueba un token; buscar de forma dirigida las excepciones en el framework.
  4. Probar por separado inicio de sesión, cierre de sesión y cambio de contraseña.
  5. Añadir session.cookie_samesite y la comprobación de origen.
  6. Corregir las vulnerabilidades XSS, porque el código inyectado puede leer cualquier token. El procedimiento lo describe Evitar el cross-site scripting en PHP.
Importante

Una protección CSRF que solo se comprueba en el frontend con JavaScript es ineficaz. La comprobación debe realizarse en el servidor antes de ejecutar la acción.

¿Cómo ayuda PHP SOS en concreto?

  • Registramos todas las rutas de su aplicación PHP que modifican datos y comprobamos dónde falta un token o se ha desactivado.
  • Incorporamos un punto de comprobación central, en lugar de repartir las comprobaciones de token entre muchos scripts.
  • Añadimos la comprobación de origen y los ajustes SameSite sin perturbar los flujos de pago ni de inicio de sesión.
  • Tras la migración, volvemos a probar y documentamos los resultados.

La revisión completa de su aplicación la describe el servicio Auditoría de seguridad y prueba de penetración.

Preguntas frecuentes

¿Basta SameSite=Lax como protección CSRF?

No. Según OWASP, las cookies SameSite son una capa adicional, no un sustituto. Lax sigue enviando cookies en las visitas normales a páginas mediante GET, y los ataques desde subdominios del mismo sitio web no quedan cubiertos.

¿Necesitan las API JSON un token CSRF?

Si la API autentica con cookies, sí, o una comprobación equivalente. Una alternativa son cabeceras de petición propias, que los navegadores permiten en sitios ajenos solo tras una comprobación previa CORS. Entonces, la autorización CORS solo debe incluir orígenes expresamente de confianza.

¿También hay que proteger el formulario de inicio de sesión?

Sí. En el CSRF de login, una víctima inicia sesión sin darse cuenta en una cuenta ajena e introduce allí sus propios datos. Un token en el formulario de login y una sesión nueva tras el inicio de sesión lo impiden.

¿Protege la protección CSRF también frente a XSS?

Al revés: una vulnerabilidad XSS anula cualquier protección CSRF, porque el código inyectado puede leer y enviar tokens válidos en nombre de la página. Ambas vulnerabilidades deben corregirse por separado.

Fuentes

Contacto inmediato

Cada hora cuenta. Escríbanos ahora.

Describa brevemente el sistema, la versión de PHP y lo que ha observado. Nos pondremos en contacto, aclararemos la situación y trabajaremos en remoto hasta que el incidente quede cerrado y documentado.

Ayuda inmediata