TigerZF
🌐Español

65.4. Gestión global de sesiones

El comportamiento por defecto de las sesiones puede modificarse utilizando los métodos estáticos de Zend_Session. Toda la gestión y manipulación global de sesiones ocurre usando Zend_Session, incluyendo la configuración de las opciones habituales proporcionadas por ext/session, usando Zend_Session::setOptions(). Por ejemplo, no asegurar el uso de un save_path seguro o un nombre de cookie único por parte de ext/session usando Zend_Session::setOptions() puede resultar en problemas de seguridad.

65.4.1. Opciones de configuración

Cuando se solicita el primer espacio de nombres de sesión, Zend_Session iniciará automáticamente la sesión PHP, a menos que ya se haya iniciado con Zend_Session::start(). La sesión PHP subyacente utilizará los valores por defecto de Zend_Session, a menos que sean modificados primero mediante Zend_Session::setOptions().

Para establecer una opción de configuración de sesión, incluya el nombre base (la parte del nombre después de "session.") como clave de un array pasado a Zend_Session::setOptions(). El valor correspondiente en el array se utiliza para establecer el valor de la opción de sesión. Si el desarrollador no establece ninguna opción, Zend_Session utilizará primero las opciones recomendadas por defecto, y luego los valores por defecto de php.ini. Los comentarios de la comunidad sobre las mejores prácticas para estas opciones deben enviarse a fw-auth@lists.zend.com.

Ejemplo 65.16. Uso de Zend_Config para configurar Zend_Session

Para configurar este componente usando Zend_Config_Ini, primero añada las opciones de configuración al archivo INI:

; Accept defaults for production
[production]
; bug_compat_42
; bug_compat_warn
; cache_expire
; cache_limiter
; cookie_domain
; cookie_lifetime
; cookie_path
; cookie_secure
; entropy_file
; entropy_length
; gc_divisor
; gc_maxlifetime
; gc_probability
; hash_bits_per_character
; hash_function
; name should be unique for each PHP application sharing the same
; domain name
name = UNIQUE_NAME
; referer_check
; save_handler
; save_path
; serialize_handler
; use_cookies
; use_only_cookies
; use_trans_sid

; remember_me_seconds = <integer seconds>
; strict = on|off

; Development inherits configuration from production, but overrides
; several values
[development : production]
; Don't forget to create this directory and make it rwx (readable and
; modifiable) by PHP.
save_path = /home/myaccount/zend_sessions/myapp
use_only_cookies = on
; When persisting session id cookies, request a TTL of 10 days
remember_me_seconds = 864000

A continuación, cargue el archivo de configuración y pase su representación en array a Zend_Session::setOptions():

$config = new Zend_Config_Ini('myapp.ini', 'development');

Zend_Session::setOptions($config->toArray());

