Menú

Mostrar Mensajes

Esta sección te permite ver todos los mensajes escritos por este usuario. Ten en cuenta que sólo puedes ver los mensajes escritos en zonas a las que tienes acceso en este momento.

Mostrar Mensajes Menú

Mensajes - Alex

#521
Back-end / Re:Sesiones
Enero 31, 2013, 12:21:01 AM
Propagar SID: Sólo URL con trans_sid y form=action

La ejecución se inicia en este enlace solo-url-transsid-action/propaga0.php. Este ejemplo es igual que el anterior, propagando la sesión sólo con URL, pero usando el atributo action en la opción url_rewriter.tags en lugar de fakeentry. Las configuraciones iniciales son:

Código: text
ini_set("session.use_cookies", 0);
ini_set("session.use_only_cookies", 0);
ini_set("session.use_trans_sid", 1);
ini_set("url_rewriter.tags", "a=href,form=action");
define("SESION", "sesionUrlTranssidAction");
session_name(SESION);
session_start();


Al solicitar la página propaga2.php podemos ver el código fuente generado en nuestro navegador:

Código: text
...
<form
action="propaga2.php?sesionUrlTranssidAction=1375aptlieg4arkrsm0rfjpj26"
method="post">
<input type="hidden" name="sesionUrlTranssidAction"
value="1375aptlieg4arkrsm0rfjpj26" />
<input name="fin-sesion" type="submit" value="finalizar sesión" />
</form>
.....
<a href="propaga1.php?sesionUrlTranssidAction=1375aptlieg4arkrsm0rfjpj26">
propaga1.php</a>
...
<form action="propaga3.php?sesionUrlTranssidAction=1375aptlieg4arkrsm0rfjpj26"
method="get">
<input type="hidden" name="sesionUrlTranssidAction"
value="1375aptlieg4arkrsm0rfjpj26" />
<label>campo-get:
<input type="text" name="campo-get" value="Valor del input get" />
</label>
<input type="submit" value="enviar" />
</form>
...
<form
action="propaga3.php?sesionUrlTranssidAction=1375aptlieg4arkrsm0rfjpj26"
method="post">
<input type="hidden" name="sesionUrlTranssidAction"
value="1375aptlieg4arkrsm0rfjpj26" />
...


En los elementos formulario vemos que PHP agregó el SID en el atributo action de la misma forma que para los elementos vínculo, pero también dotó a los formularios del campo de texto oculto. Si desde esta página propaga2.php envíamos el formulario POST, podemos observar en propaga3.php este resultado:

Código: text
$_GET["sesionUrlTranssidAction"]: 1375aptlieg4arkrsm0rfjpj26 
$_POST["sesionUrlTranssidAction"]: 1375aptlieg4arkrsm0rfjpj26
$_GET["campo-get"] del formulario GET: NO EXISTE
$_POST["campo-post"] del formulario POST: Valor del input post

   

Se recibe el SID por ambas vías, el GET del parámetro URL agregado al action del formulario y el POST del campo oculto.
#522
Back-end / Re:Sesiones
Enero 31, 2013, 12:20:23 AM
Propagar SID: Sólo URL con trans_sid y form=fakeentry

La ejecución se inicia en este enlace solo-url-transsid-fake/propaga0.php. El código de la página de entrada sólo contiene la definición de la constante SESION. No es necesario enviar una cookie de prueba pues no se utilizarán para las sesiones. En la página de inicio de sesión, propaga1.php hacemos lo mismo que antes, pero adaptando la configuración a sólo URL con SID transparente y fakeentry para los formularios (ver tabla del primer apartado):

