comment
IRC Chat
play_arrow
Este sitio utiliza cookies propias y de terceros. Si continúa navegando consideramos que acepta el uso de cookies. OK Más Información.

Sesiones

  • 32 Respuestas
  • 6743 Vistas

0 Usuarios y 1 Visitante están viendo este tema.

Desconectado alexander1712

  • *
  • Underc0der
  • Mensajes: 850
  • Actividad:
    0%
  • Reputación -2
    • Ver Perfil
    • El blog del programador
    • Email
« en: Enero 30, 2013, 10:05:48 pm »
Encontré un manual muy interesante de Sessiones de php.

Índice:
    Introducción
   
    Sesiones y cookies
    Sesiones y cookies
    El proceso de una sesión PHP basada en cookies
    Ejemplo de una sesión basada en cookies
    Javascript también puede manejar cookies
    Gestionando una sesión abierta

    Destrucción de sesiones
    Vida de una sesión
    Acceso indebido con sesiones vencidas y no limpiadas
    Destruyendo una sesión
    Destruyendo sesiones inesperadas

    Sesiones expuestas
    Alojamiento compartido en Apache Server
    Un explorador de carpetas con PHP
    Configuración safe_mode en PHP
    Configuración open_basedir en PHP
    Protegiendo las variables de sesión

    Cifrar variables de sesión
    Alojamiento compartido en Apache Server
    Un explorador de carpetas con PHP
    Configuración safe_mode en PHP
    Configuración open_basedir en PHP
    Protegiendo las variables de sesión

    Propagar sesiones
    El identificador de la relación servidor-cliente en una sesión
    Esquema básico para todos los ejemplos
    Propagar SID: Sólo cookies
    Propagar SID: Sólo URL con trans_sid y form=fakeentry
    Propagar SID: Sólo URL con trans_sid y form=action
    Propagar SID: Sólo URL sin trans_sid
    Propagar SID: Cookies o URL con trans_sid
    Propagar SID: Cookies o URL sin trans_sid y usando constante SID
    Propagar SID: POST oculto

    Asegurar sesiones
    Suplantación de sesiones
    Ejemplo de sesión con autenticación
    Fijación de sessión (session fixation)
    Secuestro de sessión (session hijacking)

No tienes permisos para ver links. Registrate o Entra con tu cuenta
« Última modificación: Enero 31, 2013, 12:31:41 am por [H]arkonnen »

Desconectado alexander1712

  • *
  • Underc0der
  • Mensajes: 850
  • Actividad:
    0%
  • Reputación -2
    • Ver Perfil
    • El blog del programador
    • Email
« Respuesta #1 en: Enero 30, 2013, 10:06:24 pm »
Introducción

Cuando decidimos aprender por nuestra cuenta todo lo que podamos sobre el desarrollo web, en algún momento hemos de enfrentarnos con las sesiones. Si el aprendizaje no sigue alguna secuencia temática ordenada, nos encontraremos con problemas que nos obligan a retroceder en ese aprendizaje para retomar cuestiones que habíamos aparcado provisionalmente. Me explico. Quise hacer un sitio web real, como este, pero intentando producir todos los contenidos con el objeto de entenderlos en todos sus entresijos. Y en un momento dado ya tocaba poner un formulario de contacto que remitise los mensajes a un correo electrónico.

Se pone uno manos a la obra y ve que es necesario tener en cuenta el tema de la seguridad con los formularios que remiten datos al servidor. Pues entonces toca estudiarse todo lo que se pueda sobre seguridad. Pero hay algunas técnicas para hacer más seguro un sitio usando sesiones y cookies. Así que no queda más remedio que retroceder un paso y enfrentarse con preguntas como:

    Qué son las sesiones.
    Qué son las cookies.
    Qué relación hay entre ambos conceptos.
    Dónde se almacenan.
    Cómo se crean y destruyen.
    Qué riesgo hay si guardamos datos sensibles en las variables de sesión.
    Cómo se propagan las sesiones.
    Qué es la suplantación de sesión (session hijacking y session fixation).

Desconectado alexander1712

  • *
  • Underc0der
  • Mensajes: 850
  • Actividad:
    0%
  • Reputación -2
    • Ver Perfil
    • El blog del programador
    • Email
« Respuesta #2 en: Enero 30, 2013, 10:07:46 pm »
Sesiones y cookies

El término inglés cookie se traduce literalmente por galleta. Fueron introducidas por Lou Montulli en las primeras versiones del navegador Netscape. La razón de su existencia se debe a que el protocolo HTTP no está preparado para el uso de estados. Un estado en informática se define como una configuración de un sistema en un momento dado. Esa configuración viene a ser información que puede almacenarse en algún lugar para uso futuro. Así cuando se cierra una página web no queda ningún rastro de ese proceso. En definitiva las solicitudes HTTP realizadas al servidor son totalmente independientes.

Pero a veces es necesario almacenar ese estado. Por ejemplo, si en nuestro sitio queremos controlar el acceso de usuarios a ciertas páginas, hemos de tener en el servidor una lista de usuarios registrados. Pero en el navegador debe guardarse también la información de registro, pues de otra forma el usuario tiene que identificarse cada vez que pase de una página a otra.

Una cookie es un trozo de texto que un servidor web almacena en el disco duro del ordenador del usuario para recuperarla cuando la necesite. El contenido son parejas de nombre y valor que pueden usarse para almacenar el estado del sistema. Es importante señalar que sólo puede almacenarse texto y con ciertas limitaciones. Por si mismas las cookies no hacen nada, es decir, no son programas ni ninguna otra cosa que puede ejecutar algo no deseado.

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
En la medida de lo posible hay que buscar los términos en español, pero no podemos traducir cookie directamente por galleta en este contexto. Se puede traducir por su definición como registro de identificación o similar, pero quizás es más apropiado traducirlo como huella tal como hace el navegador Opera. Realmente son huellas que el servidor deja en el navegador para poder identificarlo. De todas formas el término en inglés está tan difundido que quizás es mejor usarlo sin traducir.
Como mucha documentación está en inglés, es conveniente conocer estas definiciones:

State (estado):
    En sistemas que procesan la información, un estado es un conjunto de propiedades transmitidas por un objeto a un observador a través de algún canal. Aplicado a HTTP, las propiedades serán transmitidas por el navegador al servidor, de tal forma que éste determina que información servir en base a las propiedades de ese estado.
Stateless (sin estado), stateful (con estado)
    Son adjetivos que se forman con state less y state full. Significa que un sistema de información trabaja sin o con conocimiento del estado del mismo, respectivamente. HTTP es un sistema stateless (sin manejo de estados), pero con el uso de cookies se convierte en un sistema stateful (con manejo de estados).
Session (sesión)
    Una sesión (de comunicación) es un intercambio no permanente de información entre sistemas. Haciendo HTTP con estados mediante el uso de las cookies, una sesión es el proceso de comunicación entre servidor y navegador para intercambiar la información de estado. Es clásico el ejemplo de una tienda web (el "shopping cart"), donde el usuario selecciona productos que va agregando a su "carrito" a medida que navega entre las páginas del sitio. Las sesiones se caracterizan por:

        Cada sesión tiene un inicio y un final.
        Las sesiones no son permanentes.
        Una sesión puede ser finalizada por el servidor o por el navegador.
        Una sesión siempre está implícita cuando se intercambia información de estado.

    El concepto de sesión es independiente de las cookies, por lo que puede haber sesiones que se manejen sin ellas, tal como veremos más adelante. Sin embargo una cookie está normalmente relacionada con una sesión.

Los documentos que sirven de estándar para implantar HTTP con estados son los siguientes:

    RFC2109 del año 1997 donde se describe inicialmente un protocolo para manejos de estado en HTTP (HTTP State Management Mechanism)
    RFC2965 del año 2000, una revisión y mejora del anterior.

Literalmente el objetivo de estos documentos es especificar una forma para crear un sesión con estado en solicitudes y respuestas con el protocolo HTTP.

Desconectado alexander1712

  • *
  • Underc0der
  • Mensajes: 850
  • Actividad:
    0%
  • Reputación -2
    • Ver Perfil
    • El blog del programador
    • Email
« Respuesta #3 en: Enero 30, 2013, 10:11:06 pm »
El proceso de una sesión PHP

Tal como señala el manual de PHP sobre No tienes permisos para ver links. Registrate o Entra con tu cuenta, el concepto es preservar cierta información a través de accesos subsiguientes de los usuarios. El proceso en PHP es el siguiente:

    Un usuario entra por primera vez en una página del sitio que usa sesiones para, a partir de esa página, navegar a otras.
    PHP crea un identificador único para esa sesión (designado para abreviar a veces como ID). Digamos de forma sencilla que es una cadena de números y letras creada por PHP que identifica de forma única un usuario en un momento del tiempo. Esta cadena se genera de forma aleatoria y se almacena en el servidor, estando disponible en el navegador del usuario de dos formas posibles:
        El servidor envía una cookie al navegador del usuario con el identificador. Podemos llamarlas sesiones basadas en cookies.
        El servidor propaga el identificador a través de la URL. En este caso podemos denominarlas sesiones basadas en URL.
    Después de recibir la primera respuesta desde el servidor acompañada del identificador, el navegador hace las siguientes peticiones de otras páginas acompañando el identificador, tanto como cookie o por la URL. Así el servidor identifica al navegador.

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
Aparte de usar cookies o enviar por la URL el identificador de sesión, podría haber otra forma usando un formulario con un campo de texto oculto. En el tema V sobre propagar sesiones veremos ejemplos de las distintas formas para propagar el identificador de sesión.
Según se desprende del manual PHP, las sesiones basadas en cookies son más seguras que las basadas en URL. Por defecto en el archivo de configuración php.ini (versión 5.2.13) encontramos los siguientes valores para configuraciones de sesiones que pueden ser modificadas en tiempo de ejecución:

La opción No tienes permisos para ver links. Registrate o Entra con tu cuenta, que especifica si PHP usará cookies para propagar las sesiones. Por defecto se establece en 1 (true), tal como se ve en el php.ini:

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
; Whether to use cookies.
; http://php.net/session.use-cookies
session.use_cookies = 1

La opción No tienes permisos para ver links. Registrate o Entra con tu cuenta, que especifica si PHP sólo usará cookies para almacenar el identificador de sesión en el navegador del cliente, no permitiendo la otra opción de propagarlo por la URL. El valor predeterminado es 1 (true), recomendando usar cookies para evitar la suplantación de sesiones (hijacking), al menos en parte. Esto es lo que aparece en el php.ini:

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
; This option forces PHP to fetch and use a cookie for storing and maintaining
; the session id. We encourage this operation as it's very helpful in combatting
; session hijacking when not specifying and managing your own session id. It is
; not the end all be all of session hijacking defense, but it's a good start.
; http://php.net/session.use-only-cookies
session.use_only_cookies = 1

La opción No tienes permisos para ver links. Registrate o Entra con tu cuenta, que especifica que el identificador sea comunicado a través de URL entre el navegador y el usuario de forma transparente, es decir sin necesidad de incorporar el identificador en cada petición o respuesta. Por defecto es 0 (false). SID es una constante de PHP que contiene el nombre de la sesión y el identificador en la forma "name=ID" o una cadena vacía si se está usando la opción de sesiones basadas en cookies. Para usar sesiones no basadas en cookies sino en URL debemos primero poner la opción session.use_cookies = 0 y luego esta opción a 1. Sin embargo debe tenerse en cuenta el riesgo asumido, tal como se expone en el php.ini:

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
; trans sid support is disabled by default.
; Use of trans sid may risk your users security.
; Use this option with caution.
; - User may send URL contains active session ID
;   to other person via. email/irc/etc.
; - URL that contains active session ID may be stored
;   in publically accessible computer.
; - User may access your site with the same session ID
;   always using URL stored in browser's history or bookmarks.
; http://php.net/session.use-trans-sid
session.use_trans_sid = 0

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
Algunas configuraciones pueden modificarse en tiempo de ejecución con la función ini_set(a, b), donde "a" será el string de la variable de configuración y "b" será el nuevo valor a poner. Por ejemplo, para habilitar sesiones basadas en URL haríamos:

ini_set("session.use_cookies", 0);
ini_set("session.use_trans_sid", 1);
session_start();
Traducción del comentario de esta opción:
El uso de trans sid puede poner en riesgo la seguridad de los usuarios. Use esta opción con precaución.

    Un usuario podría enviar una URL conteniendo un identificador de una sesión activa de otro usuario a través de un email, canales IRC, etc.
    Una URL con una sesión activa podría ser almacenada en ordenadores públicamente accesibles.
    Un usuario podría acceder con el mismo identificador de otro usuario utilizando una URL almacenada en el historial del navegador.

Si PHP recomienda usar sesiones basadas en cookies tendremos que hacerle caso, pues ahora mismo no se mucho más del tema. Pero hay que tener en cuenta que en este caso el navegador tiene que aceptar las cookies, pues el usuario puede tenerlas desactivadas. En cuanto a esto, el estándar No tienes permisos para ver links. Registrate o Entra con tu cuenta sobre el mecanismo de estado de sesiones basadas en cookies, en su punto 2 expone literalmente:

"Neither clients nor servers are required to support cookies. A server MAY refuse to provide content to a client that does not return the cookies it sends.": Ni navegador ni servidor deben estar obligados a soportar cookies. Un servidor podría rehusar servir contenido a un navegador que no le devuelve una cookie que previamente le fue enviada.

Por lo tanto, y para empezar, suponemos que el servidor manejará sesiones basadas en cookies que deberán ser aceptadas por el navegador. En el tema V sobre propagar sesiones veremos con ejemplos otras formas de propagar las sesiones para los casos en que las cookies no estuvieran activadas en el navegador.

Desconectado alexander1712

  • *
  • Underc0der
  • Mensajes: 850
  • Actividad:
    0%
  • Reputación -2
    • Ver Perfil
    • El blog del programador
    • Email
« Respuesta #4 en: Enero 30, 2013, 10:15:03 pm »
Ejemplo de una sesión PHP basada en cookies

Antes de meternos en un ejemplo para ver cómo trabajan una sesión, no debemos olvidar que las sesiones sirven para mantener información entre accesos a distintas páginas. Esa información, que se almacena en el servidor, estará disponible a través de las variables de sesión pero no de forma permanente. Digo esto porque yo mismo pensaba que las sesiones son para eso del "registro y acceso de usuarios", conocido como proceso de autenticación por el cual un sistema verifica que un usuario es quién dice ser, para entonces permitir o denegar el acceso. Las sesiones sirven para esto, pero su principal objetivo es mantener la información entre accesos.

Supongamos que tenemos un catálogo de productos en un sitio. Se compone de un conjunto grande de páginas, cada página para una línea de productos, que a su vez se desglosa en subpáginas con sublíneas. Nos podría interesar ofrecer al usuario un sistema que le ayude a encontrar lo que necesita. Por ejemplo, si en la primera página elige la línea "electrodomésticos" y en la siguiente "frigoríficos", podríamos ofrecerle todos los productos relacionados en esa sublínea y además un conjunto de enlaces relacionados. Algo así como "quizás le interese también congeladores, hornos, lavadoras, ...". Pero si elige "televisores" podríamos sugerirle "cámaras de video, aparatos música, ...".

Ese es un ejemplo de personalización de páginas. Para que un sistema así funcione es necesario almacenar la información previa del camino que va siguiendo el usuario. Para esto pueden servir las sesiones, pero no es estrictamente necesario que el usuario esté registrado en alguna base de datos.

Usaremos un ejemplo más simple. Sea una primera página (pagina1.php) que abre una sesión para navegar por un conjunto siguiente de páginas (pagina2.php y otras). Supongamos que deseamos abrir la sesión con objeto de personalizar el conjunto de páginas segunda y siguientes. Para simplificar el ejemplo hacemos que la personalización sea sólo para el color del fondo de las páginas. Podemos ya entrar en la pagina1.php que tiene el siguiente código (obviamos con puntos suspensivos el HTML que no es de interés):

<?php
//Lo primero es iniciar la sesión antes de enviar nada, pero
//estableciendo el modo con cookies por si estuviera desactivado

ini_set("session.use_cookies", 1);
ini_set("session.use_only_cookies", 1);
session_start();

//Por defecto asignamos un color para el fondo. Si esta es la
//primera vez que abrimos la sesión, le pone el color amarillo.

if (!isset($_SESSION['color-fondo'])) {
    //Esto evita la fijación de sesión
    session_regenerate_id(true);
    //Ahora asignamos el parámetro de sesión por primera vez
    $_SESSION['color-fondo']  = 'yellow';
}

//Ahora se construye la página
?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"No tienes permisos para ver links. Registrate o Entra con tu cuenta">
<html xmlns="No tienes permisos para ver links. Registrate o Entra con tu cuenta" lang="es" xml:lang="es">
    ...
    <h3>Página 1</h3>
    <p>Esta página ... el color del fondo, que ahora tiene el valor
    <code>
<?php echo htmlspecialchars($_SESSION['color-fondo']); ?></code>,
    color de fondo que se pondrá en las páginas 2 y 3.
    </p>
    <p>Podemos ver la cookie que almacenó PHP en el navegador:<br />
    <code id="mi-cookie"></code>
    </p>
    <script>
        var arrayCookies = document.cookie.split(";");
        for (var i=0; i<arrayCookies.length; i++){
            var unaCookie = arrayCookies.split("=");
            var nombre = quitaEspacios(unaCookie[0]);
            if (nombre == "PHPSESSID") {
               var contenido = escapaHtml(arrayCookies);
               setInnerText(document.getElementById("mi-cookie"), contenido);
               break;
            }
        }
    </script>   
    ...

   

En color marrón está el código PHP y en azul el HTML, incluso el de JavaScript. Los comentarios están en verde. En primer lugar vemos que para iniciar una página con sesión hemos de reconfigurar las opciones de inicio session.use_cookies y session.use_only_cookies para que la sesión sólo use cookies. Es una medida extra de seguridad por si la configuración se hubiera modificado, pues en otro caso se usaría URL para enviar la sesión y esta es una vía que no se aconseja como vimos más arriba. Luego hacemos session_start() antes que se envíe cualquier cosa al navegador. Con esto se inicia la sesión pero no la consideramos activa hasta que guardemos algún parámetro de la sesión como $_SESSION['color-fondo'] = 'yellow'. Para saber si ya la sesión fue iniciada antes miramos con isset() si nuestro parámetro color-fondo está ya registrado. Si no lo estuviera es que es el primer inicio de la sesión y lo registramos con un color de fondo amarillo, por ejemplo.

Entonces el servidor creará un identificador único para esa sesión. Este identificador se mantiene mientras mantengamos abierto el navegador, por lo que cada vez que accedamos a las páginas del ejemplo veremos que se mantiene el color de fondo seleccionado. Este ejemplo se ejecuta en mi servidor Apache + PHP montado en local sólo para aprender. Puede ver más detalles de la instalación en Apache 2.2.15 y PHP 5.2.13 en Windows. Buscando el php.ini encontramos una línea con session.save_path="C:\WINDOWS\Temp", que es el lugar donde se configura para guardar las sesiones. Esta es una carpeta que usa el servidor para almacenar la sesión, cuyo contenido después de ejecutar la primera página es el siguiente:



Crea un archivo con el nombre iniciado con sess_q75n4oq6turdv67uohqkbl7lv5. La ristra después de sess_ es el identificador único de esa sesión. Si abrimos el archivo con un bloc de notas podemos ver que contiene color-fondo|s:6:"yellow";. Se trata del parámetro que le pasamos con el color de fondo. Realmente no tenemos que preocuparnos por esto pues nos basta con crear nuevos parámetros para una sesión simplemente haciendo $_SESSION["parametro"] = $algun_valor. Para recuperar un parámetro bastaría hacer $variable = $_SESSION["parametro"]. (La barra vertical separa el nombre del valor del parámetro y que la s:6: significa que el valor es un string con 6 caracteres).

A continuación el servidor crea el documento pagina1.php que será enviado al navegador del usuario. Lo que se envía incluyendo la cabecera HTTP será algo como esto:

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
HTTP/1.1 200 OK
Date: Sun, 12 Sep 2010 22:46:25 GMT
Server: Apache/2.2.15 (Win32) PHP/5.2.13
X-Powered-By: PHP/5.2.13
Set-Cookie: PHPSESSID=q75n4oq6turdv67uohqkbl7lv5; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 2031
Content-Type: text/html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
<head>
    <title>Página 1</title>
    ... continúa el resto de la página ...

Esto lo he obtenido mediante un script PHP que hace algo parecido al Telnet en Windows. Puede ver más detalles en telnet con php. En todo caso lo que nos interesa apreciar es que el servidor envia una orden para que el navegador albergue una cookie. Ésta contiene el identificador de la sesión, el mismo que almacena en la carpeta de sesiones C:/Windows/Temp como vimos antes. El nombre dado a la cookie siempre es PHPSESSID. Este nombre se configura en el php.ini inicialmente por defecto (aunque puede modificarse):
Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
; Name of the session (used as cookie name).
; http://php.net/session.name
session.name = PHPSESSID   

   

Un detalle interesante es la fecha de expiración de la cookie. Se observa una fecha anterior, el 19/11/1981, con lo que la cookie que se almacena en el navegador tiene una duración de vida mientras el navegador esté abierto. Por lo tanto sólo va a existir en memoria y no será almacenada en el disco duro. En ese caso y con Windows se grabarían en las carpetas de documentos y configuración, en algún lugar de la carpeta del usuario. Pero creo que no viene al caso saber donde se almacenan, pues todos los navegadores nos dan la posibilidad de consultarlas y, en algún caso, hasta modificarlas.

Podemos ver las cookies en Internet Explorer 8 mediante herramientas de desarrollo, luego en caché y en ver información de cookies. Este navegador construye una página para mostrar todas las cookies almacenadas, por lo que podemos buscar al final del documento, apareciendo algo como esta imagen, donde el identificador es igual que el primero de la lista de la imagen de más arriba:



Es importante ver que Internet Explorer no muestra el dominio del que procede cuando estamos trabajando con localhost, pero que debe ser algún error puesto que para el resto de cookies si se ve el dominio. De todas formas aprovechamos para decir que las cookies de un dominio sólo pueden ser gestionadas por ese dominio, por lo que es un dato de suma importancia que se almacena con la cookie. Observe el tiempo de expiración at the end of the session, al finalizar la sesión. En los siguientes navegadores veremos que se aporta más información.

En Opera 10.61 podemos verlas en herramientas, avanzados, huellas(cookies) con algo como esto. Este navegador es el que me parece más completo al mostrar las cookies (huellas como le llama), pues ofrece además la posibilidad de editarlas. También se puede acceder con el botón derecho del ratón con la opción editar opciones del sitio, huellas, mostrando todas las cookies del dominio de esa página. Se corresponde con el segundo identificador de la lista mencionada.



En Firefox 3.6.8 podemos verlas en opciones, privacidad, mostrar cookies con algo como esto, por supuesto con otro identificador de sesión. También podemos verlas con el botón derecho del ratón en la página, con la opción ver información de la página y luego en seguridad, obteniéndose todas las cookies para el dominio de esa página. El identificador es el mismo que el tercero de la lista del /Temp en el servidor:



En Safari 4.0.5 están las cookies en el botón de ajustes generales, en la opción de preferencias, seguridad. El identificador es el mismo que el último de la lista en el /Temp del servidor:



En cualquier caso la cookie con el identificador de la sesión se almacena en el navegador del usuario. A partir de aquí y siempre dentro de la duración de la sesión, el navegador acompañará esta cookie con las solicitudes de páginas que se hagan a ese mismo servidor. Éste comprueba que el identificador es igual que el que tiene almacenado en la carpeta de sesiones del servidor (en C:/Windows/Temp para mi servidor local), en cuyo caso pone a disposición de uso los parámetros allí almacenados (el color de fondo para nuestro ejemplo).

La importancia de guardar sólo un identificador en el navegador del usuario radica en que es un dato sin valor en sí mismo. Veámos, ¿porqué no guardamos también los parámetros como el color de nuestro ejemplo en la cookie?. Existe la función setcookie() en PHP que permite enviar cookies desde el servidor al navegador. Mientras que con la variable superglobal $_COOKIE podemos verla en el servidor. Podemos argumentar que así no estamos almacenando nada en el servidor. ¿Pero si en lugar de ser un simple color para el fondo de las páginas fuera un dato personal que consideramos privado?. Dónde es más seguro tenerlo, ¿en el servidor o en el navegador?. Por ahora respondemos que en el servidor, siguiendo el criterio general de PHP que es almacenar el identificador en el navegador y el resto de la información en el servidor. Espero que a medida que desarrolle estos temas pueda dar más luz sobre este asunto.

Desconectado alexander1712

  • *
  • Underc0der
  • Mensajes: 850
  • Actividad:
    0%
  • Reputación -2
    • Ver Perfil
    • El blog del programador
    • Email
« Respuesta #5 en: Enero 30, 2013, 10:15:52 pm »
Javascript también puede manejar cookies

En el ejemplo del código de la pagina1.php anterior teníamos un trozo de código en Javascript que reproducimos otra vez:

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
    ...
    <code id="mi-cookie"></code>
    </p>
    <script>
        var arrayCookies = document.cookie.split(";");
        for (var i=0; i<arrayCookies.length; i++){
            var unaCookie = arrayCookies[i].split("=");
            var nombre = quitaEspacios(unaCookie[0]);
            if (nombre == "PHPSESSID") {
               var contenido = escapaHtml(arrayCookies[i]);
               setInnerText(document.getElementById("mi-cookie"), contenido);
               break;
            }
        }
    </script> 
    ... 
   