La mayoría de las opciones mostradas arriba no necesitan más explicación que la que se encuentra en la documentación estándar de PHP, pero aquellas de especial interés se detallan a continuación.

  • booleano strict - deshabilita el inicio automático de Zend_Session al usar new Zend_Session_Namespace().

  • entero remember_me_seconds - cuánto tiempo debe persistir la cookie del id de sesión, después de que el user agent haya finalizado (por ejemplo, se cierre la aplicación del navegador).

  • cadena save_path - El valor correcto depende del sistema, y debe ser proporcionado por el desarrollador usando una ruta absoluta a un directorio legible y con permisos de escritura por el proceso PHP. Si no se proporciona una ruta con permisos de escritura, entonces Zend_Session lanzará una excepción al iniciarse (es decir, cuando se llama a start()).

    [Note] Riesgo de seguridad

    Si la ruta es legible por otras aplicaciones, entonces el secuestro de sesión podría ser posible. Si la ruta tiene permisos de escritura por otras aplicaciones, entonces el envenenamiento de sesión podría ser posible. Si esta ruta se comparte con otros usuarios u otras aplicaciones PHP, pueden ocurrir diversos problemas de seguridad, incluyendo el robo del contenido de la sesión, el secuestro de sesiones y la colisión de la recolección de basura (por ejemplo, la aplicación de otro usuario podría hacer que PHP elimine los archivos de sesión de su aplicación).

    Por ejemplo, un atacante puede visitar el sitio web de la víctima para obtener una cookie de sesión. Luego, edita la ruta de la cookie hacia su propio dominio en el mismo servidor, antes de visitar su propio sitio web para ejecutar var_dump($_SESSION). Armado con conocimiento detallado del uso que la víctima hace de los datos en sus sesiones, el atacante puede entonces modificar el estado de la sesión (envenenando la sesión), alterar la ruta de la cookie de vuelta hacia el sitio web de la víctima, y luego realizar solicitudes desde el sitio web de la víctima usando la sesión envenenada. Incluso si dos aplicaciones en el mismo servidor no tienen acceso de lectura/escritura al save_path de la otra aplicación, si el save_path es adivinable, y el atacante tiene control sobre uno de esos dos sitios web, el atacante podría alterar el save_path de su sitio web para usar el de la otra, y así lograr el envenenamiento de sesión, bajo algunas configuraciones comunes de PHP. Por lo tanto, el valor de save_path no debe hacerse público y debe alterarse hacia una ubicación segura y única para cada aplicación.

  • cadena name - El valor correcto depende del sistema y debe ser proporcionado por el desarrollador usando un valor único para la aplicación.

    [Note] Riesgo de seguridad

    Si el valor de php.ini para session.name es el mismo (por ejemplo, el valor por defecto "PHPSESSID"), y hay dos o más aplicaciones PHP accesibles a través del mismo nombre de dominio entonces compartirán los mismos datos de sesión para los visitantes de ambos sitios web. Además, podría producirse una posible corrupción de los datos de sesión.

  • booleano use_only_cookies - Para evitar introducir riesgos de seguridad adicionales, no altere el valor por defecto de esta opción.

    [Note] Riesgo de seguridad

    Si esta configuración no está habilitada, un atacante puede fijar fácilmente el id de sesión de la víctima, usando enlaces en el sitio web del atacante, como http://www.example.com/index.php?PHPSESSID=fixed_session_id. La fijación funciona si la víctima aún no tiene una cookie de id de sesión para example.com. Una vez que la víctima usa un id de sesión conocido, el atacante puede entonces intentar secuestrar la sesión haciéndose pasar por la víctima, y emulando el user agent de la víctima.

65.4.2. Error: cabeceras ya enviadas

Si ve el mensaje de error, "Cannot modify header information - headers already sent", o, "You must call ... before any output has been sent to the browser; output started in ...", entonces examine cuidadosamente la causa inmediata (función o método) asociada con el mensaje. Cualquier acción que requiera el envío de cabeceras HTTP, como el envío de una cookie, debe realizarse antes de enviar la salida normal (salida sin buffer), excepto cuando se utiliza el buffering de salida de PHP.

  • Usar el buffering de salida suele ser suficiente para prevenir este problema, y puede ayudar a mejorar el rendimiento. Por ejemplo, en php.ini, "output_buffering = 65535" habilita el buffering de salida con un buffer de 64K. Aunque el buffering de salida puede ser una buena táctica en servidores de producción para aumentar el rendimiento, confiar únicamente en el buffering para resolver el problema de "headers already sent" no es suficiente. La aplicación no debe exceder el tamaño del buffer, o el problema ocurrirá siempre que la salida enviada (antes de las cabeceras HTTP) exceda el tamaño del buffer.

  • Si un método de Zend_Session está involucrado en causar el mensaje de error, examine el método cuidadosamente, y asegúrese de que su uso realmente sea necesario en la aplicación. Por ejemplo, el uso por defecto de destroy() también envía una cabecera HTTP para expirar la cookie de sesión del lado del cliente. Si esto no es necesario, entonces use destroy(false), ya que las instrucciones para establecer cookies se envían con cabeceras HTTP.

  • Alternativamente, intente reorganizar la lógica de la aplicación de modo que todas las acciones que manipulan cabeceras se realicen antes de enviar cualquier salida.

  • Elimine cualquier etiqueta de cierre "?>", si aparecen al final de un archivo fuente PHP. No son necesarias, y los saltos de línea y otros espacios en blanco casi invisibles después de la etiqueta de cierre pueden desencadenar el envío de salida al cliente.

65.4.3. Identificadores de sesión