Código: text
ini_set("session.use_cookies", 0);
ini_set("session.use_only_cookies", 0);
ini_set("session.use_trans_sid", 1);
ini_set("url_rewriter.tags", "a=href,form=fakeentry");
define("SESION", "sesionUrlTranssidFake");
session_name(SESION);
session_start();
if (isset($_POST["fin-sesion"])){
    ...finalizamos sesión....

if (!isset($_SESSION["iniciada"])) {
    $_SESSION["iniciada"]  = "SESIÓN INICIADA CON SÓLO URL,
    TRANSSID Y FORM=FAKEENTRY";
}


La única diferencia es que no detectamos ninguna cookie, pues ahora no hace falta. Observamos el nuevo nombre de sesión que usaremos en este ejemplo sesionUrlTranssidFake. Además ahora especificamos la reescritura de tags a=href y form=fakeentry. Esta página de entrada de sesión tiene dos vínculos. Por un lado un formulario con un botón para finalizar sesión, cuyo vínculo en el atributo action la llama a si misma. Luego hay otro vínculo que apunta a la siguiente página propaga2.php. Si ejecutamos el ejemplo y vemos el código fuente generado en nuestro navegador, observaremos esto:

Código: text
...
<form action="propaga1.php" method="post">
<input type="hidden"
name="sesionUrlTranssidFake"
value="jbspfclsdtfhm57fe104n3r8a6" />
<input name="fin-sesion" type="submit" value="finalizar sesión" />
</form>
....
<li>Vínculo a otra página
<a
href="propaga2.php?sesionUrlTranssidFake=jbspfclsdtfhm57fe104n3r8a6">
propaga2.php</a></li>
...


Si observamos el vínculo <a>, vemos que PHP ha agregado el parámetro SID, es decir, nombre_sesión = identificador_sesión. Por otro lado PHP ha generado un elemento <input type="hidden" > en el formulario para albergar el SID de la sesión.

Esto lo hace PHP de forma transparente para el programador, teniendo en cuenta la configuración establecida en url_rewriter.tags, cuyo valor por defecto es a=href,area=href,frame=src,input=src,form=fakeentry,fieldset=, pero que hemos reescrito sólo para a=href,form=fakeentry pues el resto no los vamos a utilizar en este ejemplo. Así para los elementos <a> usará el agregado de parámetro en el atributo href, mientras que para los formularios la palabra clave fakeentry le dice que debe crear un campo oculto para ubicar el SID.

Si nos situamos en el vínculo que lleva a la siguiente página propaga2.php comprobaremos que la barra de estado del navegador contiene la URL con el parámetro de sesión. Al pasar a esa página observamos, en el apartado de variables de sesión, que ésta se recibió con $_GET["sesionUrlTranssidFake"]: jbspfclsdtfhm57fe104n3r8a6. Es decir, se recibe el parámetro con el SID por GET en la URL. Pero también lo podemos recibir por POST.

En esa misma página propaga2.php tenemos un formulario que remite por POST su campo a la página propaga3.php. Si lo ejecutamos veremos en el apartado de variables de sesión lo siguiente:
Código: text

$_GET["sesionUrlTranssidFake"]: NO EXISTE
$_POST["sesionUrlTranssidFake"]: jbspfclsdtfhm57fe104n3r8a6
$_GET["campo-get"] del formulario GET: NO EXISTE
$_POST["campo-post"] del formulario POST: Valor del input post

   

En definitiva, PHP estará vigilando los GET y POST para el recibido de un parámetro con el nombre de la sesión. Entonces lo compara con las sesiones iniciadas y si el identificador coincide podrá completar el proceso de identificación de sesión tal como hacía en el caso de cookies.

Si tenemos un vínculo a un sitio externo, PHP no agrega el SID. Por ejemplo, en la última página propaga3.php tenemos estos tres vínculos generados en nuestro navegador:

Código: text
...
<li>Volver a la primera página
<a href="propaga1.php?sesionUrlTranssidFake=jbspfclsdtfhm57fe104n3r8a6">
propaga1.php</a></li>
<li>Volver a la segunda página
<a href="propaga2.php?sesionUrlTranssidFake=jbspfclsdtfhm57fe104n3r8a6">
propaga2.php</a></li>           
<li>Enlace a un sitio externo
<a href="http://docs.php.net/manual/es/">http://docs.php.net/manual/es/</a>
</li>       
...


PHP agregó el SID a los dos primeros mientras que no lo hizo con el último, supongo porque detectó http:// al inicio. De todas formas dejar en manos de PHP que realice estas acciones puede comprometer la seguridad al exponer un SID con una URL a un sitio externo, en el caso de que por alguna razón algo no vaya bien. Luego veremos que podemos mantener un mayor control sin utilizar use_trans_sid, aunque suponga más esfuerzo pues habrá que escribir todos los agregados de SID.
#523
Back-end / Re:Sesiones
Enero 31, 2013, 12:19:38 AM
Propagar SID: Sólo cookies

Este primer ejemplo puede verse desde el enlace solo-cookies/propaga0.php. La ejecución de las páginas presenta los estados de las variables y configuraciones relacionadas con la sesión que ayudarán a comprender mejor el proceso.

La página de entrada sin sesión propaga0.php sólo tiene un par de líneas de código PHP (en marrón). El HTML (en azul) que no tiene mayor interés lo obviamos. El JavaScript expuesto aquí, que se incluye en todas las páginas, busca las cookies en el navegador, lo que nos servirá para verificar que se están gestionando como se espera. Este script se expone con detalle en el tema I, en el apartado sobre Javascript también puede manejar cookies.

Código: text
<?php
define("SESION", "sesionCookie");
setcookie("test-cookie", SESION);
?>

...HTML...
    <code id="mi-cookie" style="border: red solid 1px;"> </code>
    <script>
        var arrayCookies = document.cookie.split(";");
        var contenido = "";
        for (var i=0; i<arrayCookies.length; i++){
            var unaCookie = arrayCookies[i].split("=");
            var nombre = quitaEspacios(unaCookie[0]);
            if ((nombre == "test-cookie")||
                (nombre == "<?php echo SESION; ?>")) {
               contenido += escapaHtml(arrayCookies[i]) + "; ";
            }
        }
        if (contenido == "") contenido = "No hay cookies en el navegador.";
        setInnerText(document.getElementById("mi-cookie"), contenido);
    </script>
...sigue HTML...


La constante SESION es simplemente para agilizar el uso del nombre de la sesión en el cuerpo del documento y, sobre todo, es más necesaria en el resto de páginas. La siguiente línea PHP si es importante en este caso. Dado que se trata de sesiones que sólo usarán cookies para propagarse, es necesario detectar si el navegador las tiene activadas. Como la sesión la iniciaremos en la siguiente página propaga1.php, en ese momento se habrá emitido una cookie de sesión al navegador, pero el servidor no sabría si fue aceptada. Así aprovechamos la página de entrada sin sesión para emitir una cookie de prueba (que llamamos test-cookie). Luego en la página de inicio de sesión comprobaremos que el navegador la aceptó si la recibimos.

La página de inicio de sesión es propaga1.php. El script PHP es ahora más extenso. En esta y las siguientes páginas ponemos lo mismo al inicio del código:

Código: text
ini_set("session.use_cookies", 1);
ini_set("session.use_only_cookies", 1);
ini_set("session.use_trans_sid", 0);
define("SESION", "sesionCookie");
session_name(SESION);
session_start();


Las modificaciones en las opciones del php.ini sólo serán válidas durante la ejecución del script, por lo que habrá que acompañarlas en todas las páginas. A menos que podamos acceder al archivo php.ini y realizarlas ahí. Si nuestro sitio está en un alojamiento compartido, quizás esto no sea posible, pero incluso puede resultar una medida positiva la ejecución de los ini_set() en cada script, por si alguién le da por modificar el php.ini del alojamiento compartido.

Se trata de establecer las opciones para usar sólo cookies, como vimos en la tabla del primer apartado. Para cada ejemplo usamos un nombre distinto de sesión, siendo en este caso sesionCookie. Luego iniciamos sesión. A continuación vemos si hemos recibido la cookie de prueba:

Código: text
$hay_cookie = (((isset($_COOKIE["test-cookie"]))&&
    (($_COOKIE["test-cookie"]) == SESION))||
    (isset($_COOKIE[SESION])));
if ($hay_cookie) setcookie("test-cookie", "", time() - 42000);


Si es la primera vez que entramos en esta página con sesión no iniciada, no existirá $_COOKIE[SESION], por lo que tendremos que comprobar $_COOKIE["test-cookie"] que viene desde la página de entrada. Si $hay_cookie es true entonces el navegador tiene las cookies activadas y podemos eliminar la cookie de prueba. El código sigue con una acción para finalizar la sesión y llevarnos a la página de entrada sin sesión (propaga0.php):

Código: text
if (isset($_POST["fin-sesion"])){
    $_SESSION = array();
    if (ini_get("session.use_cookies")) {
        $params = session_get_cookie_params();
        setcookie(session_name(), '', time() - 42000,
            $params["path"], $params["domain"],
            $params["secure"], $params["httponly"]
        );
    }
    session_destroy();
    $host  = $_SERVER["HTTP_HOST"];
    $uri   = rtrim(dirname($_SERVER["PHP_SELF"]), "/\\");
    $pagina = "propaga0.php";
    header("Location: http://$host$uri/$pagina");
    exit;
}


Este código para finalizar sesión ya se expuso en el tema II, en el apartado sobre destrucción de sesiones, por lo que no comentamos más nada aquí. Finalmente el script PHP acaba registrando la variable de sesión por primera vez:
Código: text

if (!isset($_SESSION["iniciada"])) {
    $_SESSION["iniciada"]  = "SESION INICIADA CON SOLO COOKIES";
}


Esta variable será la que nos indique en los siguientes accesos si se está propagando la sesión. Las siguientes páginas, propaga2.php y propaga3.php no tienen estas últimas líneas, mientras que el resto es igual a excepción de la verificación de cookies activadas. En este caso sólo basta con verificar que se ha recibido la cookie de sesión:

Código: text
$hay_cookie = isset($_COOKIE[SESION]);


Con el ejemplo en ejecución pueden verse los detalles acerca de las variables y configuraciones. Puede probarse desactivando las cookies del navegador y ver que la sesión no se propaga por las páginas, creando una nueva sesión en cada acceso.
#524
Back-end / Re:Sesiones
Enero 31, 2013, 12:18:04 AM
Esquema básico para todos los ejemplos

Se trata de elaborar un conjunto de 4 páginas para cada uno de los ejemplos anteriores:

    Una primera página llamada propaga0.php que será una página de entrada sin sesión. Tiene dos cometidos principales:
        Sirve de entrada y salida de la sesión. En las siguientes páginas con sesión si necesitáramos cerrar la sesión, nos remitiría aquí.
        Para la propagación con cookies, nos sirve para enviar una cookie para testear si el navegador las tiene activas. Esta cookie de prueba (que denominamos test-cookie) no es una cookie de sesión. En la siguiente página donde iniciamos sesión haríamos una consulta para ver si nos llegó esta cookie.
    Una segunda página propaga1.php que sería la página de inicio de sesión. Aquí hacemos el primer session_start() y damos valor por primera vez a una variable de sesión que llamaremos $_SESSION["iniciada"]. Así comprobaremos en las siguientes páginas que esta variable se está propagando con la sesión.
    Una tercera página propaga2.php que recogerá la variable de sesión. También se insertarán un par de formularios para ver cómo se reciben sus campos en la siguiente página.
    Una cuarta página propaga3.php que también recogerá la variable de sesión y los campos recibidos, en su caso, de la página anterior.

El código PHP de todos los ejemplos es similar, por lo que sólo se expone el código del ejemplo número 7 propagar SID con cookies o URL y con trans_sid, pues el más completo y el resto tiene muy pocas variaciones, en todo caso menos líneas de código. Las diferencias se exponen en cada apartado. Si lo desea puede descargar este archivo comprimido You are not allowed to view links. You are not allowed to view links. Register or Login or You are not allowed to view links. Register or Login (70 KB) con todos los códigos en formato de archivos de texto plano (con extensión cambiada a "txt").
#525
Back-end / Re:Sesiones
Enero 31, 2013, 12:15:31 AM
Propagar sesiones

El identificador de la relación servidor-cliente en una sesión

En los temas anteriores se hizo una exposición de las sesiones realizando ejemplos con sesiones basadas en cookies. Parecen ser la forma más segura pero también hay otras formas de propagar el identificador de sesión, que recordemos que es un vínculo que relaciona el servidor con un cliente determinado. Este identificador debe alojarse en el navegador del cliente con la finalidad de que en sucesivas peticiones sea remitido al servidor. Así éste podrá seleccionar las variables de sesión individuales para ese único cliente.

En el navegador del cliente podemos alojar el identificador de sesión de al menos tres maneras:

    En una cookie que envía el servidor y que el cliente devolverá con cada petición.
    El servidor lo aplica en todos los elementos que tengan un vínculo. Así el cliente cuando active unos de esos vínculos, el identificador llegará al servidor como un parámetro URL.
    Como campos ocultos de formularios que se envían por POST. Este es similar al anterior, aunque con importantes diferencias en cuanto a seguridad.

En el primer tema, en el apartado sobre el proceso de una sesión PHP, expusimos las siguientes configuraciones de PHP relacionadas con la forma en que se propagan las sesiones.

    session.use_cookies: Con valor activado PHP usará cookies para la propagación, pero si éstas estuvieran desactivadas en el navegador entonces usará URL a menos que la siguiente opción esté activada. Si está desactivado no usará cookies en ningún caso.
    session.use_only_cookies: Si ésta y la anterior están activadas, sólo se usarán cookies. Si ambas están desactivadas sólo se usará URL.
    session.use_trans_sid: Para el caso de URL, si ésta opción está activada, el servidor construirá parámetros URL en todos los elementos que contengan vínculos en la página. El parámetro será el SID, una cadena que concatena el nombre de la sesión y el identificador interponiendo el signo igual nombre_sesión=identificador_sesión, siguiendo los estándares de paso de parámetros por la URL. Por eso la denominación usar SID transparente (use_trans_sid), pues se adjunta el SID en los vínculos de forma transparente para el programador. Los elementos afectados por el SID pueden configurarse en la opción url_rewriter.tags, cuyo valor por defecto es a=href,area=href,frame=src,input=src,form=fakeentry,fieldset=. Se trata de una cadena de parejas elemento=atributo, de tal forma que es en ese atributo donde se incorporará el identificador. Si por ejemplo en nuestro HTML tenemos un elemento como <a href="otra-pagina.html">... entonces PHP agregará <a href="otra-pagina.html?SID" >..., . Para el elemento <form> si se incluye la palabra clave fakeentry entonces PHP incluirá una nuevo campo de texto oculto con el identificador. Si no queremos esto hemos de cambiarlo por form=action para que lo agregue a la URL. Si desactivamos session.use_trans_sid tendremos que agregar manualmente los SID en el código a cada vínculo.

CitarPHP dispone de la constante SID que se actualiza después de hacer un session_start() con el mismo resultado que la cadena session_name()."=".session_id(). Pero en este documento cuando hablemos de SID en general nos estaremos refiriendo a la pareja nombre e identificador de sesión, que son los dos valores a propagar para conseguir la identificación, mientras que escribiremos constante SID en los casos en que deseemos referirnos a esa propia constante de PHP.

En base a como se combinan estas opciones, que son configurables en tiempo de ejecución con ini_set(), podemos establecer la forma de propagación. En esta tabla resumimos todos los ejemplos que desarrollaremos en esta página, donde 1 indica activado y 0 desactivado:




Propagaciónsession.use_cookiessession.use_only_cookiessession.use_trans_sidurl_rewriter.tagsNotas
Sólo cookies 1 1 0 indiferente(indiferente) Opción por defecto en php.ini 5.2.13
Sólo URL con trans_sid y form=fakeentry 00 1 ...form=fakeentry... 
Sólo URL con trans_sid y form=action001...form=action...
Sólo URL sin trans_sid000indiferente
Cookies o URL con trans_sid101...form=fakeentry...
Cookies o URL sin trans_sid100indiferenteUsando constante SID
Sólo POST000indiferenteIgual que sólo URL sin trans_sid, pero sólo usando POST

La razón de tantas posibilidades se debe a que el servidor, en este caso PHP, trata de cumplir el estándar RFC2965 sobre el mecanismo de estado de sesiones basadas en cookies, pues en su punto 2 expone que "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 si el navegador no acepta cookies, el servidor podría disponer de otras posibilidades para continuar usando sesiones. Aunque también se advierte que la forma más segura es usando sólo cookies, como veremos en un tema posterior sobre seguridad.
#526
Back-end / Re:Sesiones
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: text
<?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_base, 0, $mitad);
    $_SESSION["iniciada"]  = $llave_servidor;
    $llave_cliente = substr($llave_base, $mitad, $longitud);   
    setcookie("llave-sesion", $llave_cliente, 0,  dirname($_SERVER["PHP_SELF"]));
} else {//CON SESIONES INICIADAS
    if ($hay_post && $finaliza_sesion){
        $_SESSION = array();
        setcookie(session_name(), "", time()-42000, dirname($_SERVER["PHP_SELF"]));
        setcookie("llave-sesion", "", time()-42000, dirname($_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: text
...
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: text
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.
#527
Back-end / Re:Sesiones
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: text
...
//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.
#528
Back-end / Re:Sesiones
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: text

//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: text
<?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: text
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: text
...
...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.
#529
Back-end / Re:Sesiones
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:


CitarLa 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.
#530
Back-end / Re:Sesiones
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.
#531
Back-end / Re:Sesiones
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: text
; 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: text
...
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: text

<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?
#532
Back-end / Re:Sesiones
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: text

; 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: text
-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: text
<?php
readfile('/etc/passwd');
?>


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

Código: text
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.

CitarLas 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
#533
Back-end / Re:Sesiones
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.
#534
Back-end / Re:Sesiones
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: text
# 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: text
# 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: text
... 
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: text
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: text
; 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: text
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: text
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.
#535
Back-end / Re:Sesiones
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 You are not allowed to view links. You are not allowed to view links. Register or Login or You are not allowed to view links. Register or Login 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: text

//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: text
<?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 You are not allowed to view links. You are not allowed to view links. Register or Login or You are not allowed to view links. Register or Login 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.
#536
Back-end / Re:Sesiones
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: text
<?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: text
...
$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.
#537
Back-end / Re:Sesiones
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: text
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: text
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".
#538
Back-end / Re:Sesiones
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: text

; 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: text
; 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   

   

CitarAlgunas 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%).
#539
Back-end / Re:¿Que es un framework?
Enero 30, 2013, 11:50:55 PM
You are not allowed to view links. You are not allowed to view links. Register or Login or You are not allowed to view links. Register or Login
Sólo he usado el JQuery como framework de javascript pero en php no jeje, me recomiendas alguno que hayas usado?

PD: Te faltó el famoso pasado de moda You are not allowed to view links. You are not allowed to view links. Register or Login or You are not allowed to view links. Register or Login ;)

Zalu2

y kohana, y RainFramework.

kohana me dio muy buen resultado, Rain es un famoso gestor de plantillas que funciona muy bien, es muy liviano y a mi me encanta, tienen un frameworok, quizá sea bueno.

saludos!
#540
Back-end / Re:Sesiones
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
<?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();
//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"])){
    //Para evitar la fijación de sesión regeneramos el id
    //cuando la sesión no haya sido iniciada por el cauce previsto
    session_regenerate_id(true);
    //Con sesiones no iniciadas ponemos un color rojo
    $un_color_fondo = "red";
} 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"], 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"] = $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">
...
<!-- Insertamos en el body el color de fondo -->
<body style="background-color:<?php echo $un_color_fondo; ?>;" >
    ...   
    <h3>Página 2</h3>
    <p>Esta página2 tiene el valor
    <big><big><?php echo $un_color_fondo;?></big></big>
    para la propiedad <code>background-color</code> del cuerpo del documento.
    </p>
    <?php if($un_color_fondo == "red"){?>
        <p style="font-size: 2em;">Ha llegado a esta página sin antes haber pasado
        por la página 1.</p>
    <?php } else {?>
        <!-- Este formulario sirve para que el usuario seleccione otro color para
        el fondo. Al enviarlo se recoge en esta misma página, pues en el inicio
        hemos puesto el script para ello. -->
        <form action="pagina2.php" method="post">
            <label>Seleccione color para el fondo:
                <select name="color">
                    <option value="yellow"
                    <?php if ($un_color_fondo == "yellow") echo "selected='selected'" ?>
                    >amarillo (yellow)</option>
                    <option value="aqua"
                    <?php if ($un_color_fondo == "aqua") echo "selected='selected'" ?>
                    >azul (aqua)</option>
                    <option value="lime"
                    <?php if ($un_color_fondo == "lime") echo "selected='selected'" ?>
                    >verde (lime)</option>
                </select>
            </label>
            <input type="submit" value="enviar" />
        </form>       
    <?php } ?>
    ...(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: text
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
HTTP/1.1 200 OK
Date: Thu, 16 Sep 2010 09:41:45 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
Set-Cookie: PHPSESSID=nlikqad9ib1563tgod36ggfa84; path=/
...
<!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">
...
<body style="background-color:red;" >
    ...
    <p>Esta página2 tiene el valor <big><big>red</big></big>
    para la propiedad <code>background-color</code> del cuerpo del documento.</p>
    <p style="font-size: 2em;">Ha llegado a esta página sin antes haber pasado
    por la página 1.</p>
    ...


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.