Podemos ver que usamos document.cookie que nos devuelve un string con todas las cookies del dominio de esa página almacenadas en el navegador. La colección de cookies de un dominio son parejas nombre=valor separadas por punto y coma (;). Así haciendo un split(";") sobre ese string obtenemos un array de parejas nombre=valor, que luego separamos otra vez para obtener el nombre y buscar el que nos interesa, PHPSESSSID para mostrar el identificador en la página de ejemplo. Así veremos que realmente el identificador se almacenó en una cookie de nuestro navegador.

Las tres funciones señaladas quitaEspacios(), escapaHtml() y setInnerText() las he creado para anular espacios antes y después de una cadena, escapar caracteres reservados e insertar texto dentro de un elemento.

No quiero extenderme ahora mucho sobre el manejo de cookies con JavaScript, pues mi interés está ahora en las que se gestionan desde un servidor para el uso de sesiones basadas en cookies. La única razón de exponerlo es para corroborar que esa cookie de sesión está realmente alojada en nuestro navegador.

Desconectado alexander1712

  • *
  • Underc0der
  • Mensajes: 850
  • Actividad:
    0%
  • Reputación -2
    • Ver Perfil
    • El blog del programador
    • Email
« Respuesta #6 en: Enero 30, 2013, 10:17:13 pm »
Gestionando una sesión abierta

A partir de la página 1 del ejemplo podemos acceder a las siguientes páginas, pagina2.php y pagina3.php, ambas de igual estructura. Una vez que pasemos por la página 1 y mientras no cerremos el navegador, la cookie seguirá almacenada en el navegador, por lo que aunque cerremos esa página 1 y mientras el navegador esté abierto, la sesión seguirá viva. Bueno a excepción de que no se destruya si acaba su duración de vida como veremos posteriormente. El código de las páginas 2 y 3 es el siguiente (son iguales), donde ponemos puntos suspensivos en el HTML que no es de interés:

Código: PHP
  1. <?php
  2. //Lo primero es iniciar la sesión antes de enviar nada, pero
  3. //estableciendo el modo con cookies por si estuviera desactivado
  4. No tienes permisos para ver links. Registrate o Entra con tu cuenta("session.use_cookies", 1);
  5. No tienes permisos para ver links. Registrate o Entra con tu cuenta("session.use_only_cookies", 1);
  6. No tienes permisos para ver links. Registrate o Entra con tu cuenta();
  7. //Establecemos una variable para el color del fondo por defecto
  8. $un_color_fondo = "white";
  9. //Comprobamos si la sesión ya fue iniciada
  10. if (!No tienes permisos para ver links. Registrate o Entra con tu cuenta($_SESSION["color-fondo"])){
  11.     //Para evitar la fijación de sesión regeneramos el id
  12.     //cuando la sesión no haya sido iniciada por el cauce previsto
  13.     No tienes permisos para ver links. Registrate o Entra con tu cuenta(true);
  14.     //Con sesiones no iniciadas ponemos un color rojo
  15.     $un_color_fondo = "red";
  16. } else {
  17.     //Aunque un parámetro de sesión proviene de una carpeta del
  18.     //servidor, no está de más filtrar caracteres no deseados,
  19.     //pues este color se insertará en el style del body.
  20.     $un_color_fondo = No tienes permisos para ver links. Registrate o Entra con tu cuenta($_SESSION["color-fondo"], ENT_QUOTES);
  21.     //Si está página se está recibiendo por la ejecución del botón
  22.     //submit del formulario, tomamos el color seleccionado
  23.     if (No tienes permisos para ver links. Registrate o Entra con tu cuenta($_POST["color"])){
  24.         //Filtramos caracteres no deseados
  25.         $un_color_fondo = No tienes permisos para ver links. Registrate o Entra con tu cuenta($_POST["color"], ENT_QUOTES);
  26.         //Eliminamos comillas por si el servidor tiene activado el
  27.         //escape con barras invertidas
  28.         if (No tienes permisos para ver links. Registrate o Entra con tu cuenta() == 1) $un_color_fondo = No tienes permisos para ver links. Registrate o Entra con tu cuenta($un_color_fondo);
  29.         //Por último actualizamos el parámetro de la sesión con el nuevo color
  30.         $_SESSION["color-fondo"] = $un_color_fondo;
  31.     }    
  32. }
  33. ?>
  34.  
  35. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  36. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  37. <html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
  38. ...
  39. <!-- Insertamos en el body el color de fondo -->
  40. <body style="background-color:<?php echo $un_color_fondo; ?>;" >
  41.     ...    
  42.     <h3>Página 2</h3>
  43.     <p>Esta página2 tiene el valor
  44.     <big><big><?php echo $un_color_fondo;?></big></big>
  45.     para la propiedad <code>background-color</code> del cuerpo del documento.
  46.     </p>
  47.     <?php if($un_color_fondo == "red"){?>
  48.         <p style="font-size: 2em;">Ha llegado a esta página sin antes haber pasado
  49.         por la página 1.</p>
  50.     <?php } else {?>
  51.         <!-- Este formulario sirve para que el usuario seleccione otro color para
  52.         el fondo. Al enviarlo se recoge en esta misma página, pues en el inicio
  53.         hemos puesto el script para ello. -->
  54.         <form action="pagina2.php" method="post">
  55.             <label>Seleccione color para el fondo:
  56.                 <select name="color">
  57.                     <option value="yellow"
  58.                     <?php if ($un_color_fondo == "yellow") echo "selected='selected'" ?>
  59.                     >amarillo (yellow)</option>
  60.                     <option value="aqua"
  61.                     <?php if ($un_color_fondo == "aqua") echo "selected='selected'" ?>
  62.                     >azul (aqua)</option>
  63.                     <option value="lime"
  64.                     <?php if ($un_color_fondo == "lime") echo "selected='selected'" ?>
  65.                     >verde (lime)</option>
  66.                 </select>
  67.             </label>
  68.             <input type="submit" value="enviar" />
  69.         </form>        
  70.     <?php } ?>
  71.     ...(aquí va el JavaScript como el de pagina1.php)...

Recordamos que el proceso para este ejemplo es que el usuario entrase a través de la primera página (pagina1.php), de tal forma que allí se creaba una nueva sesión guardando por primera vez el parámetro $_SESSION['color-fondo'] = 'yellow' si la sesión no hubiese estado iniciada. Recordamos que una sesión se mantiene abierta mientras no se cierre el navegador. Al pasar a la segunda página con el enlace dispuesto en la primera página, lo primero que hacemos es iniciar sesión otra vez con session_start(). Luego hacemos una comprobación de seguridad para ver si el parámetro existe con !isset($_SESSION["color-fondo"]). Lo que se espera es que exista, pues habiendo estado antes (en algún momento) en la primera página tiene que haber sido creado ya el color de fondo. ¿Qué razones hay para que no exista?. Una razón podría ser intentar acceder a pagina2.php sin antes haber estado en la primera página.

Las sesiones no son permanentes, por lo que tienen un inicio y un final. Una forma de finalizar una sesión es cerrar el navegador, pero con esta opción no podríamos acceder directamente a la página 2 con enlace desde la 1, aunque si podríamos acceder poniendo en la barra de direcciones del navegador la URL completa de la página 2. Si cerramos el navegador para que no haya sesión abierta y luego lo abrimos poniendo la URL de pagina2.php en la barra de direcciones del navegador, comprobaremos que el script PHP detecta que no existe la sesión, es decir, el parámetro color de fondo para esa sesión usando if (!isset($_SESSION["color-fondo"])). Si es así le ponemos el color de fondo rojo. Además regeneramos el identificador con session_regenerate_id(). Con esto tratamos de impedir las fijaciones de sesión, aspecto que trataré de explicar más adelante (si es que al final logro entenderlo).

Otra forma de acceder a pagina2.php sin usar la entrada de la página primera podría ser con el telnet con php que comenté más arriba. Con esta cadena de petición:

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
GET /temas/php-sesion/ejemplos/proceso-sesion/pagina2.php HTTP/1.1
Host: localhost
cookie: PHPSESSID=falso-id
Connection: Close
Poniendo cualquier identificador de sesión como el señalado en azul, el servidor nos responderá devolviendo la página 2:

Código: PHP
  1. HTTP/1.1 200 OK
  2. No tienes permisos para ver links. Registrate o Entra con tu cuenta: Thu, 16 Sep 2010 09:41:45 GMT
  3. Server: Apache/2.2.15 (Win32) PHP/5.2.13
  4. X-Powered-By: PHP/5.2.13
  5. Expires: Thu, 19 Nov 1981 08:52:00 GMT
  6. Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
  7. Pragma: no-cache
  8. Set-Cookie: PHPSESSID=nlikqad9ib1563tgod36ggfa84; path=/
  9. ...
  10. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  11. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  12. <html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
  13. ...
  14. <body style="background-color:red;" >
  15.     ...
  16.     <p>Esta página2 tiene el valor <big><big>red</big></big>
  17.     para la propiedad <code>background-color</code> del cuerpo del documento.</p>
  18.     <p style="font-size: 2em;">Ha llegado a esta página No tienes permisos para ver links. Registrate o Entra con tu cuenta antes haber pasado
  19.     por la página 1.</p>
  20.     ...

En este caso al no existir una sesión anterior con ese identificador, tampoco habrá un parámetro de color guardado en $_SESSION. Entonces se regenera el identificador tal como podemos ver que se pasa en la cabecera de la respuesta y se pone el color rojo para el fondo de la página. El identificador falso-id que se pasó es guardado en la carpeta Temp con el resto de sesiones, pero al regenerar la sesión con session_regenerate_id(true) usando el argumento true lo que hacemos es eliminar ese archivo de falsa sesión.

Otro aspecto de las sesiones es el tiempo de vida que poseen. Esto también es una razón por la cual deberíamos inspeccionar en pagina2.php si existen las variables de sesión (el parámetro de color en nuestro ejemplo). Las sesiones que ya no se utilizan son eliminadas del sistema, tanto en memoria como en disco. Esto lo veremos en el próximo tema que habla sobre la destrucción de sesiones.

Desconectado alexander1712

  • *
  • Underc0der
  • Mensajes: 850
  • Actividad:
    0%
  • Reputación -2
    • Ver Perfil
    • El blog del programador
    • Email
« Respuesta #7 en: Enero 30, 2013, 11:52:12 pm »
Destrucción de sesiones

Vida de una sesión

En el archivo de configuración php.ini de mi servidor local podemos ver esta entrada:
Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
; After this number of seconds, stored data will be seen as 'garbage' and
; cleaned up by the garbage collection process.
; http://php.net/session.gc-maxlifetime
session.gc_maxlifetime = 1440

Recuerde que todo lo que aparece aquí ha sido probado en mi servidor local Apache+PHP montado para el aprendizaje, pues no conviene exponer las configuración del servidor real donde está viendo estas páginas por una cuestión de seguridad.

La máxima duración de una sesión se establece por defecto en 1440 segundos (24 minutos). La traducción del comentario dice que "Después de este número de segundos, los datos almacenados serán vistos como 'basura' y limpiados por el proceso de recolección de basura." La recolección de basura sucede durante el inicio de sesiones pero no siempre, pues depende de las siguientes configuraciones del php.ini:

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
; Defines the probability that the 'garbage collection' process is started
; on every session initialization. The probability is calculated by using
; gc_probability/gc_divisor. Where session.gc_probability is the numerator
; and gc_divisor is the denominator in the equation. Setting this value to 1
; when the session.gc_divisor value is 100 will give you approximately a 1% chance
; the gc will run on any give request.
; Default Value: 1
; Development Value: 1
; Production Value: 1
; http://php.net/session.gc-probability
session.gc_probability = 1

; Defines the probability that the 'garbage collection' process is started on every
; session initialization. The probability is calculated by using the following equation:
; gc_probability/gc_divisor. Where session.gc_probability is the numerator and
; session.gc_divisor is the denominator in the equation. Setting this value to 1
; when the session.gc_divisor value is 100 will give you approximately a 1% chance
; the gc will run on any give request. Increasing this value to 1000 will give you
; a 0.1% chance the gc will run on any give request. For high volume production servers,
; this is a more efficient approach.
; Default Value: 100
; Development Value: 1000
; Production Value: 1000
; http://php.net/session.gc-divisor
session.gc_divisor = 100   

   

Citar
Algunas configuraciones pueden modificarse en tiempo de ejecución con la función ini_set(a, b), donde "a" será el string de la variable de configuración y "b" será el nuevo valor en segundos a poner. Estas tres variables session.gc_maxlifetime, session.gc_probability y session.gc_divisor pueden modificarse en tiempo de ejecución.

En definitiva, una sesión tiene una duración de 24 minutos. Pasado este tiempo si no se ha usado, entonces pasará a ser basura. Pero el recolector la limpiará de la carpeta Temp sólo cuando se inicien nuevas sesiones pero no en todos los inicios. La probabilidad de que se inicie el recolector será la división entre session.gc_probability y session.gc_divisor. Tal como está por defecto esta probabilidad es del 1%, aunque en sistemas en producción (en un servidor real) es conveniente reducirla poniendo el valor 1000 para session.gc_divisor.