Introducción: la mejor práctica en relación con el uso de sesiones con Zend Framework recomienda usar una cookie de navegador (es decir, una cookie normal almacenada en su navegador web), en lugar de incrustar un identificador de sesión único en las URLs como medio para rastrear usuarios individuales. Por defecto, este componente usa únicamente cookies para mantener los identificadores de sesión. El valor de la cookie es el identificador único de la sesión de su navegador. El ext/session de PHP usa este identificador para mantener una relación única uno a uno entre los visitantes del sitio web y el almacenamiento de datos de sesión persistente único para cada visitante. Zend_Session* envuelve este mecanismo de almacenamiento ($_SESSION) con una interfaz orientada a objetos. Desafortunadamente, si un atacante obtiene acceso al valor de la cookie (el id de sesión), un atacante podría ser capaz de secuestrar la sesión de un visitante. Este problema no es exclusivo de PHP, ni de Zend Framework. El método regenerateId() permite a una aplicación cambiar el id de sesión (almacenado en la cookie del visitante) a un valor nuevo, aleatorio e impredecible. Nota: aunque no es lo mismo, para facilitar la lectura de esta sección, usamos los términos "user agent" y "navegador web" indistintamente.

¿Por qué?: Si un atacante obtiene un identificador de sesión válido, un atacante podría ser capaz de hacerse pasar por un usuario válido (la víctima), y luego obtener acceso a información confidencial o manipular de otro modo los datos de la víctima gestionados por su aplicación. Cambiar los ids de sesión ayuda a proteger contra el secuestro de sesión. Si el id de sesión se cambia, y un atacante no conoce el nuevo valor, el atacante no puede usar el nuevo id de sesión en sus intentos de secuestrar la sesión del visitante. Incluso si un atacante obtiene acceso a un id de sesión antiguo, regenerateId() también mueve los datos de sesión del "handle" del id de sesión antiguo al nuevo, de modo que no quedan datos accesibles a través del id de sesión antiguo.

Cuándo usar regenerateId(): añadir Zend_Session::regenerateId() a su bootstrap de Zend Framework produce una de las formas más seguras de regenerar los ids de sesión en las cookies del user agent. Si no hay lógica condicional para determinar cuándo regenerar el id de sesión, entonces no hay fallos en esa lógica. Aunque regenerar en cada solicitud previene varias posibles vías de ataque, no todo el mundo quiere el pequeño costo asociado en rendimiento y ancho de banda. Por lo tanto, las aplicaciones comúnmente intentan determinar dinámicamente situaciones de mayor riesgo, y solo regeneran los ids de sesión en esas situaciones. Cada vez que se "escalan" los privilegios de la sesión de un visitante del sitio web (por ejemplo, un visitante vuelve a autenticar su identidad antes de editar su "perfil" personal), o cada vez que ocurre un cambio "sensible" de seguridad en un parámetro de sesión, considere usar regenerateId() para crear un nuevo id de sesión. Si llama a la función rememberMe(), entonces no use regenerateId(), ya que la primera llama a la segunda. Si un usuario ha iniciado sesión exitosamente en su sitio web, use rememberMe() en lugar de regenerateId().

65.4.3.1. Secuestro y fijación de sesión

Evitar las vulnerabilidades de cross-site scripting (XSS) ayuda a prevenir el secuestro de sesión. Según las estadísticas de Secunia los problemas de XSS ocurren con frecuencia, independientemente de los lenguajes usados para crear aplicaciones web. En lugar de esperar no tener nunca un problema de XSS con una aplicación, planifique para ello siguiendo las mejores prácticas para ayudar a minimizar el daño, si ocurre. Con XSS, un atacante no necesita acceso directo al tráfico de red de la víctima. Si la víctima ya tiene una cookie de sesión, un XSS con Javascript podría permitir a un atacante leer la cookie y robar la sesión. Para víctimas sin cookies de sesión, usando XSS para inyectar Javascript, un atacante podría crear una cookie de id de sesión en el navegador de la víctima con un valor conocido, y luego establecer una cookie idéntica en el sistema del atacante, con el fin de secuestrar la sesión de la víctima. Si la víctima visitó un sitio web del atacante, entonces el atacante también puede emular la mayoría de otras características identificables del user agent de la víctima. Si su sitio web tiene una vulnerabilidad de XSS, el atacante podría insertar un Javascript de AJAX que "visite" en secreto el sitio web del atacante, de modo que el atacante conozca las características del navegador de la víctima y se dé cuenta de una sesión comprometida en el sitio web de la víctima. Sin embargo, el atacante no puede alterar arbitrariamente el estado del lado del servidor de las sesiones PHP, siempre que el desarrollador haya establecido correctamente el valor de la opción save_path.

