Cuando hablamos de CSRF vimos que la sesión de PHP guarda el token que nos protege. Pero la propia sesión también puede ser un punto débil si no la configuras bien: es donde vive el «he iniciado sesión como este usuario», así que si alguien consigue manipularla, puede acabar suplantando a otra persona sin necesidad de conocer su contraseña.
En este artículo vemos cómo configurar sesiones seguras en PHP: cookies bien configuradas, regeneración del ID tras el login, y qué evitar guardar dentro de la sesión.
¿Qué puede salir mal con las sesiones?
Dos ataques clásicos aprovechan sesiones mal configuradas:
- Session fixation: el atacante fuerza a la víctima a usar un ID de sesión que él ya conoce (por ejemplo, en un enlace), y cuando la víctima inicia sesión, el atacante usa ese mismo ID para entrar como ella.
- Session hijacking: el atacante roba la cookie de sesión ya activa (por ejemplo, interceptando tráfico sin HTTPS, o mediante XSS) y la usa directamente, sin necesidad de contraseña.
Cookies de sesión seguras
Antes de llamar a session_start(), configura los parámetros de la cookie:
session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax'
]);
session_start();Lenguaje del código: PHP (php)Cada opción cierra una puerta distinta:
httponly: impide que JavaScript lea la cookie de sesión, aunque exista una vulnerabilidad XSS en la página.secure: la cookie solo se envía por HTTPS, nunca en texto plano por HTTP.samesite: evita que la cookie se envíe en peticiones que vienen de otros dominios (la misma defensa que vimos contra CSRF).
Regenerar el ID de sesión tras el login
Esta es la defensa directa contra session fixation. Cada vez que un usuario inicia sesión (o sube de privilegios, por ejemplo al confirmar una contraseña de administrador), genera un ID de sesión nuevo:
if (password_verify($password, $usuario['password_hash'])) {
session_regenerate_id(true); // true = borra la sesión antigua del servidor
$_SESSION['user_id'] = $usuario['id'];
$_SESSION['logged_in_at'] = time();
}Lenguaje del código: PHP (php)El parámetro true es importante: sin él, la sesión antigua se queda abierta en el servidor y sigue siendo válida, lo que anula buena parte de la protección.
No guardes más de lo necesario en la sesión
La sesión se guarda en el servidor (normalmente en disco, en /tmp por defecto), pero eso no significa que debas volcar ahí cualquier cosa. Evita guardar contraseñas, tokens de tarjetas o datos sensibles innecesarios: si guardas solo el user_id, y necesitas el resto de datos del usuario, consulta la base de datos cuando haga falta.
// Mal: guardar el objeto completo, incluyendo datos sensibles
$_SESSION['usuario'] = $usuarioCompleto;
// Bien: guardar solo el identificador
$_SESSION['user_id'] = $usuario['id'];Lenguaje del código: PHP (php)Cerrar sesión correctamente
Un logout incompleto deja rastros que se pueden reutilizar. La forma correcta de cerrar sesión en PHP borra tanto los datos como la cookie del navegador:
$_SESSION = [];
if (ini_get('session.use_cookies')) {
$params = session_get_cookie_params();
setcookie(
session_name(), '', time() - 42000,
$params['path'], $params['domain'],
$params['secure'], $params['httponly']
);
}
session_destroy();Lenguaje del código: PHP (php)Tiempo de vida de la sesión
Por defecto, PHP mantiene una sesión inactiva bastante tiempo antes de limpiarla del servidor. Para una aplicación sensible (banca, panel de administración) conviene acortar ese margen:
ini_set('session.gc_maxlifetime', 1800); // 30 minutos de inactividad
session_set_cookie_params(['lifetime' => 0]); // 0 = expira al cerrar el navegadorLenguaje del código: PHP (php)gc_maxlifetime controla cuándo el servidor puede borrar una sesión inactiva; el lifetime de la cookie controla cuánto tiempo la recuerda el navegador. Para sesiones realmente cortas necesitas ajustar ambas.
Cómo comprobarlo
El OWASP Session Management Cheat Sheet es la referencia más completa si quieres profundizar en cada uno de estos puntos con más detalle del que cabe aquí.
Buenas prácticas
- Configura
httponly,secureysamesiteantes desession_start(). - Llama a
session_regenerate_id(true)justo después de un login válido. - Guarda solo el identificador del usuario en sesión, nunca datos sensibles completos.
- Implementa un logout que borre la cookie y destruya la sesión en el servidor, no solo
unset()de variables. - Sirve todo tu sitio por HTTPS: sin eso, la cookie
secureno se envía nunca. - Combina esto con las cabeceras de seguridad HTTP que vimos en el post anterior de la serie.