Para probar que esto funciona, durante las pruebas con las sesiones del ejemplo anterior comprobé en la carpeta Temp de mi servidor local que habían muchas sesiones registradas. Pero también observé que sólo estaban las más recientes, pues las de días anteriores habían desaparecido. El recolector de basura había estado limpiándolas. Entonces modifique el session.gc_probability con un valor 100, así que la probabilidad de limpieza con la apertura de una sesión debería ser del 100%. También modifiqué el session.gc_maxlifetime a 60 segundos para comprobar la duración de vida. Luego restauré el Apache y abrí la pagina1.php del ejemplo. Y, efectivamente, eliminó todas las sesiones del Temp antes de crear la nueva sesión. Luego esperé 1 minuto y cerré el navegador y abrí pagina1.php otra vez para comprobar que elimina esa sesión anterior pues ya se había vencido su vida, lo cual hizo antes de grabar la nueva sesión.

Por supuesto, es de entender que una probabilidad del 100% significa que el servidor estará continuamente ejecutando esa acción con cada apertura de sesión. Si el servidor tiene muchos usuarios abriendo sesiones, el sistema se ve muy forzado. Por eso se recomienda una probabilidad de 1/1000 (0.1%) en un caso real, aunque por defecto tiene 1/100 (1%).

Desconectado alexander1712

  • *
  • Underc0der
  • Mensajes: 850
  • Actividad:
    0%
  • Reputación -2
    • Ver Perfil
    • El blog del programador
    • Email
« Respuesta #8 en: Enero 30, 2013, 11:52:59 pm »
Acceso indebido con sesiones vencidas y no limpiadas

Si antes de vencer el período de vida establecido se abre una página que llama a una sesión existente en el Temp, lo que se hace con sesion_start(), entonces el contador de tiempo vuelve a iniciarse para esa sesión. Pero también he comprobado que si una sesión tiene el período vencido pero aún no ha sido limpiado por el recolector de basura y se activa de nuevo con sesion_start(), también en este caso el contador vuelve a iniciarse.

Si las sesiones con el identificador y las variables de sesión se guardan en el servidor, parecería que no tendríamos porque preocuparnos. Si hay sesiones que no se están usando, vencidas o no, en la carpeta Temp ¿qué podría pasar?. Uno de los problemas sobre el que he leído es que muchas veces los sitios web se alojan en servidores compartidos, donde pudiera ser que todas las sesiones de todos los sitios de una máquina se alojaran en una misma carpeta Temp. ¿Podría otra persona acceder a las sesiones de mi sitio?. No tengo una respuesta para esta pregunta, pero sería recomendable no dejar sesiones sin usar en ningún lado.

Usando mi servidor local (no este real donde está viendo estas páginas) he ido a la carpeta Temp y he buscado la última sesion que he usado en los ejemplos de pagina1.php y siguientes. Esta sesión estaba vencida después de pasar más de 24 minutos pero dado que no había abierto ninguna sesión nueva, el recolector de basura ni siquiera tenía alguna probabilidad de limpiarla. Comprobado que contenía el color de fondo color-fondo|s:5:"green";, he abierto mi telnet con php para lanzar una petición al servidor usando pagina2.php con una cookie igual al identificador de sesión:

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
GET /temas/php-sesion/ejemplos/proceso-sesion/pagina2.php HTTP/1.1
Host: localhost
cookie: PHPSESSID=l2i5h7s7rdeooe74ecfqm64u42
Connection: Close

Y esta es la repuesta desde el servidor, donde hemos obviado alguna líneas no interesantes para el tema:

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
HTTP/1.1 200 OK
Date: Thu, 16 Sep 2010 17:52:00 GMT
Server: Apache/2.2.15 (Win32) PHP/5.2.13
X-Powered-By: PHP/5.2.13
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 2955
Connection: close
Content-Type: text/html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
<head>
    <title>Página 2</title>
    ...
<!-- Insertamos en el body el color de fondo -->
<body style="background-color:green;" >
    ...

Se observa que se pudo reabrir la sesión vencida e incluso mostrarse el fondo de color verde. ¿Cómo podemos evitar esto?, es decir ¿cómo podemos evitar que se reabra una sesión sin que se usen los cauces previstos para ello?. El "cauce" previsto en nuestro ejemplo era ir por la pagina1.php. Si hemos cerrado y abierto el navegador, indepedientemente de que hayan sesiones vencidas o no en Temp, si vamos a pagina1.php el servidor construirá una nueva sesión. Esta debería ser la forma correcta para poder controlar las sesiones. Entonces el asunto estaría en destruir las sesiones que ya no necesitemos y no esperar por el "camión de la basura".

Desconectado alexander1712

  • *
  • Underc0der
  • Mensajes: 850
  • Actividad:
    0%
  • Reputación -2
    • Ver Perfil
    • El blog del programador
    • Email
« Respuesta #9 en: Enero 30, 2013, 11:53:49 pm »
Destruyendo una sesión

Para destruir una sesión parece que podemos usar la función session_destroy(). Según dice el manual PHP, esta función destruye toda la información asociada con la sesión actual. No destruye ninguna de las variables globales asociadas con la sesión, ni destruye la cookie de sesión. Las variables pueden aún volver a usarse si volvemos a llamar a session_start(). En otro caso, si se desean eliminar, podemos iniciar el array con $_SESSION = array();. En cuanto a eliminar la cookie del usuario, debe usarse setcookie().

Pongámonos en marcha para comprobar esto. Antes de probar este ejemplo, primero tendríamos que tener abierta una sesión, lo cual puede hacerse llamando al ejemplo anterior pagina1.php desde aquí mismo, en caso en que en el tema anterior no hubiese ya entrado en ese ejemplo, donde podemos ir a la pagina3.php con un vínculo que nos lleva a la página destruye-sesion.php. El código importante de este ejemplo es:

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
<?php
//Lo primero es iniciar la sesión antes de enviar nada, pero
//estableciendo el modo con cookies por si estuviera desactivado
ini_set("session.use_cookies"1);
ini_set("session.use_only_cookies"1);
session_start();
//Primero destruimos las variables de sesión, en nuestro ejemplo "color-fondo"
$_SESSION = array();
//A continuación envíamos una cookie con tiempo negativo, lo que supone que
//el navegador eliminará la cookie en su sistema.
//Esto lo podemos hacer sólo si el php.ini está configurado para usar
//cookies, aunque por defecto es que sí.
if (ini_get("session.use_cookies")) {
    
$params session_get_cookie_params();
    
setcookie(session_name(), ''time() - 42000,
        
$params["path"], $params["domain"],
        
$params["secure"], $params["httponly"]
    );
}
//Por último destruimos la sesión
session_destroy();
?>


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
<head>
    ...

El HTML de esta página no tiene ningún contenido creado con PHP, aunque si tiene el mismo JavaScript que las de los otros ejemplos. Por eso obviamos el código pues no tiene mayor interés.

Se puede comprobar que destruye también el archivo de sesión de la carpeta Temp e incluso la cookie del navegador. Si ahora volvemos a pagina2.php o pagina3.php veremos que aparece con fondo rojo, indicativo de que no existe sesión abierta. Bueno parece que no queda ningún rastro y que esto es lo que estaba buscando. Pero queda un tema pendiente, ¿que pasa con la personalización del color del fondo?. Por lo visto no podemos usar sesiones para guardar particularidades de usuarios y, al mismo tiempo, querer destruirlas para evitar que alguién ajeno acceda a ellas.

Podríamos sólo destruir el archivo en Temp y dejar las variables de sesión vivas en memoria, $_SESSION["color-fondo"] en nuestro caso. Eso lo hacemos con el ejemplo destruye-parcial-sesion.php, cuyo vínculo también puede ver en la pagina3.php del ejemplo. El código es igual que el anterior, pero quitamos $_SESSION = array() y agregamos lo siguiente:

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
...
$un_color_fondo = "white";
if (isset($_SESSION["color-fondo"])){
    $un_color_fondo = htmlspecialchars($_SESSION["color-fondo"], ENT_QUOTES);
}
?>
...
<!-- Insertamos en el body el color de fondo -->
<body style="background-color:<?php echo $un_color_fondo?>;" >
...

Esto evidencia que la variable de sesión $_SESSION["color-fondo"] sigue estando en memoria. Este color lo aplicamos al cuerpo de la página. Lo único es que las variables de sesión sólo nos servirían para esta página, pues si volvemos a las páginas 2 o 3 veremos que aparecerán con el fondo rojo debido a que no hay sesión previamente abierta.

Pero no tiene ningún sentido destruir una sesión cuando lo que estamos buscando es precisamente poder usar sesiones, aunque tenemos que lograr que sean sesiones esperadas. En otro caso si podemos destruirlas.

Desconectado alexander1712

  • *
  • Underc0der
  • Mensajes: 850
  • Actividad:
    0%
  • Reputación -2
    • Ver Perfil
    • El blog del programador
    • Email
« Respuesta #10 en: Enero 30, 2013, 11:54:37 pm »
Destruyendo sesiones inesperadas

En los ejemplos anteriores veíamos que se ponía el fondo de color rojo cuando se llegaba a las pagina2.php o pagina3.php sin antes haber pasado por pagina1.php, es decir, sin que existiera previamente una sesión abierta. Podemos modificar las páginas para destruir la sesión y redirigir al usuario a la pagina1.php. En este ejemplo repetimos esas 3 páginas y las renombramos en otra carpeta como pagina1b.php, pagina2b.php y pagina3b.php. El procedimiento será el mismo: entrando por la primera página abrimos sesión. Desde esa página tenemos vínculos a las siguientes manteniendo la sesión, pero si entramos por las siguientes sin sesión abierta previa destruiremos la sesión y nos redigirá a la primera página.

El ejemplo está disponible en este enlace a pagina1b.php

Se comprueba que funciona si copiamos la URL de la pagina2b.php e intentamos acceder directamente a ella. Por ejemplo, cerrando el navegador y abriéndolo de nuevo, ponemos en la barra de direcciones del navegador la ruta No tienes permisos para ver links. Registrate o Entra con tu cuenta para intentar acceder a la segunda página. Veremos que se abrirá la primera página.

También puede probarlo si, arrancando desde el navegador cerrado en caso de haber estado antes en pagina1b.php, usa este enlace que lleva a pagina2b.php o al de pagina3b.php, enlaces que redigirán a la primera página si no existe sesión previa.

La pagina1b.php tiene casi el mismo código que pagina1.php del ejemplo anterior, aunque sólo se modifica en esto:
Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
//Lo primero es iniciar la sesión antes de enviar nada, pero
//estableciendo el modo con cookies por si estuviera desactivado
//Además le damos un nombre particular a esta sesión.
ini_set("session.use_cookies", 1);
ini_set("session.use_only_cookies", 1);
session_name("sesionB");
session_start();
...

    ...
    <script>
        var arrayCookies = document.cookie.split(";");
        for (var i=0; i<arrayCookies.length; i++){
            ...
            if (nombre == "sesionB") {
                ...
            }
        }
    </script> 
    ...

Hacemos notar lo resaltado en amarillo session_name("sesionB"), sentencia que debe ir antes de session_start(). En el tema anterior decíamos que el nombre de la sesión era PHPSESSID si no se especificaba otra cosa. Pero podemos manejar más de una sesión para un mismo navegador. Si previamente se usó el ejemplo del conjunto de páginas pagina1.php y siguientes, en caso de que aún no se hubiese cerrado el navegador esa sesión PHPSESSID estaría "viva". Eso quiere decir que su variable de sesión $_SESSION["color-fondo"] seguirá existiendo, por lo que si llamamos a este nuevo conjunto de páginas pagina1b.php y siguientes, tomará esa variable de sesión si tiene el mismo nombre. Por lo tanto si deseamos mantener este segundo conjunto de páginas protegidas en un entorno de variables independientes (aunque se llamen igual), hemos de darle un nombre de sesión distinto, tal como hemos hecho con session_name("sesionB"). La cookie del navegador también hemos de buscarla por este nombre nuevo de sesión.