Por sí solo, llamar a Zend_Session::regenerateId() cuando se usa la sesión del usuario por primera vez, no previene los ataques de fijación de sesión, a menos que pueda distinguir entre una sesión originada por un atacante que emula a la víctima. A primera vista, esto podría parecer contradictorio con la afirmación anterior, hasta que consideramos a un atacante que primero inicia una sesión real en su sitio web. La sesión se "usa por primera vez" por el atacante, quien luego conoce el resultado de la inicialización (regenerateId()). El atacante entonces usa el nuevo id de sesión en combinación con una vulnerabilidad de XSS, o inyecta el id de sesión mediante un enlace en el sitio web del atacante (funciona si use_only_cookies = off).

Si puede distinguir entre un atacante y una víctima que usan el mismo id de sesión, entonces el secuestro de sesión puede tratarse directamente. Sin embargo, tales distinciones generalmente implican algún tipo de compromiso de usabilidad, porque los métodos de distinción suelen ser imprecisos. Por ejemplo, si se recibe una solicitud desde una IP en un país diferente al de la IP de la solicitud cuando se creó la sesión, entonces la nueva solicitud probablemente pertenece a un atacante. Bajo las siguientes condiciones, podría no haber ninguna forma de que una aplicación web distinga entre una víctima y un atacante:

  • el atacante primero inicia una sesión en su sitio web para obtener un id de sesión válido

  • el atacante usa una vulnerabilidad de XSS en su sitio web para crear una cookie en el navegador de la víctima con el mismo id de sesión válido (es decir, fijación de sesión)

  • tanto la víctima como el atacante se originan desde la misma granja de proxies (por ejemplo, ambos están detrás del mismo firewall en una gran empresa, como AOL)

El código de ejemplo a continuación hace mucho más difícil que un atacante conozca el id de sesión actual de la víctima, a menos que el atacante ya haya realizado los dos primeros pasos anteriores.

Ejemplo 65.17. Fijación de sesión

$defaultNamespace = new Zend_Session_Namespace();

if (!isset($defaultNamespace->initialized)) {
    Zend_Session::regenerateId();
    $defaultNamespace->initialized = true;
}

65.4.4. rememberMe(integer $seconds)

Normalmente, las sesiones terminan cuando el user agent finaliza, como cuando un usuario final cierra un programa de navegador web. Sin embargo, su aplicación puede proporcionar la capacidad de extender las sesiones de usuario más allá del tiempo de vida del programa cliente mediante el uso de cookies persistentes. Use Zend_Session::rememberMe() antes de que se inicie una sesión para controlar el tiempo antes de que expire una cookie de sesión persistida. Si no especifica un número de segundos, entonces el tiempo de vida de la cookie de sesión por defecto es remember_me_seconds, que puede establecerse usando Zend_Session::setOptions(). Para ayudar a frustrar la fijación/secuestro de sesión, use esta función cuando un usuario se autentique exitosamente con su aplicación (por ejemplo, desde un formulario de "login").

65.4.5. forgetMe()

Esta función complementa a rememberMe() escribiendo una cookie de sesión que tiene un tiempo de vida que termina cuando el user agent finaliza.

65.4.6. sessionExists()

Use este método para determinar si ya existe una sesión para el user agent/solicitud actual. Puede usarse antes de iniciar una sesión, e independientemente de todos los demás métodos de Zend_Session y Zend_Session_Namespace.

65.4.7. destroy(bool $remove_cookie = true, bool $readonly = true)

Zend_Session::destroy() destruye todos los datos persistentes asociados con la sesión actual. Sin embargo, ninguna variable en PHP se ve afectada, por lo que sus sesiones con espacio de nombres (instancias de Zend_Session_Namespace) siguen siendo legibles. Para completar un "logout", establezca el parámetro opcional como TRUE (el valor por defecto) para también eliminar la cookie de id de sesión del user agent. El parámetro opcional $readonly elimina la capacidad de crear nuevas instancias de Zend_Session_Namespace y de que los métodos de Zend_Session escriban en el almacén de datos de sesión.

