En resumen: en la seguridad de sesiones PHP cuentan cuatro cosas: ajustes seguros de cookies, un ID de sesión nuevo tras cada inicio de sesión, lugares de almacenamiento separados y protegidos, y duraciones cortas. Por PHP2Shell, además, debería estar establecido en el servidor session.upload_progress.enabled=0 mientras no haya motivo para usar esa función.
Por qué las sesiones son un objetivo atractivo
Una sesión recuerda que un usuario ha iniciado sesión. Técnicamente, el navegador recibe para ello un ID de sesión aleatorio en una cookie, y el servidor guarda los datos correspondientes. Quien conoce ese ID es, para la aplicación, el mismo usuario, sin contraseña y sin segundo factor.
Para los responsables, esto significa que una gestión de sesiones débil puede anular la protección del inicio de sesión, aunque las contraseñas y la autenticación de dos factores estén bien aplicadas. Es especialmente crítico en las áreas de administración, las cuentas de clientes y los portales con datos personales.
Los riesgos típicos son la captura del ID de sesión (secuestro de sesión), imponer un ID conocido antes del inicio de sesión (fijación de sesión) y las sesiones demasiado largas en equipos compartidos.
Ajustes básicos seguros en php.ini
Los valores más importantes se pueden establecer de forma centralizada en php.ini o, por sitio web, en el pool de FPM. En el pool, use php_admin_value o php_admin_flag para que la aplicación no pueda sobrescribir los valores.
; Refuerzo básico de sesiones
session.use_strict_mode = 1
session.use_only_cookies = 1
session.use_trans_sid = 0
session.cookie_httponly = 1
session.cookie_secure = 1
session.cookie_samesite = "Lax"
session.cookie_lifetime = 0
session.gc_maxlifetime = 1800
session.name = "APPSESSID"
; Aviso de PHP2Shell de php.net, después reiniciar todos los workers
session.upload_progress.enabled = 0
use_strict_mode hace que PHP no acepte ID desconocidos impuestos por el navegador. cookie_httponly impide que JavaScript pueda leer el ID, lo que limita las consecuencias del cross-site scripting. cookie_secure envía la cookie solo por HTTPS. SameSite=Lax dificulta las peticiones entre sitios con una sesión válida; para áreas exclusivamente de administración puede convenir Strict.
La duración de 1.800 segundos es un valor de ejemplo. Establézcala según la necesidad de protección: más corta para la administración y los datos de clientes, más larga solo donde sea necesario por razones funcionales.
Tenga en cuenta que gc_maxlifetime solo determina a partir de cuándo se pueden borrar los datos antiguos de sesión. En algunas distribuciones no limpia PHP mismo, sino una tarea periódica del sistema, los archivos de sesión. Compruebe entonces qué valor usa esa tarea, para que su ajuste surta efecto realmente. Para cerrar la sesión por inactividad, no se fíe además solo de la función de limpieza, sino compruebe en la propia aplicación el momento de la última acción, como muestra el ejemplo siguiente.
Renovar el ID de sesión tras el inicio de sesión
La regla más importante en el código es generar de nuevo el ID de sesión en cada cambio de autorización, es decir, al iniciar sesión, al pasar a un modo de administración y al cerrar sesión. El ejemplo siguiente muestra un patrón básico limpio con password_verify():
<?php
declare(strict_types=1);
session_start();
function anmelden(PDO $db, string $email, string $passwort): bool
{
$stmt = $db->prepare('SELECT id, passwort_hash FROM benutzer WHERE email = :email');
$stmt->execute(['email' => $email]);
$nutzer = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$nutzer || !password_verify($passwort, $nutzer['passwort_hash'])) {
return false; // el mismo mensaje para dirección errónea y contraseña errónea
}
session_regenerate_id(true); // ID nuevo, descartar el antiguo
$_SESSION['benutzer_id'] = (int) $nutzer['id'];
$_SESSION['letzte_aktion'] = time();
return true;
}
function pruefeLeerlauf(int $sekunden = 900): void
{
if (isset($_SESSION['letzte_aktion']) && time() - $_SESSION['letzte_aktion'] > $sekunden) {
abmelden();
return;
}
$_SESSION['letzte_aktion'] = time();
}
function abmelden(): void
{
$_SESSION = [];
$p = session_get_cookie_params();
setcookie(session_name(), '', [
'expires' => time() - 3600,
'path' => $p['path'],
'domain' => $p['domain'],
'secure' => $p['secure'],
'httponly' => $p['httponly'],
'samesite' => $p['samesite'],
]);
session_destroy();
}
Como complemento, proteja los formularios que modifican algo con un token CSRF. Se genera con bin2hex(random_bytes(32)), se guarda en la sesión y se compara con hash_equals() al enviar.
Lugares de almacenamiento y separación de clientes
Por defecto, PHP guarda las sesiones como archivos en un directorio compartido. En un servidor con varios sitios web, eso puede significar que un sitio comprometido pueda leer las sesiones de otro. Por eso, cada sitio web debe tener su propio pool con usuario propio y ruta de sesiones propia.
; en el pool de FPM del sitio web correspondiente
php_admin_value[session.save_path] = /var/lib/php/sessions/kunde-a
El directorio debería pertenecer al usuario del pool y tener los permisos 0700. Cómo aislar además los sitios web entre sí lo describe la guía open_basedir y aislamiento por sitio web. Si las sesiones se guardan en Redis o en una base de datos, rigen los mismos principios: acceso propio para cada aplicación, sin acceso abierto a la red.
Seguridad de sesiones PHP y PHP2Shell
Las vulnerabilidades de PHP2Shell CVE-2026-109618 y CVE-2026-109619 residen en el propio PHP. Como una de las medidas transitorias, php.net indica establecer session.upload_progress.enabled=0 en php.ini, en el pool de FPM o en la configuración del servidor y reiniciar después todos los workers. Expresamente no mediante ini_set() ni un .user.ini.
El 23/10/2026 llegó un aviso sobre CVE-2026-115560. Una posible ejecución de código solo existe allí con una combinación poco habitual de serialización de sesiones, cambio mediante ini_set() antes de session_start() y progreso de subida activo. La mitigación es el mismo ajuste, hasta la próxima versión regular de corrección de errores.
Este ajuste es una medida transitoria y no una protección completa. Lo decisivo sigue siendo la actualización a una versión corregida de PHP y el posterior reinicio de todos los workers de PHP.
Tras el cambio, compruebe qué valor rige realmente. La línea de comandos suele mostrar una configuración distinta a la del servidor web; es concluyente una página de comprobación protegida, que después se vuelve a borrar, o la propia configuración del pool.
Errores típicos con las sesiones en aplicaciones PHP
Muchas vulnerabilidades no surgen por ajustes ausentes, sino por la forma en que las aplicaciones usan las sesiones. Conviene preguntar de forma específica por estos patrones en el código propio y a los proveedores de servicios:
- Demasiado contenido en la sesión: las contraseñas, los datos de pago completos o los registros grandes no pertenecen a la sesión. Guarde solo lo necesario para la asignación, como el ID de usuario y los roles.
- Permisos solo desde la sesión: quién es administrador debería comprobarse contra la base de datos en las acciones críticas. De lo contrario, los permisos retirados siguen vigentes hasta que caduca la sesión.
- ID de sesión en la dirección: los ID en las URL acaban en registros, historiales del navegador e indicaciones de referer.
use_only_cookiesyuse_trans_sid = 0lo evitan. - Sin fin de sesión: si falta un tiempo de espera por inactividad, un administrador permanece conectado indefinidamente en un equipo compartido.
- HTTPS mal detectado tras un proxy: si el cifrado termina en el balanceador de carga, la aplicación debe saberlo. De lo contrario, puede establecer cookies sin el flag
Secure.
Tras un incidente de seguridad también hay que descartar todas las sesiones existentes. Lo más sencillo es borrar el contenido del directorio de sesiones o cambiar el nombre de la sesión. Todos los usuarios deben entonces volver a iniciar sesión, y las sesiones robadas dejan de valer.
¿Cómo ayuda PHP SOS en concreto?
- Revisamos los ajustes de sesión, los flags de cookie y los lugares de almacenamiento en todos los sitios web de un servidor y documentamos los valores efectivamente vigentes.
- Aplicamos las medidas transitorias de PHP2Shell por pool, reiniciamos los workers de forma controlada y acreditamos el resultado.
- Separamos los sitios web con pools y rutas de sesión propios en el marco del refuerzo del servidor.
- En los desarrollos propios, revisamos en el código el inicio de sesión, el cierre de sesión y el cambio de sesión y recomendamos cambios concretos.
Preguntas frecuentes
¿Qué es el secuestro de sesión?
En el secuestro de sesión, un atacante se hace con la sesión de un usuario conectado obteniendo su ID de sesión. Con el ID, para la aplicación es ese usuario, sin conocer la contraseña. Unos flags de cookie seguros y duraciones cortas lo dificultan notablemente.
¿Por qué hay que generar de nuevo el ID de sesión tras el inicio de sesión?
Si el ID es el mismo antes y después del inicio de sesión, un ID conocido de antemano o impuesto podría seguir siendo válido tras la autenticación. session_regenerate_id(true) genera un ID nuevo y descarta el antiguo.
¿Puedo desactivar session.upload_progress.enabled con ini_set()?
No, según php.net eso no basta. El ajuste debe ir en php.ini, en el pool de FPM o en la configuración del servidor, no en ini_set() ni en un .user.ini. Después hay que reiniciar todos los workers de PHP.
¿Dónde deben guardarse los archivos de sesión?
En un directorio fuera del webroot que solo pueda leer el usuario del pool de PHP correspondiente. En servidores con varios sitios web, cada sitio debería tener su propio directorio de sesiones.