Las páginas 2 y 3 sólo son modificadas en el script PHP, pues lo demás es igual. Vemos la pagina2b.php

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
<?php
//Lo primero es iniciar la sesión antes de enviar nada, pero
//estableciendo el modo con cookies por si estuviera desactivado
//Además le damos un nombre particular a esta sesión.
ini_set("session.use_cookies"1);
ini_set("session.use_only_cookies"1);
session_name("sesionB");
session_start();
//Establecemos una variable para el color del fondo por defecto
$un_color_fondo "white";
//Comprobamos si la sesión ya fue iniciada
if (!isset($_SESSION["color-fondo"])){
    
//Si se ha llegado aquí es que es una sesión inesperada.
    //Primero destruimos las variables de sesión
    
$_SESSION = array();
    
//A continuación eliminamos la cookie del navegador
    
if (ini_get("session.use_cookies")) {
        
$params session_get_cookie_params();
        
setcookie(session_name(), ''time() - 42000,
            
$params["path"], $params["domain"],
            
$params["secure"], $params["httponly"]
        );
    }
    
//Por último destruimos la sesión
    
session_destroy();
    
//Rederigimos al usuario a pagina1b.php
    
$host  $_SERVER["HTTP_HOST"];
    
$uri   rtrim(dirname($_SERVER["PHP_SELF"]), "/\\");
    
$extra "pagina1b.php";
    
header("Location: http://$host$uri/$extra");
    
//Tras la redirección e inmediatamente salimos del script
    
exit;
} else {
    
//Aunque un parámetro de sesión proviene de una carpeta del
    //servidor, no está de más filtrar caracteres no deseados,
    //pues este color se insertará en el style del body.
    
$un_color_fondo htmlspecialchars($_SESSION["color-fondo-b"], ENT_QUOTES);
    
//Si está página se está recibiendo por la ejecución del botón
    //submit del formulario, tomamos el color seleccionado
    
if (isset($_POST["color"])){
        
//Filtramos caracteres no deseados
        
$un_color_fondo htmlspecialchars($_POST["color"], ENT_QUOTES);
        
//Eliminamos comillas por si el servidor tiene activado el
        //escape con barras invertidas
        
if (get_magic_quotes_gpc() == 1$un_color_fondo stripslashes($un_color_fondo);
        
//Por último actualizamos el parámetro de la sesión con el nuevo color
        
$_SESSION["color-fondo-b"] = $un_color_fondo;
    }    
}
?>


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
    ...

Para redirigir al usuario usamos la función header(). Nos fijamos en un ejemplo que hay en ese manual PHP, en relación con una nota que dice que HTTP/1.1 requiere una URI absoluta como argumento a Location. Expone un ejemplo que hemos trasladado. Por un lado la variable global $_SERVER nos permite acceder a las variables de entorno del servidor. Así $_SERVER["HTTP_HOST"] contiene el dominio de la página actual. Luego $_SERVER["PHP_SELF"] contiene la ruta del actual script que se está ejecutando. En el ejemplo la ruta completa del script es la que señalamos más arriba No tienes permisos para ver links. Registrate o Entra con tu cuenta por lo que $_SERVER["PHP_SELF"] contiene la ruta relativa al raíz, es decir, temas/php-sesion/ejemplos/proceso-sesion-b/pagina2b.php.

Obtenemos la carpeta con dirname(). Por último con la función rtrim(ruta, "/\\") quitamos los espacios a la derecha de una ruta y también la última barra de la derecha. Ponemos las dos barras "/\" (aunque escapamos la segunda por estar dentro de un string), pues en Windows se pueden usar ambas y hemos de usar siempre las barras derecha, porque luego concatenemos todo con barras derecha. Inmediatamente después salimos del script con exit, con lo que no enviamos el HTML de la página 2 sino que redirigimos a la página 1.

Aunque hemos avanzado algo, esto no excluye la posibilidad de "robar" una sesión de la carpeta Temp y, mediante una aplicación como telnet con php, acceder a pagina2b.php sin que el servidor note la diferencia. En el próximo tema sobre sesiones expuestas intentaré buscar una solución.

Desconectado alexander1712

  • *
  • Underc0der
  • Mensajes: 850
  • Actividad:
    0%
  • Reputación -2
    • Ver Perfil
    • El blog del programador
    • Email
« Respuesta #11 en: Enero 30, 2013, 11:56:50 pm »
Sesiones expuestas

Alojamiento compartido en Apache Server

El problema que tratamos en esta página se refiere a los riesgos de seguridad cuando nuestro sitio está en un servidor de alojamiento compartido (share hosting). Decíamos que la carpeta C:/Windows/Temp/ (o la que fuese según la instalación de PHP) se usaba para almacenar las sesiones. Otros usuarios que comparten el alojamiento podrían llegar a ver sesiones que no eran suyas, por lo que decimos que nuestras sesiones quedan expuestas a ser leídas. Y aunque no lo hicieran ellos directamente, sí podría algún tercero aprovechar huecos de seguridad en sus páginas. Para ver estos efectos, montaré mi servidor local como alojamiento compartido.

El archivo de configuración httpd.conf de mi servidor local Apache 2.2.15 se encuentra en la carpeta conf del servidor. Ahí podemos encontrar las opciones relativas a los suplementos de configuración:

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
# Supplemental configuration
#
# The configuration files in the conf/extra/ directory can be
# included to add extra features or to modify the default configuration of
# the server, or you may simply copy their contents here and change as
# necessary.
...
# Virtual hosts

Include conf/extra/httpd-vhosts.conf
...

En este archivo los comentarios comienzan con #, e inicialmente la opción Include estaba puesta como comentario. Quitándole ese caracter pasa a ser una opción que configura el suplemento httpd-vhosts.conf que se encuentra en la carpeta conf/extra/, archivo que viene inicialmente por defecto para ayudarnos a configurar un servidor de alojamiento compartido. Se trata de alojamientos virtuales (vhosts = virtual host) para lograr el alojamiento compartido que necesitamos. Es virtual porque todos los sitios comparten el mismo servidor, aunque externamente esto no afecta al usuario que verá cada sitio como si estuviera alojado en un servidor propio. Esta configuración la vamos a modificar para incluir dos dominios que apunten a una misma IP pero que puedan compartir el servidor como si fueran sitios distintos.

Usando Apache en local, el servidor busca el dominio local por defecto, que suele ser localhost con la IP 127.0.0.1. Esa IP no es accesible desde Internet y se usa para que un ordenador se identifique a si mismo en los protocolos de Internet. En todo caso también puede explicitarse con una opción del archivo httpd.conf que por defecto viene anulada con la marca de comentario #ServerName localhost:80, pues siempre es la misma. Pero podemos cambiar ese nombre de dominio en Windows de tal forma que entonces tendremos que hacérselo saber a Apache en esa opción.

Para cambiar ese dominio o agregar nuevos en Windows, vamos a la carpeta C:/Windows/system32/drivers/etc/ y encontraremos un archivo de texto hosts sin extensión. Lo abrimos con el Notepad o similar y vemos algo como esto (incluso con los comentarios en español):

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
# Copyright (c) 1993-1999 Microsoft Corp.
#
# Éste es un ejemplo de archivo HOSTS usado por Microsoft TCP/IP para Windows.
#
# Este archivo contiene las asignaciones de las direcciones IP a los nombres de
# host. Cada entrada debe permanecer en una línea individual. La dirección IP
# debe ponerse en la primera columna, seguida del nombre de host correspondiente.
# La dirección IP y el nombre de host deben separarse con al menos un espacio.
#
#
# También pueden insertarse comentarios (como éste) en líneas individuales
# o a continuación del nombre de equipo indicándolos con el símbolo "#"
#
# Por ejemplo:
#
#      102.54.94.97     rhino.acme.com          # servidor origen
#       38.25.63.10     x.acme.com              # host cliente x

127.0.0.1       localhost   
   

Vemos que viene por defecto con localhost en 127.0.0.1. Ahora agregamos dos nuevos dominios simplemente añadiendo líneas con cualquier nombre como localhost1 y localhost2:

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
...
127.0.0.1       localhost
127.0.0.1       localhost1
127.0.0.1       localhost2   
   

Ahora vamos al archivo de suplemento del alojamiento compartido (virtual host) httpd-vhosts.conf y marcamos como comentarios el ejemplo que aparece para que nos sirva de consulta y sólo dejamos como líneas sin comentar las siguientes:

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
NameVirtualHost *:80

<VirtualHost *:80>
    DocumentRoot ".../Apache server/htdocs/home/sitio"
    ServerName localhost
</VirtualHost>

<VirtualHost *:80>
    DocumentRoot ".../Apache server/htdocs/home/sitio1"
    ServerName localhost1
</VirtualHost>

<VirtualHost *:80>
    DocumentRoot ".../Apache server/htdocs/home/sitio2"
    ServerName localhost2
</VirtualHost>

Los 3 puntos suspensivos previos son un trozo de ruta que he obviado para simplificar la exposición. Realmente mi Apache local lo tengo instalado en la carpeta de usuario C:/Documents and Settings/Administrador/Mis documentos/web, aunque la instalación por defecto seguramente lo hará en C:/Archivos de Programa/ o similar, pero esto no es importante para lo que estamos viendo.

Dentro de htdocs uno puede montar las carpetas como desee. Aquí home será la carpeta que agrupe a todos los dominios compartidos donde habrá una carpeta para cada dominio virtual. Pero también las carpetas de los dominios podrían colgar directamente de htdocs. Esto es sólo una cuestión de nombres sin mayor importancia, pues al final se suben los documentos incluidos dentro de la carpeta sitio al alojamiento real pero no se sube esa carpeta ni las que anteceden, por lo que podría usarse cualquier nombre en el servidor local a efectos del desarrollo del sitio.

Inicialmente ya se habrán creado las carpetas home/sitio dentro de la carpeta htdocs que viene por defecto con la instalación de Apache. En primer lugar hay que poner el dominio virtual que ya existe especificando la ruta raíz que apunta a ".../home/sitio". Luego se deja el nombre de servidor que viene por defecto en Windows: localhost.

A continuación se crean los otros dos dominios virtuales apuntando a .../home/sitio1 y con el nombre localhost1 para el servidor (y lo mismo para sitio2). El primer dominio virtual que se especifique será el que Apache muestre si se le realiza una consulta a un nombre de dominio y no lo encuentra en este archivo httpd-vhosts.conf.

Para ver que el ejemplo funciona hemos de crear la carpeta .../htdocs/home/sitio1 y .../htdocs/home/sitio2 y meter en ella algún documento para que el servidor lo abra. Para acabar rápido copiamos el que viene con la instalación index.html y que está ubicado en la carpeta .../htdocs, añadiéndole algún parráfo.

Vemos en un navegador el dominio localhost:


Y los nuevos dominios localhost1 con ese párrafo que le hemos añadido:


así como localhost2


Con esto ya estamos en condiciones de probar localmente un ejemplo de alojamiento compartido. Hay que observar que en una situación real probablemente no podemos acceder a modificar las configuraciones del servidor Apache (los .conf) ni a los de PHP (php.ini). Por lo tanto cualquier solución al problema del riesgo de acceso a la carpeta /Temp pasa por lo que podamos hacer sin tocar esos archivos de configuración.

Además se plantea otro problema relacionado con esas configuraciones. Por motivos de seguridad, el servidor real puede tener desactivadas todas las lecturas de configuraciones e incluso de las versiones que usa tanto de Apache como de PHP. Por ejemplo con la configuración del php.ini:

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
; Decides whether PHP may expose the fact that it is installed on the server
; (e.g. by adding its signature to the Web server header).  It is no security
; threat in any way, but it makes it possible to determine whether you use PHP
; on your server or not.
; http://php.net/expose-php
expose_php = On

podemos desactivar que se presente la siguiente cabecera, obtenida con mi telnet con php llamando a mi localhost:

   
Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
HTTP/1.1 200 OK
Date: Sun, 26 Sep 2010 11:46:12 GMT
Server: Apache/2.2.15 (Win32) PHP/5.2.13

simplemente poniendo expose_php = Off, guardando el archivo php.ini y reiniciando el servidor, vemos que ahora con la misma petición sólo sale la referencia de Apache y no la de PHP:

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
HTTP/1.1 200 OK
Date: Sun, 26 Sep 2010 11:47:02 GMT
Server: Apache/2.2.15 (Win32)

Es obvio que cuanto menos sepa un atacante sobre un sistema, más esfuerzo le requerirá efectuar alguna intromisión no deseada. Por lo tanto hay que partir de la base de que en un alojamiento compartido no contaremos con la ayuda del servidor para reducir el riesgo de nuestro sitio. Y esta forma de actuar nos lleva a no considerar las medidas de seguridad globales del servidor que no podamos gestionar, sino sólo a tener en cuenta aquellas que están a nuestro alcance. Que el servidor tiene medidas de seguridad globales, pues mejor, pero si esas medidas fallan al menos quedarán las de nuestro sitio compartido para intentar hacer frente a la intromisión.

Desconectado alexander1712

  • *
  • Underc0der
  • Mensajes: 850
  • Actividad:
    0%
  • Reputación -2
    • Ver Perfil
    • El blog del programador
    • Email
« Respuesta #12 en: Enero 30, 2013, 11:57:23 pm »
Un explorador de carpetas con PHP

Para calibrar el riesgo de acceso a carpetas, he construido un explorador de carpetas y archivos con PHP. Se trata de una página PHP que no podrá ver en línea aquí por motivos de seguridad como luego entenderá, pero cuyo código puede consultar, copiar e instalarlo en su servidor local si lo desea.

Si lo instala en su servidor local verá que puede "navegar" por todas las carpetas de su ordenador si no hay restricciones. E incluso llegar a la carpeta C:/Windows/Temp y ver el contenido de una sesión cualquiera. Esta imagen ofrece una vista de un acceso a una sesión almacenada de las que usamos en los ejemplos del tema anterior. Se reconstruye la imagen eliminado el resto de entradas del directorio /Temp con unas líneas de puntos rojos para no extender en exceso la imagen y poder ver al final el contenido de la variable de sesión color-fondo:


Se trata de un formulario que envía la ruta de una carpeta o la de un archivo. Muestra todo el contenido de esa carpeta y si es un archivo además lo lee y lo presenta. La potencia de PHP para manejar ficheros es evidente. Si no hay restricciones, un usuario de un dominio compartido podría acceder a cualquier carpeta del ordenador donde está el servidor.

En esta aplicación he agregado la lectura dos opciones de configuración de PHP que restringuen el acceso a carpetas. Son safe_mode y open_basedir que analizaremos a continuación. Además se leen los uid y gid, identificadores de usuario y grupo que también analizaremos.

Desconectado alexander1712

  • *
  • Underc0der
  • Mensajes: 850
  • Actividad:
    0%
  • Reputación -2
    • Ver Perfil
    • El blog del programador
    • Email
« Respuesta #13 en: Enero 30, 2013, 11:58:46 pm »
Configuración safe_mode en PHP

La configuración safe_mode se traduce por modo seguro pero tal como expone el manual PHP, se trata de una opción desaconsejada en la versión 5 y probablemente ya no se incluya en posteriores versiones. Según dice el manual, es un intento de resolver el problema de seguridad de los servidores compartidos. Pero añade que es estructuralmente incorrecto intentar resolver este problema a nivel de PHP.

Por lo tanto y siguiendo mi criterio expuesto antes, si estoy en un alojamiento compartido será mejor ignorar este modo seguro y, aunque estuviese activada, montar nuestro sitio como si esta opción no existiera. De todas formas vamos a echarle un vistazo rápido. En el archivo de configuración php.ini en mi servidor local viene así:
Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
; Safe Mode
; http://php.net/safe-mode
safe_mode = Off

Vemos que ya por defecto en la versión 5.2.12 viene desactivada. Se supone que cuando está activado (On), PHP chequeará si el propietario del script coincide con el propietario del fichero con el que se va a operar. El caso es que safe_mode actúa de tal forma que si estoy accediendo con un script del cual soy propietario a otra carpeta de otro propietario, entonces se supone que safe_mode lo impedirá.

En el manual de PHP, en security and safe mode, hay un parte donde explica como funciona, la cual exponemos aquí literalmente y con la traducción:

When safe_mode is on, PHP checks to see if the owner of the current script matches the owner of the file to be operated on by a file function or its directory. For example: (Cuando safe_mode está activado, PHP comprueba si el propietario del script que se está ejecutando coincide con el propietario del fichero donde ese script quiere operar mediante una función que manipula ficheros o directorios. Por ejemplo:)

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
-rw-rw-r--    1 rasmus   rasmus       33 Jul  1 19:20 script.php
-rw-r--r--    1 root     root       1116 May 26 18:01 /etc/passwd

Running script.php: (Ejecutando script.php:)

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
<?php
 readfile
('/etc/passwd'); 
?>

results in this error when safe mode is enabled: (aparecerá este error cuando safe mode está activado:)

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
Warning: SAFE MODE Restriction in effect. The script whose uid is 500 is not
allowed to access /etc/passwd owned by uid 0 in /docroot/script.php on line 2
(Aviso: Restricción SAFE MODE actuando. El script cuyo uid is 500 no le
está permitido acceder a /etc/passwd cuyo propietario es uid 0 en
/docroot/script.php, línea 2)

Pero mi servidor local está montado en Windows XP, donde el sistema de grupos y usuarios es diferente que el de este ejemplo que se corresponde con el SO de UNIX y similares. De todas formas intenté montar las carpetas de los dominios compartidos localhost1 y localhost2 de tal forma que sus propietarios fuesen diferentes. Usando la forma en que Windows configura permisos y creando dos usuarios, hice que uno de ellos poseyera las carpetas de home/sitio1 y el otro usuario poseyera home/sitio2. Tuve que sacar estas carpetas de Documents and Settings que pertenece al usuario administrador y montarlas por fuera, de tal forma que pudiera traspasar la posesión. Puse un ejemplar del script explora.php en cada sitio, comprobando que cada uno tenía su propia posesión sobre su script.

Antes de probar el ejemplo puse safe_mode = On en el php.ini y reinicié el servidor. Lamentablemente al ejecutar desde, por ejemplo, localhost1 el script pude alcanzar la carpeta de localhost2 y cualquier otra carpeta del ordenador. E igual resultado para localhost2. En definitiva, en Windows no parece que safe_mode lea adecuadamente quiénes son los propietarios de los archivos y carpetas, pues siempre sale uid=0 y gid=0, por lo que para PHP todos los archivos y carpetas de mi ordenador son del mismo propietario.

De todas formas el único interés era comprobar el funcionamiento, pues dado que safe_mode es una configuración que desaparecerá en versiones futuras de PHP, es mejor no basar la seguridad en ella. Además si estamos en un alojamiento compartido y esta opción estuviera activada, pues mejor, pero en todo caso como dije es mejor no tenerla en cuenta y basar la seguridad de nuestro sitio en configuraciones que podamos controlar. Y safe_mode no la podremos controlar porque en un alojamiento compartido no tendremos acceso al archivo php.ini para cambiarlo.

Citar
Las configuraciones del php.ini pueden modificarse en tiempo de ejecución con ini_set() pero no en todos los casos. Por ejemplo, safe_mode tiene la condición PHP_INI_SYSTEM que quiere decir que sólo puede cambiarse en php.ini y httpd.conf. Sin embargo open_basedir que veremos a continuación tiene la condición PHP_INI_ALL lo que significa que puede cambiarse también en ejecución, aunque para versiones anteriores a 5.3 tiene la condición PHP_INI_SYSTEM. Estos dos enlaces del manual PHP lo aclaran:

    Cuándo se puede cambiar una configuración
    Listado configuraciones PHP

Desconectado alexander1712

  • *
  • Underc0der
  • Mensajes: 850
  • Actividad:
    0%
  • Reputación -2
    • Ver Perfil
    • El blog del programador
    • Email
« Respuesta #14 en: Enero 31, 2013, 12:00:35 am »
Configuración open_basedir de PHP

Esta configuración open_basedir también puede limitar el acceso a carpetas. Veámos que pone el archivo php.ini de mi servidor local al respecto:

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
; open_basedir, if set, limits all file operations to the defined directory
; and below.  This directive makes most sense if used in a per-directory
; or per-virtualhost web server configuration file. This directive is
; *NOT* affected by whether Safe Mode is turned On or Off.
; http://php.net/open-basedir
open_basedir =

Podemos traducirlo como "si se activa open_basedir, limita todas las operaciones con ficheros al directorio especificado. Esta configuración tiene mayor utilidad cuando se comparten webs en el mismo servidor. Esta configuración no es afectada si safe mode está activado o desactivado". En el manual PHP encontramos la parte sobre PHP instalado como modulo apache (en mi servidor local está instalado así) que explica el uso de esta directiva como una medida más simple que usar una configuración basada en permisos de grupos y usuarios.

Por lo tanto hay tres cosas por las que debemos considerar open_basedir:

    Es independiente de safe_mode, configuración que no podíamos controlar en el alojamiento compartido real.
    Con las últimas versiones de PHP (5.3) puede modificarse en tiempo de ejecución. Yo tengo instalado en local el PHP 5.2.13 y, aunque debería actualizarlo, por ahora prefiero terminar esta serie sobre sesiones antes de hacer ningún cambio.
    Se puede incluir una directiva en el archivo httpd-vhost.conf de la forma php_admin_value open_basedir para cada sitio, pero esto tampoco estará en nuestra mano en un alojamiento compartido real.

Valdrá la pena realizar algunas pruebas en mi servidor compartido local, que antes lo dejamos con esta estructura de carpetas:

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
...
Apache server
    cgi-bin
    conf
    error
    htdocs
        home
            sitio  (localhost)
            sitio1 (localhost1)
            sitio2 (localhost2)
            ...
   

La primera prueba se basa en limitar cualquier acceso por encima de la carpeta htdocs, configuración que pondremos en php.ini:

open_basedir = ".../Apache server/htdocs"

Ubicamos el script del explorador PHP en los tres sitios, en su carpeta raíz. Ahora vamos a abrirlo en localhost sin especificar open_basedir y verá que se muestra en esta captura de pantalla:


En cada exploración tenemos dos primeras carpetas: ./ que señala a sí misma y ../ que nos lleva a la carpeta que contiene a la actual, con lo que podemos navegar "hacia arriba" por el directorio. Ahora activamos open_basedir y le ponemos como carpeta tope .../htdocs. Si intentamos navegar hacia atrás con la carpeta de nivel superior ../, cuando lleguemos a htdocs e intentemos seguir subiendo, veremos que no muestra esa carpeta superior:


Observe que la segunda carpeta ../ ahora está puesta como un archivo .. sin vínculo. Se ha desactivado con el open_basedir. Además si en el script del explorador PHP no tenemos control de mostrar errores, lo cual podíamos hacer usando el caracter @ en las funciones que manejan carpetas, entonces en la cabecera de esta página saldrá un mensaje como este:

Warning: is_dir(): open_basedir restriction in effect. File(C:\Documents and Settings\Administrador\Mis documentos\web\Apache server\htdocs/..) is not within the allowed path(s): (C:\Documents and Settings\Administrador\Mis documentos\web\Apache server\htdocs\) in C:\Documents and Settings\Administrador\Mis documentos\web\Apache server\htdocs\home\sitio\explora.php on line 87 ......

Este mensaje nos dice que se produce una restricción sobre la función is_dir() usada en la carpeta htdocs cuando intenta mostrar la carpeta superior ../. También salen mensajes sobre otras funciones como is_readable(), pero que hemos obviado por simplicidad.

A continuación vamos a incluir una configuración open_basedir en el archivo de Apache httpd-vhosts.conf para cada uno de los dominios virtuales que están en mi servidor local:
Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
<VirtualHost *:80>
    DocumentRoot ".../Apache server/htdocs/home/sitio"
    ServerName localhost
    php_admin_value open_basedir ".../Apache server/htdocs/home/sitio"     
</VirtualHost>

<VirtualHost *:80>
    DocumentRoot ".../Apache server/htdocs/home/sitio1"
    ServerName localhost1
    php_admin_value open_basedir ".../Apache server/htdocs/home/sitio1"   
</VirtualHost>

<VirtualHost *:80>
    DocumentRoot ".../Apache server/htdocs/home/sitio2"
    ServerName localhost2
    php_admin_value open_basedir ".../Apache server/htdocs/home/sitio2"     
</VirtualHost>

Así cada sitio virtual se limitará a ver las carpetas de su sitio y no otras. Por ejemplo, arrancando el explorador del sitio localhost1 veremos esto:


Aquí se observa que la segunda entrada no es la carpeta ../ sino los dos puntos .. sin vínculo, por lo que no podremos seguir subiendo por el directorio para alcanzar algo que esté fuera del sitio1.

Esto está bien, pero no deja de ser una configuración realizada en el archivo httpd-vhosts.conf del servidor. ¿Qué pasa si nuestro servidor real no tiene esta configuración y otros dominios virtuales que comparten el mismo servidor pueden acceder a la carpeta Temp para ver mis sesiones?. Pero también puede suceder que el servidor esté correctamente configurado para impedirlo, pero que de alguna forma alguién se salte esa protección. Incluso alguién podría utilizar otro lenguaje de programación que no sea PHP y que permita acceso a carpetas, de tal forma que ni safe_mode ni open_basedir se lo pueda impedir. Pero entonces, ¿cómo evitar al menos que alguién vea nuestras sesiones?

Desconectado alexander1712

  • *
  • Underc0der
  • Mensajes: 850
  • Actividad:
    0%
  • Reputación -2
    • Ver Perfil
    • El blog del programador
    • Email
« Respuesta #15 en: Enero 31, 2013, 12:01:12 am »
Protegiendo las variables de sesión

Cuando necesitemos usar sesiones hemos de pensar que las variables de sesión se almacenarán en una carpeta Temp, recurso al que otros pudieran tener acceso en un alojamiento compartido como hemos visto antes. Si esas variables no contienen datos importantes podríamos dejar las cosas así, pero en otro caso caben la siguientes opciones:

    Usar session_set_save_handler() para redefinir la forma en que se almacenan las variables de sesión.
    Seguir usando la carpeta Temp que exista para almacenar las sesiones, pero cifrando los datos.

Podríamos haber añadido otra opción: olvidarse del alojamiento compartido y buscar un servidor dedicado. Si tenemos un sitio con procesamiento de datos sensibles es obvio que esta es la única solución que parece más segura. Pero también requiere más conocimiento pues estará a nuestro cargo toda la configuración del servidor. Por ahora mi propósito es usar un alojamiento compartido por dos razones, una económica pues son más baratos y otra por falta de conocimientos suficientes para hacerme cargo de un servidor por completo.

La posibilidad de usar session_set_save_handler() se sale de mis posibilidades actuales. El problema es que hay que "construir" un manejador de sesiones, es decir, el código necesario para guardar las sesiones en algún sitio, en un archivo o en una base de datos. Así este recurso podemos ubicarlo dentro de nuestro sitio y protegerlo, siendo exclusivamente para almacenar nuestras sesiones, de tal forma que el alojamiento compartido no suponga una amenaza tan severa como cuando se albergan en la carpeta Temp.

El manual PHP expone un ejemplo de un manejador usando archivos, donde se presentan las funciones para abrir y cerrar sesiones, escribir y recuperar variables de sesión, etc. Como estoy empezando en estos temas de seguridad, creo que no es conveniente limitarse a copiar y pegar código sin saber como funciona. Y lo del manejo de archivos en PHP o las bases de datos son temas sobre los que quiero adentrarme con más profundidad en otra ocasión. Por lo tanto por ahora sólo contemplaré la protección de los datos almacenados en las variables de sesión. Una forma de hacerlo es cifrando los datos de esas variables, tal como veremos en el siguiente tema.

Desconectado alexander1712

  • *
  • Underc0der
  • Mensajes: 850
  • Actividad:
    0%
  • Reputación -2
    • Ver Perfil
    • El blog del programador
    • Email
« Respuesta #16 en: Enero 31, 2013, 12:03:45 am »
Instalar librería Mcrypt de PHP

En el tema anterior decíamos que con el uso de sesiones en alojamientos compartidos alguién podría acceder a las variables de sesión en la carpeta /Temp. Podemos proteger esas variables cifrando sus datos. Puede que PHP no se haya instalado con un cifrador, por lo que tendremos que agregar ese complemento. En primer lugar recordamos como instalé PHP en mi servidor local Apache. Allí veíamos que no se instalaban todos lo complementos, por lo que ahora podemos hacerlo. Como estoy en Windows voy a agregar/quitar programas y busco PHP:


Pulso "cambiar" y entro en la pantalla de instalación de PHP:


Pulso "Change" para realizar cambios en la instalación:


Dejo esta configuración tal como estaba (lo tenía instalado como módulo Apache 2.2) y pasamos a los complementos:


Los que no están instalados aparecen con una aspa roja. Buscamos el complemento mcrypt y seleccionamos para instalarlo:


Seguimos el resto de pasos hasta completar la instalación. Podemos ver que está instalado ejecutando la función phpinfo() y observar la parte del módulo Mcrypt con algo como esto:


Citar
La función phpinfo() es un recurso de PHP que compone un documento HTML con todas las configuraciones del PHP instalado. Con la instalación suele incluirse un documento llamado info.php con sólo esta función:

<?php
    echo phpinfo();
?>

Es obvio que este documento no debe estar al alcance de los usuarios de una web, pues hemos de intentar que nadie conozca cuáles son nuestras configuraciones. Por ello nunca debe incluirse en una ruta con alcance público, aunque aquí estoy probando todo esto en mi servidor local por lo que no hay mayor riesgo.

Desconectado alexander1712

  • *
  • Underc0der
  • Mensajes: 850
  • Actividad:
    0%
  • Reputación -2
    • Ver Perfil
    • El blog del programador
    • Email
« Respuesta #17 en: Enero 31, 2013, 12:04:47 am »
Cifrando datos en las variables de sesión

Antes de continuar conviene aclarar que muchas veces se usa el término encriptar en lugar de cifrar (o desencriptar en lugar de descifrar). El término correcto es cifrar (y descifrar), que según la R.A.E. en su primera acepción dice "transcribir en guarismos, letras o símbolos, de acuerdo con una clave, un mensaje cuyo contenido se quiere ocultar. De hecho encriptar no aparece registrado en la R.A.E., aunque si aparece el término criptografía definido como el "arte de escribir con clave secreta o de un modo enigmático".

Para poner en práctica el cifrado de datos, haremos un ejemplo de un conjunto de 3 páginas similar a los ejemplos en temas anteriores. En la primera página (pagina1c.php) iniciaremos una sesión. Luego en la página 2 se ofrece al usuario un formulario para que nos envíe algún dato que cifraremos y almacenaremos en una variable de sesión. En la tercera página accederemos a ese dato descifrándolo y devolviéndolo al usuario. Puede ver el código completo de las tres páginas.

La pagina1c.php es igual que la de los ejemplos de los temas anteriores, sólo contiene un inicio de sesión, con dos enlaces a las páginas siguientes:

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
//Lo primero es iniciar la sesión antes de enviar nada, pero
//estableciendo el modo de usar sesiones sólo con cookies por si
//estuviera desactivado. También damos nombre a la sesión.
ini_set("session.use_cookies", 1);
ini_set("session.use_only_cookies", 1);
session_name("sesionC");
session_start();
//Ahora comprobamos que la sesión no haya sido iniciada
if (!isset($_SESSION["iniciada"])) {
    //Esto evita la fijación de sesión
    session_regenerate_id(true);
    //Ahora asignamos el parámetro de sesión por primera vez
    $_SESSION["iniciada"]  = "si";
}
//Ahora se construye la página
?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
<head>
...
        <li><a href="pagina2c.php">Ir a página 2c</a> para enviar una dirección de
        email y que sea cifrada y almacenada en una variable de sesión.</li>
        <li><a href="pagina3c.php">Ir a página 3c</a>, donde se recupera la dirección
        email descifrada.</li>
        ...   
   

En la pagina2c.php realizamos el cifrado y descifrado. Esquemáticamente hace lo siguiente:

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
<?php
...
session_name("sesionC");
session_start();
...   
//Comprobamos si la sesión ya fue iniciada
if (!isset($_SESSION["iniciada"])){//LA SESIÓN NO HA SIDO INICIADA
    //Si se ha llegado aquí es que es una sesión inesperada.
    
...DESTRUIMOS SESIÓN Y REDIRIGIMOS A pagina1c.php...
} else {
//LA SESIÓN YA FUE INICIADA
    //Vemos si existe la variable de sesión almacenada
    
if (isset($_SESSION["email"])) {
        ...
DESCIFRAMOS EL EMAIL...               
    }
    
//Si está página se está recibiendo por la ejecución del botón
    //submit del formulario, tomamos el valor del email recibido
    
if (isset($_POST["email"])){
        ...
CIFRAMOS EL EMAIL... 
    }    
}
?>


...
<html ...>
    ...
    <form action="pagina2c.php" method="post">
        <label>Email:
        <input type="text" name="email" size="50" maxlength="50"
        style="font-family: Courier New; color: maroon; "
        value ="<?php echo $un_email?>" />
        </label>
        <input type="submit" value="enviar" />
    </form>
    ...   
   

En esta pagina2c.php primero hacemos session.start para iniciar la sesión denominada "sesionC". Luego comprobamos si no hay sesión iniciada anteriormente con la variable $_SESSION["iniciada"], con lo cual sería una sesión inesperada. Esta parte es igual que lo que explicamos en el tema 2 sobre la destrucción de sesiones inesperadas.

En otro caso la sesión ya estaba iniciada. Recordemos que el ejemplo almacenará la variable de sesión $_SESSION["email"] en la carpeta /Temp de sesiones, y que nuestro propósito es cifrar esa variable para que no pueda ser leída. Por lo tanto en primer lugar debemos comprobar si existe la variable y en ese caso proceder a descifrarla e incluirla en el formulario que se acompaña en esta página. Pero si no existe la variable, comprobaremos si la hemos recibido por POST a través de ese formulario, en cuyo caso la ciframos y guardamos en la sesión. La pagina3c.php sólo realiza la parte del descifrado.

Si en pagina2c.php envíamos algo como "esto está cifrado" por el formulario, en la variable de sesión $_SESSION["email"] quedará almacenada esa frase cifrada como pHuY/TE2xvbrPWNEIKGI6S+hSGqP8wg6. Si vamos a la carpeta /Temp y buscamos el archivo de sesión (cuyo identificador vemos expuesto en la página), abriéndolo en modo texto aparecerá esto:

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
iniciada|s:2:"si";email|s:32:"pHuY/TE2xvbrPWNEIKGI6S+hSGqP8wg6";
En principio parece que hemos conseguido nuestro propósito: cualquiera que rebusque en /Temp sólo encontrará datos cifrados. Pero aún podemos mejorarlo. En la página con el código completo puede ver con comentarios detallados como funciona el cifrado y descifrado, por lo que no voy a extenderme en exceso aunque exponemos ahora el código de la parte de cifrado para comentar algunos detalles, con enlaces al manual PHP para más información:

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
...
...Nota: en la variable $un_email tenemos un dato que vamos a cifrar...
...
$cifrador = mcrypt_module_open(MCRYPT_DES, "", MCRYPT_MODE_ECB, "");
$maximo_tamanyo_vector_inicio = mcrypt_enc_get_iv_size($cifrador);
//ECB ignora el vector inicio, pero hay que ponerlo
$vector_inicio = mcrypt_create_iv($maximo_tamanyo_vector_inicio, MCRYPT_RAND );
$maximo_tamanyo_llave = mcrypt_enc_get_key_size($cifrador);
$llave_md5 = md5("1234");
$llave = substr($llave_md5, 0, $maximo_tamanyo_llave);
mcrypt_generic_init($cifrador, $llave, $vector_inicio);
$un_email_cifrado = mcrypt_generic($cifrador, $un_email);
mcrypt_generic_deinit($cifrador);
mcrypt_module_close($cifrador);
$un_email_cifrado_64 = base64_encode($un_email_cifrado);
$_SESSION["email"] = $un_email_cifrado_64;
   

En primer lugar el algoritmo usado para el cifrado es el DES en modo ECB, declarado con las constantes MCRYPT_DES y MCRYPT_MODE_ECB respectivamente al abrir el módulo cifrador. Este modo ECB cifra bloques de 64 bits de forma independiente. Por ejemplo, la cadena 12345678 tiene 64 bits, cuya cadena cifrada y sin aún codificarla en base64 sería CDCF8812BD2ECCB8 (presentada en hexadecimal). Si duplicamos la cadena inicial 1234567812345678 también se cifra igual el siguiente bloque CDCF8812BD2ECCB8CDCF8812BD2ECCB8. Si hay bloques cifrados repetidos, parece que se podrían usar técnicas que logran descifrar el mensaje. Además este modo ECB cifra el mensaje siempre de la misma forma. Para evitar ambas cosas deben usarse otros modos como CBC cuyo detalle veremos en el próximo apartado.

Pero el problema más importante es la sentencia $llave_md5 = md5("1234") donde especificamos la contraseña de cifrado, que en este ejemplo hemos puesto "1234". Si estamos intentando impedir que alguién interno a nuestro alojamiento pueda ver nuestras sesiones en /Temp, nada le impedirá ver el código fuente de este algoritmo y coger esta contraseña. Como el cifrador en modo ECB cifra siempre igual para una misma contraseña, no tendría ningún problema para descifrar el dato usando su propio código.

Desconectado alexander1712

  • *
  • Underc0der
  • Mensajes: 850
  • Actividad:
    0%
  • Reputación -2
    • Ver Perfil
    • El blog del programador
    • Email
« Respuesta #18 en: Enero 31, 2013, 12:05:25 am »
Detalles del descifrado

Antes de ver el siguiente apartado donde intentaremos resolver los problemas planteados, conviene detenerse en el descifrado:

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
...
//Vemos si existe la variable de sesión almacenada
if (isset($_SESSION["email"])) {
    //Extraemos el email cifrado y codificado en base64
    $un_email_cifrado_64 = $_SESSION["email"];
    if ($un_email_cifrado_64 != ""){
        //decodificamos base64 para dejarlo en binario
        $un_email_cifrado = base64_decode($un_email_cifrado_64);
        //Abrimos cifrador, IV, llave e inicializamos cifrador de la
        //misma forma que en la parte de cifrado (ver allí comentarios)
        $cifrador = mcrypt_module_open(MCRYPT_DES, "", MCRYPT_MODE_ECB, "");
        $maximo_tamanyo_vector_inicio = mcrypt_enc_get_iv_size($cifrador);
        $vector_inicio = mcrypt_create_iv($maximo_tamanyo_vector_inicio, MCRYPT_RAND );
        $maximo_tamanyo_llave = mcrypt_enc_get_key_size($cifrador);
        $llave_md5 = md5("1234");
        $llave = substr($llave_md5, 0, $maximo_tamanyo_llave);
        mcrypt_generic_init($cifrador, $llave, $vector_inicio);
        //Desciframos el email
        $un_email = mdecrypt_generic($cifrador, $un_email_cifrado);
        //Quitamos los nulos de relleno de la derecha
        $un_email = rtrim($un_email, "\0");
        //Finalizamos cifrador y lo cerramos
        mcrypt_generic_deinit($cifrador);
        mcrypt_module_close($cifrador);
    }       
}   
...

Si hay una variable almacenada $_SESSION["email"] la decodificamos base64 para luego descifrarla. El proceso es parecido al cifrado. Se abre un cifrador, un vector inicio, se ajusta el tamaño de la llave y se inicia el cifrador. Ahora desciframos con mdecrypt_generic y ya tenemos nuestro dato descifrado.

Hay un detalle a tener en cuenta cuando el cifrador se ve obligado a rellenar por la derecha con caracteres nulos. La cadena 1234567890 tiene 80 bits (10 bytes). Como el cifrador DES trabaja en bloques de 64 bits (8 bytes), antes de cifrar esa cadena le agrega 6 bytes a la derecha con caracteres nulos para conseguir 2 bloques completos de 64 bits. Si ciframos y desciframos esa cadena 1234567890 obtenemos en el navegador Firefox 3.6 lo siguiente:


La referencia de caracter &xFFFD; en UNICODE se usa para representar caracteres irreconocibles cuando estamos usando, por ejemplo, la codificación UTF-8 en este documento. El caracter es representable gráficamente si lo incluimos en un elemento HTML como <code>&xFFFD;</code> y también dentro de un <input>. Si estamos usando Firefox 3.6 podemos verlo en pantalla: �. En otros navegadores no aparecerá el caracter o bien se verá un cuadrado indicando que no reconoce el caracter UNICODE.

Para evitar esto hemos de quitar el relleno del cifrador cuando sea necesario, para lo cual usamos la función rtrim($un_email, "\0"). Así eliminamos por la derecha todos los caracteres nulos.

Desconectado alexander1712

  • *
  • Underc0der
  • Mensajes: 850
  • Actividad:
    0%
  • Reputación -2
    • Ver Perfil
    • El blog del programador
    • Email
« Respuesta #19 en: Enero 31, 2013, 12:06:40 am »
Cifrado con llave compartida

Los problemas señalados al final del apartado 2 eran:

    El modo de cifrado ECB no es seguro pues cifra siempre igual. Hay que usar otro modo donde el vector IV modifique el cifrado en cada vez y en cada bloque.
    Usar la contraseña o llave de cifrado en el código del script es una vulnerabilidad pues puede ser leída.

Para evitar esto componemos otro grupo de tres páginas siguiendo el mismo ejemplo. Puede consultar el código completo de estas páginas. Con este enlace pagina1d.php podemos ver en ejecución la página inicial que ahora tiene algunas cosas nuevas:

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
<?php
ini_set
("session.use_cookies"1);
ini_set("session.use_only_cookies"1);
session_name("sesionD");
session_start();
//Comprobamos si la sesión ya fue iniciada
$hay_sesion = isset($_SESSION["iniciada"]);
//Vemos si se está recibiendo un POST
$hay_post = (!empty($_POST));
//Vemos si se ha pulsado el botón de finalizar sesión
$finaliza_sesion = isset($_POST["fin-sesion"]);  
...
if (!
$hay_sesion) {//CON SESIONES NO INICIADAS
    
session_regenerate_id(true);
    
$llave_base md5(uniqid(rand(), true));
    
$longitud strlen($llave_base);
    
$mitad $longitud 2;
    
$llave_servidor substr($llave_base0$mitad);
    
$_SESSION["iniciada"]  = $llave_servidor;
    
$llave_cliente substr($llave_base$mitad$longitud);    
    
setcookie("llave-sesion"$llave_cliente0,  dirname($_SERVER["PHP_SELF"]));
} else {
//CON SESIONES INICIADAS
    
if ($hay_post && $finaliza_sesion){
        
$_SESSION = array();
        
setcookie(session_name(), ""time()-42000dirname($_SERVER["PHP_SELF"]));
        
setcookie("llave-sesion"""time()-42000dirname($_SERVER["PHP_SELF"]));  
        
session_destroy();
        
$host  $_SERVER["HTTP_HOST"];
        
$uri   rtrim(dirname($_SERVER["PHP_SELF"]), "/\\");
        
$pagina "pagina1d.php";
        
header("Location: http://$host$uri/$pagina");
        exit;    
    }
}
//Ahora se construye la página
?>


    ...
    <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post">
        <input name="fin-sesion" type="submit" value="finalizar sesión" />
    </form>
    ...

Conviene realizar una recogida de identificaciones iniciales para ver si la sesión ha sido iniciada o hay algo en el POST. Así declaramos booleanos $hay_sesion, $hay_post y $finaliza_sesion, valores que nos permitirán clarificar su manejo en el código siguiente.

En este grupo de páginas con sesión acompañaremos siempre un fomulario con sólo el botón de envío para finalizar la sesión. Aunque en este ejemplo remitimos a la página de inicio pagina1d.php con fines didácticos, en una situación práctica deberíamos finalizar la sesión y salir del grupo de páginas protegidas, enviando al usuario a una página sin sesión.

Si no hay sesión previa iniciada hacemos lo mismo que otras veces regenerando el identificador. Pero ahora agregamos el código necesario para construir la llave o contraseña para el cifrado de datos. En primer lugar construimos una $llave_base usando la función PHP uniqid() que genera una cadena identificadora única basada en el reloj. El primer argumento es un número aleatorio generado por la función PHP rand(), evitando posible generación del mismo identificador por varios sitios que comparten el mismo reloj, como es el caso del alojamiento compartido. El segundo argumento es true que agrega entropía adicional lo que hará que los resultados sean más únicos.

En definitiva se trata de generar una cadena de 23 caracteres que nos servirá de contraseña o llave para cifrar nuestros datos. Luego obtenemos la firma (denominado también valor resumen, valor hash, signatura, etc.) de la llave base con el algoritmo MD5, con lo cual tenemos una cadena de 32 caracteres (256 bits). Esta aún no es la llave final, pues ahora la dividiremos en dos partes. La primera mitad ($llave_servidor) la guardaremos en la variable de sesión $_SESSION["iniciada"] y la otra mitad ($llave_cliente) la enviaremos con una cookie al usuario con setcookie("llave-sesion", ...). Cuando vayamos a cifrar o descifrar algo en la página 2 y siguientes hemos de reunir las dos partes concatenando ambas mitades y aplicando la firma de nuevo md5($_SESSION["iniciada"].$_COOKIE["llave-sesion"]). Usar la firma otra vez es importante pues esa concatenación tiene 256 bits y la contraseña que necesita el algoritmo DES es de sólo 64 bits (56 útiles). Como se toman los primeros caracteres hemos de pasar otra vez la firma pues con sólo la media llave del servidor nos serviría para descifrar el mensaje.

Se observa que si se recibe el botón de finalizar sesión, lo cual se detecta con $hay_post && $finaliza_sesion, entonces destruimos la sesión como en otros ejemplos. Además también anulamos la cookie del navegador donde está almacenada la media llave del cliente.

En la pagina2.php hacemos el cifrado de un dato recibido desde un formulario. En el código de esa página puede ver todos los detalles, exponiéndo a continuación sóo lo que nos interesa comentar sobre cifrado:

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
...
if (!$hay_sesion || ($hay_sesion && $hay_post && $finaliza_sesion)){
    //LA SESIÓN NO HA SIDO INICIADA O FINALIZAMOS SESIÓN
    ... aquí se destruye sesión ...
} else  {
    //LA SESIÓN YA FUE INICIADA
    if (isset($_COOKIE["llave-sesion"])){
        $llave_doble = md5($_SESSION["iniciada"].$_COOKIE["llave-sesion"]);
        //Vemos si existen las variables de sesión almacenadas
        if (isset($_SESSION["email"]) && ($_SESSION["email"] != "")
            && isset($_SESSION["iv"])) {
            //Extraemos el email cifrado y codificado en base64
            ... aquí se extrae y descifra el email que está en $_SESSION["email"] ...
        }
        //Recogemos el email recibido
        if (isset($_POST["email"])){
            $un_email = htmlspecialchars($_POST["email"], ENT_QUOTES);
            ...
            //CIFRAMOS
            $cifrador = mcrypt_module_open(MCRYPT_DES, "", MCRYPT_MODE_CBC, "");
            $maximo_tamanyo_vector_inicio = mcrypt_enc_get_iv_size($cifrador);
            $vector_inicio = mcrypt_create_iv($maximo_tamanyo_vector_inicio, MCRYPT_RAND);
            $_SESSION["iv"] = base64_encode($vector_inicio);;
            $maximo_tamanyo_llave = mcrypt_enc_get_key_size($cifrador);
            $llave = substr($llave_doble, 0, $maximo_tamanyo_llave);
            mcrypt_generic_init($cifrador, $llave, $vector_inicio);
            $un_email_cifrado = mcrypt_generic($cifrador, $un_email);
            mcrypt_generic_deinit($cifrador);
            mcrypt_module_close($cifrador);
            $_SESSION["email"] = base64_encode($un_email_cifrado);
            ...
        }       
    }
}
   

En el código del ejemplo del apartado 2 se explica con detalle en los comentarios cada una de las sentencias involucradas en un cifrado. El descifrado es similar. De todas formas volvemos a repetir los pasos del cifrado para este ejemplo:

    Abrimos un cifrador con mcrypt_module_open, usando el algoritmo DES y el modo CBC por medio de las constantes señaladas.
    El modo CBC requiere un vector de inicialización (IV: initialization vector), pero primero obtenemos el tamaño máximo de ese vector para el algoritmo que estamos usando, lo cual conseguimos con mcrypt_enc_get_iv_size($cifrador).
    Creamos un IV con mcrypt_create_iv aplicándole ese tamaño máximo y la constante MCRYPT_RAND si estamos en PHP bajo Windows (en otro caso consulte el manual PHP).
    Ese IV lo codificamos en base64 para almacenarlo en la variable de sesión $_SESSION["iv"].
    Obtenemos el máximo tamaño de la llave para ese algoritmo, lo que se consigue con mcrypt_enc_get_key_size($cifrador).
    Nuestra llave doble es mayor que el tamaño que necesitamos, por lo que la recortamos con substr.
    Iniciamos el cifrador con mcrypt_generic_init aplicándole la llave y el vector de inicialización.
    Ciframos nuestro dato con mcrypt_generic.
    Finalizamos el cifrador con mcrypt_generic_deinit.
    Cerramos el módulo cifrador con mcrypt_module_close.
    Guardamos el dato cifrado en $_SESSION["email"] después de haberlo codificado en base64.

En resalte azul hemos señalado algunas cosas. Primero vemos como se recompone la $llave_doble si encontramos la cookie con la media llave que se envió al cliente. Esta será la contraseña para descifrar $_SESSION["email"] o cifrar $un_email que se haya recibido por POST. El algoritmo DES se abre en modo CBC. Así evitamos bloques cifrados iguales. Este modo requiere que se guarde el vector de inicialización (IV) para luego ser usado en el descifrado. Podemos almacenarlo en una variable $_SESSION["iv"]. Lo codificamos en base64 para no incluir caracteres de control en el archivo de sesión. En la ejecución de pagina2d.php se observa que cada vez que se envía el dato, éste es cifrado de forma distinta. Sucede esto porque se genera un IV diferente en cada apertura del cifrador dando lugar a un cifrado distinto. Aunque es una mejora de seguridad, obliga a tener que almacenar ese IV en algún sitio para usarlo en el descifrado.

Si enviamos la cadena "esto está cifrado" en esa pagina2d.php, obtenemos esta captura de pantalla:


En ese momento esa cadena cifrada es KddCTD+Yh24puQchmhskre6HO+tjtgF0, pero si la envíamos de nuevo obtendremos otro ciframiento. En la carpeta de sesiones /Temp encontramos el archivo de sesión con este contenido:

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
iniciada|s:16:"b2ab7f14b040b836";
iv|s:12:"aQT4gWAYOCU=";
email|s:32:"KddCTD+Yh24puQchmhskre6HO+tjtgF0";
   

Si alguién mira este archivo sólo podrá acceder a la media llave del servidor y al IV. Sin la otra media llave del cliente no podría descifrar el email. Ya no tenemos el problema planteado de que alguién pudiera tomar la contraseña escrita en el código de nuestro script. Sin embargo se ha de tener en cuenta que se envía al cliente la media llave a través de una cookie. En las peticiones del cliente siempre acompañará esta cookie, por lo que este mecanismo no se libra de que alguién pueda interceptarla y obtener la media llave. Además si se recibe el dato por POST para luego ser cifrado, también puede ser interceptado antes de llegar al servidor. E incluso en la pagina3d.php volvemos a enviar el email descifrado al cliente.

En definitiva, este mecanismo sólo serviría para proteger los datos de las variables de sesión desde la perspectiva de que otros sitios del alojamiento compartido pudieran verlas. Y si además la sesión estuviera finalizada porque el usuario cerró el navegador, aunque aún existiera la sesión en /Temp, ya no habría forma de recuperar la otra media llave del cliente para descifrar esas variables, pues la cookie ya no existiría en el navegador del cliente. Es desde este punto de vista en que esta estrategia podría ser interesante, puesto que si estamos buscando cifrar todo el flujo de datos tendremos que ir a técnicas más complejas como el protocolo HTTPS.

 

¿Te gustó el post? COMPARTILO!



[PHP] Utilizando sesiones

Iniciado por ANTRAX

Respuestas: 0
Vistas: 1091
Último mensaje Febrero 24, 2010, 11:31:04 am
por ANTRAX