Si ve el mensaje de error, "Cannot modify header information - headers already sent", entonces evite usar TRUE como valor del primer argumento (solicitando la eliminación de la cookie de sesión), o vea esta sección. Por lo tanto, Zend_Session::destroy(true) debe ser llamado antes de que PHP haya enviado cabeceras HTTP, o el buffering de salida debe estar habilitado. Además, la salida total enviada no debe exceder el tamaño de buffer establecido, con el fin de evitar que se dispare el envío de la salida antes de la llamada a destroy().

[Note] Lanza

Por defecto, $readonly está habilitado y cualquier acción posterior que implique escribir en el almacén de datos de sesión lanzará una excepción.

65.4.8. stop()

Este método no hace absolutamente nada más que alternar un indicador en Zend_Session para evitar más escrituras en el almacén de datos de sesión. Estamos solicitando específicamente comentarios sobre esta característica. Los usos/abusos potenciales podrían incluir deshabilitar temporalmente el uso de instancias de Zend_Session_Namespace o métodos de Zend_Session para escribir en el almacén de datos de sesión, mientras la ejecución se transfiere a código relacionado con la vista. Los intentos de realizar acciones de escritura mediante estas instancias o métodos lanzarán una excepción.

65.4.9. writeClose($readonly = true)

Apaga la sesión, cierra la escritura y desvincula $_SESSION del mecanismo de almacenamiento subyacente. Esto completará la transformación interna de datos en esta solicitud. El parámetro booleano opcional $readonly puede eliminar el acceso de escritura lanzando una excepción ante cualquier intento de escribir en la sesión mediante Zend_Session o Zend_Session_Namespace.

[Note] Lanza

Por defecto, $readonly está habilitado y cualquier acción posterior que implique escribir en el almacén de datos de sesión lanzará una excepción. Sin embargo, alguna aplicación heredada podría esperar que $_SESSION permanezca escribible después de terminar la sesión mediante session_write_close(). Aunque no se considera "mejor práctica", la opción $readonly está disponible para quienes la necesiten.

65.4.10. expireSessionCookie()

Este método envía una cookie de id de sesión expirada, causando que el cliente elimine la cookie de sesión. A veces esta técnica se usa para realizar un logout del lado del cliente.

65.4.11. setSaveHandler(Zend_Session_SaveHandler_Interface $interface)

La mayoría de los desarrolladores encontrarán suficiente el manejador de almacenamiento por defecto. Este método proporciona un envoltorio orientado a objetos para session_set_save_handler().

65.4.12. namespaceIsset($namespace)

Use este método para determinar si existe un espacio de nombres de sesión, o si un índice en particular existe en un espacio de nombres en particular.

[Note] Lanza

Se lanzará una excepción si Zend_Session no está marcado como legible (por ejemplo, antes de que Zend_Session haya sido iniciado).

65.4.13. namespaceUnset($namespace)

Use Zend_Session::namespaceUnset($namespace) para eliminar eficientemente un espacio de nombres completo y su contenido. Como con todos los arrays en PHP, si se destruye (unset) una variable que contiene un array, y el array contiene otros objetos, esos objetos permanecerán disponibles, si también se almacenaron por referencia en otros arrays/objetos que permanecen accesibles mediante otras variables. Por lo tanto namespaceUnset() no realiza una eliminación "profunda" del contenido de las entradas en el espacio de nombres. Para una explicación más detallada, por favor consulte References Explained en el manual de PHP.

[Note] Lanza

Se lanzará una excepción si el espacio de nombres no tiene permisos de escritura (por ejemplo, después de destroy()).

65.4.14. namespaceGet($namespace)

OBSOLETO: use getIterator() en Zend_Session_Namespace. Este método devuelve un array con el contenido de $namespace. Si tiene razones lógicas para mantener este método accesible públicamente, por favor proporcione sus comentarios a la lista de correo fw-auth@lists.zend.com. De hecho, se agradece la participación en cualquier tema relevante :)

[Note] Lanza

Se lanzará una excepción si Zend_Session no está marcado como legible (por ejemplo, antes de que Zend_Session haya sido iniciado).

65.4.15. getIterator()

Use getIterator() para obtener un array que contenga los nombres de todos los espacios de nombres.

[Note] Lanza

Se lanzará una excepción si Zend_Session no está marcado como legible (por ejemplo, antes de que Zend_Session haya sido iniciado).