Archivo de Marzo de 2006

Novedoso sistema para combatir el SPAM!!!

Jueves, 30 de Marzo de 2006

Señoras y señores, les presento a continuación un novedoso sistema para combatir el SPAM: MailAnihilator 2006. ¡Todo un descubrimiento!

MailAnihilator 2006 es un novedoso sistema que con el más avanzado filtro bayesiano (si el mail existe es considerado SPAM), retroalimentado por el más efectivo blacklist (un DNS que lista todos los dominios de Internet excepto el propio), elimina con un 100% de efectividad todos los correos que ingresen a su dirección de email. El resultado, ¡una casilla limpia de SPAM garantizada y de por vida!

Ja! Un poco en broma quiero transmitir el enojo que como usuario de Internet tengo con los irresponsables controles anti-SPAM que muchos servicios de correo están llevando a cabo. No solo no solucionan el verdadero problema, el SPAM, sino que además tienen una alta tasa de “falso positivos”, y de generación de SPAM Colateral. Dicho de otra forma, muy usualmente confunden los correos legítimos con SPAM, marcándolos como SPAM, o lo que es peor, haciendo rebotar correos legítimos, o como consecuencia de los supuestos controles, generan más correos en la red, que terminan siendo también correo basura.

Si alguno alguna vez envió un correo y el mismo no llegó al destinatario ¡levante lamano! Puede haber sido víctima de un ridículo control anti-spam que confundió su correo legítimo con uno que no lo era.

Voy a comentar solo algunos de los patéticos controles que están efectuando algunos hosts de Internet en sus servidores de correos o redes, supuestamente para combatir el SPAM y los resultados colaterales que están teniendo en la calidad del servicio al rechazar por SPAM correos legítimos. Lo que llamaremos de aquí en más “falsos positivos”. Mucho de esto es volcado de la experiencia que tengo con los sistemas de correo por YoReparo.com En YoReparo enviamos correos en forma de notificaciones del foro, o por el newsletter del sitio más de 40.000 personas registradas. Así que esto me ha dado la chance de ver rebotes de todos los colores junto con las ridículas excusas para los mismos.

Control Anti-SPAM ridículo Nro 1 “Por favor, confirme que usted no es un spammer.”

Arrancamos con el más suave de los casos de controles anti-spam ridículos, pero que aún así resulta una molestia importante para los usuarios intensivos del correo electrónico (al menos para mí :) ).

El control funciona de lo siguiente forma:

1°) Tu envías un correo a un amigo.

2°) El servidor de correo del amigo recibe tu correo, verifica si el amigo te tiene en su lista de recipientes conocidos. Si no estás en esa lista, el sistema te devuelve un correo en el que dice algo así como “Hola, estoy usando un super sistema contra el SPAM que consta de verificar manualmente el envío de su correo, por favor visite el siguiente enlace y llene el formulario allí presente para confirmar que su correo no es un SPAM”.

3°) Con muchas suerte solo hay que clickear el enlace y el correo es aceptado. El caso más general es que estos sistemas paranoicos querrán confirmar que el que clickea el enlace es un ser humano y no un maligno robot programado por un huberspammer, por lo tanto utilizan un formulario con una imagen para confirmación visual. Esas famosas imágenes que muestran con tipografía estrafalaria números y letras, de forma que supuestamente un programa no pueda leerlos pero un ser humano sí. La verdad, a veces ni un humano puede y hay que adivinar qué es lo que dice, para luego de varios intentos embocar al código. Ingresamos el código visto en la imagen para que nuestro correo sea aprobado, y nuestra dirección de correos pase a ser aprobada para futuros envíos a este destinatario.

¿Por qué pienso que este control apesta, y pasa a ser más parte del problema que parte de la solcuión?

1° Todo sistema que para solucionar el problema del SPAM requiere del envío de más correos (en este caso se envía un correo desde el servidor para comprobar que hayun ser humano del otro lado), pasa a ser parte del problema pues genera lo que se llama SPAM colateral. ¿Qué es el SPAM Colateral? Bien, ilustrémoslo con este caso. Supongamos que un gusano, se envió a miles de destinatarios que utilizan este sistema de control anti-SPAM utilizando nuestra dirección de correo. No es necesario para esto que nuestro equipo siquiera esté infectado, puede que el equipo de una amigo esté infectado, o nuestra dirección haya sido levantada de otro lado. No importa. El caso es que el gusano se envía diciendo que sale desde nuestra casilla de correo (falsear el remitente en un correo es la cosa más sencilla que se puede hacer en Internet), y llega a miles o cientas de personas con este tipo de control que envían un correo de vuelta para verificar que hay un humano del otro lado. ¿El resultado? Nuestra casilla estará tapada de correos de confirmación para un email que jamás enviamos: Spam Colateral, la solución pasó a ser parte del problema.

2° Mucho más sencillo aún, si envías muchos correos al día haciendo el primer contacto con gran cantidad de destinatarios, te verás buena parte del tiempo llenando los formularios de confirmación del tipo “Hey! Soy un humano! Acepta mi correo!”

3° Lo más sencillo de todo. ¿Qué tal si el correo de confirmación que envía el servidor nunca nos llegara, o simplemente no lo leemos a tiempo? ¿Que tal si enviamos el correo y simplemente apagamos el equipo? El envío de nuestro correo se demorará horas o hasta días. Nos hubiera resultado más rápido escribir la carta en papel y tinta, y llevarla hasta el destinatario montados en una mula.

4° Una razón trágica ¿Qué tal si nuestro propio servidor tiene el mismo sistema de control anti-SPAM ridículo instalado? Cuando llegue el primer correo del estilo “Hey! Confirme que es un humano” nuestro servidor le responderá de la misma manera. Ambos servidores no se reconocerán como remitentes legítimos y entrarían en loop reenviándose uno al otro indefinidos emails pidiendo “confirmación de que hay un ser humano del otro lado”.

Bueno, pretendo desarrollar y explicar otros dos controles antispam ridículos, y demostrar por qué son tanta parte del problema como lo son de la solución.
Control Anti-SPAM ridículo Nro 2 “Resolución de reversos.”

Control Anti-SPAM ridículo Nro 3 “Límite de SMTP Throttle.”

Como ahora estoy cansado los desarrollaré en otra entrada del blog. También voy a nombrar a algunos proveedores que utilizan estos controles anti-SPAM ridículos que terminan empobreciendo el servicio, perdiendo correos legítimos, y causando más problemas que soluciones, como lo son cantv.net, ifxnetworks.com, y otros que me iré acordando.

Saludos!

Horde IMP passwd “User not found”

Jueves, 16 de Marzo de 2006

Problema: Tienes IMP de Horde instalado, además tienes instalado una tostadora de Qmail con vpopmail con backend mysql compilado con --disable-many-domains. O sea, vpopmail está utilizando una tabla diferente para los usuarios de cada dominio. IMP passwd falla en actualizar el password del usuario y presenta u mensaje "User not found".

Solución: passwd no soporta vpopmail compilado para utilizar una tabla de usuarios por cada dominio. Hay que hacerle un par de retoques.

1°) Corregir el método _lookup de passwd/lib/Driver/vpopmail.php. Una versión correjida para la versión de passwd h3-3.0 sería:

PHP:
  1. function _lookup($username, $old_password)
  2. {
  3. /* Connect to the database. */
  4. $res = $this->_connect();
  5. if (is_a($res, 'PEAR_Error')) {
  6. return $res;
  7. }
  8.  
  9. /* Only split up username if domain is set in backend
  10. * configuration. */
  11. if (!empty($this->_params['domain'])) {
  12. list($name, $domain) = explode('@', $username);
  13. } else {
  14. $domain = $this->_params['domain'];
  15. $name = $username;
  16. }
  17.  
  18. if ($this->_params['disable_many_domains']) { // if disable_many_domains (we are using one table for each domain)
  19. $this->_params['table'] = str_replace(".","_", $domain); // build table name
  20. }
  21.  
  22. /* Build the SQL query. */
  23. $sql = 'SELECT ' . $this->_params['passwd'] .
  24. ' FROM ' . $this->_params['table'] .
  25. ' WHERE ' . $this->_params['name'] . ' = ?';
  26. $values = array($name);
  27. if ($this->_params['domain']) {
  28. $sql .= ' AND ' . $this->_params['domain'] . ' = ?';
  29. $values[] = $domain;
  30. }
  31. Horde::logMessage('SQL Query by Passwd_Driver_vpopmail::_lookup(): ' . $sql, __FILE__, __LINE__, PEAR_LOG_DEBUG);
  32.  
  33. /* Execute the query. */
  34. $result = $this->_db->query($sql, $values);
  35. if (!is_a($result, 'PEAR_Error')) {
  36. $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
  37. $result->free();
  38. if (is_array($row)) {
  39. /* Get the password from the database. */
  40. $current_password = $row[$this->_params['passwd']];
  41.  
  42. /* See if the passwords match. */
  43. return $this->comparePasswords($current_password, $old_password);
  44. }
  45. }
  46.  
  47. return PEAR::raiseError(_("User not found"));
  48. }

2°) Agregar en la configuración del backend de vpopmail (passwd/config/backends.php) un parametro llamado "disable_many_domains" seteado en true.

PHP:
  1. $backends['vpopmail'] = array (
  2. 'name' => 'Example Vpopmail Server',
  3. 'preferred' => '',
  4. 'password policy' => array(
  5. 'minLength' => 3,
  6. 'maxLength' => 8,
  7. 'maxSpace' => 0,
  8. 'minUpper' => 0,
  9. 'minLower' => 0,
  10. 'minNumeric' => 0
  11. ),
  12. 'driver' => 'vpopmail',
  13. 'params' => array(
  14. 'phptype'    => 'mysql',
  15. 'hostspec'   => 'localhost',
  16. 'username'   => '',
  17. 'password'   => '',
  18. 'encryption' => 'crypt',
  19. 'database'   => 'vpopmail',
  20. 'table'      => 'vpopmail',
  21. 'name'    => 'pw_name',
  22. 'domain'  => 'pw_domain',
  23. 'passwd' =>  'pw_passwd',
  24. 'clear_passwd' => 'pw_clear_passwd',
  25. 'use_clear_passwd' => true,
  26. 'show_encryption' => true,
  27. 'disable_many_domains' => true
  28. )
  29. );

No estoy metido en el desarrollo de horde, así que si alguien quiere submitir el patch, en caso de que la versión de CVS no esté correjida, por favor hágalo.

Es un fix tonto, pero puede sacar de un apuro a alguien.

Enfermedad de McFerson

Jueves, 16 de Marzo de 2006

Primer chistecito powered by carlospebe ® :

Un hombre muy enfermo espera su diagnóstico. Llega el médico con cara de afligido a
informarle:

- Mire, amigo, tendrá que ser fuerte, ¡usted tiene la enfermedad de McFerson!

- ¡Dios mío! ¿Y eso es grave?

- Tomará su tiempo descubrirlo, señor McFerson.

Manejo de sesiones en php

Jueves, 16 de Marzo de 2006

Nota: Lamentablemente algunos ejemplos de este artículo se verán momentáneamente rotos, pues el plugin de hilite de sintáxis de wordpress no funciona muy bien, y omite el código que está escrito dentro de bloques de código de php. En cuanto arregle el plugin o salga un fix se verán los ejemplos al 100% nuevamente. Si alguien conoce una solución por supuesto se agradecerá el dato.

Introducción

En este artículo vamos a explicar el manejo de sesiones en PHP, su utilidad, las funciones y las directivas de configuración implicadas en el manejo de sesiones.

¿Qué son las sesiones?

Primero que nada, ¿qué son las sesiones de PHP? Las sesiones son una facilidad que permite vincular información a un visitante a lo largo de sus diversos accesos a nuestro sitio web. Un visitante puede acceder a varias páginas de nuestro sitio, las sesiones nos ayudan a identificarlo y a vincularle información.

Algunos usos típicos de las sesiones son:

Mejorar la experiencia del usuario almacenando información de preferencias como colores o elementos de navegación preferidos.

Almacenar información de estado (ej: si el usuario está logeado o no).

En el caso de una aplicación de comercio electrónico llevar registro de los productos que ha agregado a su carro de compras, o de productos que ha comprado anteriormente para establecer sus preferencias.

Estos son solo unos ejemplos pero las posibilidades son infinitas. En general, cuando querramos almacenar información en relación a un visitante de forma persistente a lo largo de su visita las sesiones nos facilitarán la vida.

¿Cómo funcionan las sesiones en PHP?

Podríamos imaginarnos a la sesión como un número estampado en la frente del visitante que nos ayuda a identificarlo en cada página que visite de nuestro sitio. Además para cada cada número asignado a un visitante habrá un locker donde guardar sus cosas. Así es que quien está a la entrada (nuestro código de manejo de sesiones) al llegar un visitante escoge un número para él y se lo estampa en la frente para que lo utiliice durante su visita.

Para poner a funcionar una sesión se necesitan cumplir dos requisitos fundamentales:

  1. Asignar al visitante un identificador único (escoger un número).
  2. Propagar este identificador único a través de los diferentes accesos del visitante para que podamos reconocerlo (estampárselo en la frente).

Nuestro identificador único es una cadena única aleatoria con la que marcamos al visitante. Llamaremos a este identificador único session_id. No tendremos que preocuparnos por generar el session_id pues el sistema de sesión se encarga automáticamente de generarlo.

Ahora, dado que el session_id es generado del lado del servidor, hay que encontrar alguna forma de que esa información persista a través de los diferentes accesos del mismo visitante. No es posible vincular el session_id con la dirección IP del usuario ya que hay varios casos en que la misma IP puede ser utilizada por varios usuarios, por ejemplo cuando varios usuarios están tras un proxy o tras un router que está haciendo IP masquerade. Por lo tanto para que el session_id esté disponible junto a cada solicitud de página del usuario es necesario propagar el session_id para que esté presente junto al visitante en cada acceso y así podamos identificarlo. Esto se llama propagación de la sesión y más adelante veremos cómo se hace.

Creando una sesión

Cada sesión comienza con la función session_start(), de la forma:

bool session_start(void)

session_start() siempre devuelve true. En caso de que el visitante no tenga una sesión la crea y en el caso de que ya la tenga la continúa. Si ya tenía una sesión tiene que haber una forma de que podamos vincularla al usuario. Para ello el session_id tiene que haber sido pasado a la página actual, y esto se hace por cookie, por GET o por POST. Ten en cuenta que si la propagación es por cookie session_start() se encargará de crear un cookie, entonces es necesario llamarla antes de enviar cualquier cabecera HTTP, esto es antes de comenzar a imprimir nuestra página pues sino habrá un error al tratar de crear el cookie.

Una vez iniciada la sesión podremos almacenar información de la sesión en el array $_SESSION ( o $HTTP_SESSION_VARS en versiones anteriores a PHP 4.0.6). Este es una array asociativo, además es superglobal lo que quiere decir que su alcance se extiende a todo el ámbito de la aplicación y no tendrás que declararlo como global al utilizarlo dentro de funciones.

Hagamos un ejemplo. Supongamos que tenemos en línea una página con la foto de George la cacatúa con el siguiente código:

Ejemplo 1 (george_la_cacatua.php)

PHP:
  1. <h1>stás en la página de George la cacatúa</h1>
  2. <img title="George la cacatúa" src="http://localhost/blog/wordpress/cacatua.jpg" /> <a href="http://localhost/blog/wordpress/pagina2.php">Visita la siguiente página</a>

session_start() comienza la sesión si no la hay, o continúa con la sesión del visitante si esta ya existía; la siguiente sentencia se encarga de guardar en la posición "actividad" del array asociativo de la sesión la cadena: "¡He visto a George la cacatúa!". Esta información estará disponible aún cuando el visitante acceda otras páginas de nuestro sitio, y esta es la magia de las sesiones.

Podemos hacer sobre el array $_SESSION cualquier operación que podemos hacer sobre una un array. Esto nos dá una interfaz muy intuitiva y familiar para operar con la información de la sesión del usuario.

Operación Ejemplo
Crear un elemento $_SESSION['elemento'] = valor
Eliminar un elemento unset($_SESSION['elemento'])
elemento ha sido creado isset($_SESSION['elemento'])
Vaciar la sesión $_SESSION = array();

Existe como alternativa al uso directo del array $_SESSION el uso de las funciones session_register(), session_unregister(), session_is_registered() y session_unset() para operar sobre la información de la sesión. Sin embargo, existen varios problemas en su uso que pueden hacernos introducir fallos. session_register() registra variables globales por lo que nos tendremos que cuidar en el alcance de las variables que registremos. Estas funciones solo trabajan cuando la directiva register_globals de nuestro php.ini está activada, cosa que no es nada recomendable por motivos de seguridad, y por lo mismo desde PHP 4.2.0 en adelante el valor por defecto de register_globals es off. Así que no es nada recomendable utilizar estas funciones, pero si vas por ellas de todas formas o tienes un código que ya las utiliza abundantemente ten en cuenta que no se deben utilizar el método de acceso directo al array $_SESSION y las funciones al mismo tiempo, se debe utilizar uno o el otro.

Sigamos pensando en nuestro ejemplo. Ahora, al visitar la página de George la cacatúa nuestro visitante hace click en el enlace a pagina2.php, el código de esta página es así:

Ejemplo 2 (pagina2.php)

PHP:

De nuevo session_start() crea o continúa la sesión, como nuestro visitante ya tiene una sesión la continúa, cargando sus datos en el array $_SESSION. Entonces por arte de magia la sentencia con el echo imprimirá:

¡He visto a George la cacatúa!

¿Cómo ha llegado el valor de $_SESSION['actividad'] hasta esta página? Ha llegado por la sesión del visitante. Los valores almacenados en la sesión son serializados en un archivo al cerrar cada script y al continuar la sesión en otra página son cargados en el array $_SESSION.

El archivo de la sesión suele estar en el directorio /tmp en los sistemas UNIX o en c:\tmp en windows y tiene por nombre el session_id del visitante. Podemos configurar dónde queremos serializar las sesiones con la directiva session.save_path de nuestro php.ini o en tiempo de ejecución mediante la función session_save_path() de esta forma:

string session_save_path(string);

Si llamamos a session_save_path() sin parámetros nos devuelve el save_path actual, si le pasamos una cadena la toma como el nuevo save_path. Si vas a modificar el save_path con esta función tienes que llamarla antes de llamar a session_start().

Ahora ¿Cómo llega el llamado a session_start() en pagina2.php a conocer que el visitante ya tiene una sesión y continuarla? La única forma que tiene de hacerlo es que le proporcionemos de alguna forma el session_id del visitante, esto es lo que se logra por la propagación del session_id y vamos a explicar en datalle a continuación.

Propagación del session_id

Hay dos formas de propagar el session_id:

  1. Propagación por URL
  2. Propagación por cookie

Propagación por URL

La propagación del session_id por URL implica escribir el session_id en el query string de cada enlace interno de nuestro sitio. Un enlace con un session_id podriá verse de la siguiente forma:

PHP:
  1. <a href="http://localhost/blog/wordpress/pagina2.php?PHPSESSID=F513fad624vDx3">Visita la siguiente página</a>

Donde la variable PHPSESSID del query string se está encargando de pasar el session_id para la sesión del visitante a la página pagina2.php. El nombre de la variable que porta el session_id es igual al nombre de la sesión, y puede definirse con la directiva session.name de nuestro php.ini o en tiempo de corrida por la función session_name().

Para hacer más fácil la escritura del session_id en cada URL está disponible la constante SID que contiene la cadena "nombre_de_sesion=session_id", que en nuestro caso sería "PHPSESSID= F513fad624vDx3".

¿Cómo quedaría nuestro ejemplo de George la cacatúa si propagáramos el session_id por URL? Aquí está nuestra segunda versión de la página:

Ejemplo 3 (george_la_cacatua_reloaded.php)

PHP:

Estás en la página de George la cacatúa

Visita la siguiente página [/php]Nota que en el enlace a pagina2.php hemos embebido un echo de la constante SID, por lo que nuestro enlace se vería así:

PHP:
  1. <a href="http://localhost/blog/wordpress/pagina2.php?PHPSESSID=F513fad624vDx3">Visita la siguiente página</a>

Propagación automática del session_id en cada URL

Por suerte y para hacerlo más fácil PHP puede encargarse de transformar por nosotros todos los enlaces internos de nuestro sitio para que incluyan el session_id. Esto depende de dos cosas, de que php esté compilado para hacerlo (con soporte para trans_sid) y de que la directiva session.use_trans_sid en nuestro php.ini esté activada (valor igual a "1"). PHP 4.2.0 viene compilado por defecto con esta funcionalidad, si utilizamos de PHP 4.1.2 para atrás tendremos que recompilar PHP pasando el parámetro --enable-trans-sid al script configure antes de la compilación (en UNIX).

Por motivos de seguridad la directiva session.use_trans_sid viene con el valor "0" por defecto, de modo que para habilitarla deberemos editar nuestro php.ini o utilizar la función ini_set().

ini_set("session.use_trans_sid", "1");Si la directiva está habilitada y PHP ha sido compilado con soporte para trans_id entonces un enlace escrito de la siguiente forma en nuestra página:

PHP:
  1. <a href="http://localhost/blog/wordpress/pagina2.php">Visita la siguiente página</a>

será transformado automáticamente por PHP en el siguiente enlace:

PHP:
  1. <a href="http://localhost/blog/wordpress/pagina2.php?PHPSESSID=%20F513fad624vDx3">Visita la siguiente página</a>

propagando por nosotros el session_id en cada URL.

Propagación por cookie

¿Cómo funciona la propagación por cookie? De estar habilitada la propagación por cookie session_start() buscará en un cookie el session_id del visitante, si no lo encuentra creará una nueva sesión, creará un cookie para la sesión y almacenará en el el session_id del visitante. En un próximo llamado de session_start(), la función leerá el cookie que contiene el session_id y podrá continuar la sesión correspondiente.

Como vemos session_start() se encarga de crear y leer el cookie, así que el proceso es automático y no tendríamos que preocuparnos por él.

En nuestro Ejemplo 1 y Ejemplo 2, de George la cacatúa y la pagina2.php, asumimos que el session_id se propaga desde la primer página a la segunda por cookie. De no haber estado habilitada la propagación por cookies en pagina2.php no se hubiera continuado la sesión del visitante pues tampoco estábamos haciendo propagación del session_id por URL. La sesión hubiera quedado desvinculada del visitante y el echo $_SESSION['actividad']; no hubiera mostrado nada.

Como en el caso de la propagación por URL, el nombre del cookie que contiene el session_id es el mismo que el nombre de la sesión. Por defecto es PHPSESSID pero podemos modificarlo con la directiva session.name de nuestro php.ini o con la función session_name().

La propagación por cookie estará habilitada según el valor de la directiva session.use_cookie en nuestro php.ini. El valor por defecto es "1" o sea que está activa. Si queremos desactivar la propagación del session_id por cookie debemos cambiar session.use_cookie a "0" en nuestro php.ini, o si no tenemos acceso al php.ini o queremos cambiarla en tiempo de corrida a nivel de aplicación, podemos hacerlo mediante la función ini_set() de la siguiente forma:

ini_set("session.use_cookie", "0");

Cerrando una sesión

Hasta ahora hemos visto como abrir o continuar una sesión con session_start(), cómo registrar valores en la sesión haciendo asignaciones sobre el array $_SESSION, y cómo propagar el session_id por cookies o por URL; ahora veremos como cerrar una sesión.

Para cerrar una sesión y terminarla completamente tenemos la función session_destroy(). Esta función se encarga de hacer desaparecer la sesión de nuestro servidor, (elimina el archivo de /tmp o de nuestro session.save_path). Si se solicita una página y se le envía el session_id de una sesión destruída session_start() reportará un error pues la sesión no existe. session_destroy() no elimina las posibles variables globales creadas con la sesión (correspondientes a cada posición de $_SESSION si teníamos activado register_globals) ni vacía el array $_SESSION. Por supuesto que esto ocurrirá al cerrar el script, pero si queremos asegurarnos de que la sesión muera al instante que hacemos el session_destroy() tendremos que encargarnos de vaciar la sesión nosotros mismos. Así es que una destrucción típica de sesión se vería así:

Ejemplo 4 (destruyendo una sesión)

PHP:

¿Sesiones Vs. cookies?

No es una comparativa que sea necesaria llevar a cabo. Son herramientas diferentes y muchas veces complementarias. Pero si conoces el uso de los cookies posiblemente te hayas planteado que las sesiones te permiten hacer exactamente lo mismo que los cookies. Pues bien, te mencionamos al menos dos cosas que no puedes lograr con cookies para que veas la diferencia:

No depender del uso de cookies. Bastante obvio, si utilizas cookies dependes de ellos. ¿Por qué querría alguien no depender de cookies? Porque por principio de diseño no es bueno depender de ellos en el caso de funcionalidades medulares de nuestra aplicación web. ¿Por qué? Por el simple hecho de que muchos usuarios pueden no aceptar los cookies, o no aceptar todos los cookies que les enviamos. Si utilizas sesiones puedes no depender del uso de cookies propagando las sesiones por URL.

Mantener la información fuera del alcance del cliente. Muchas veces la información que quieres almacenar sobre el visitante no debería ser accedida por él, por ejemplo información administrativa o información de estado de un usuario (¿está logueado?). No hay forma de lograr esto con cookies pues los cookies están al alcance del visitante, se almacenan en el cliente y pueden ser modificados por él. En el caso de las sesiones no es así pues la información de cada sesión se almacena en el servidor fuera del alcance del visitante.

Consideraciones de seguridad en el manejo de sesiones

En la propagación del session_id

En la propagacción del session_id es donde encontramos el punto en el que más cuidado tendremos que tener al manejar sesiones. Vamos a discutir dónde se encuentran las posibles brechas al sistema y algunas medidas defensivas que podemos tomar. En todos los casos mencionaremos las brechas conocidas, aunque la posibilidad de que alguien las explote sea tan pequeña que parezca ridículo mencionarlas. La relevencia de cada una de ellas la juzgaremos según el sistema que estemos desarrollando.

Brechas de seguridad en la propagación del session_id por URL

Como hemos dicho anteriormente en este método el session_id se pasa de una página a otra de nuestra aplicación web incluyéndolo como una variable en el query string de cada URL. Una URL con session_id podría verse así:

PHP:
  1. http://www.example.com/index.php?PHPSESSID=A86fad765xvc

De modo que con tan solo conocer uno de los URLs de nuestro sitio que el usuario legítimo está visitando un atacante podría tomar su papel ante nuestro sistema. El método de realizar el ataque es muy sencillo, pues solo se tiene que acceder a nuestro sitio con un URL que incluya la variable PHPSESSID con el valor substraído. Por ejemplo:

PHP:
  1. http://www.example.com/account.php?PHPSESSID=A86fad765xvc

Así que la brecha de este sistema de propagación del session_id se presenta en cualquier situación en la que el URL pueda llegar a ser descubierto. Planteemos algunos escenarios en los que esto puede suceder:

1) Enlaces a páginas externas.Si tenemos enlaces a páginas externas hemos de cuidarnos de no imprimir el sesion_id en ellos. Si estamos utilizando la transformación de URLs de PHP para que inserte el session_id por nosotros no nos enfrentaremos a este riesgo, pues PHP solo reescribe con el session_id las URLs relativas, dejando intactas las URLs absolutas que son usualmente las que llevan a otros sitios.El riesgo que se corre al pasar el session_id a un sitio externo es que un webmaster malintencionado capte ese session_id y lo utilice. Así que allí nuestro primer punto a cuidar, no imprimir el session_id en enlaces a sitios externos.

Dentro del mismo caso hay otra brecha. La mayoría de los navegadores web incluyen la URL del referer en cada solicitud a un servidor web. El referer es literalmente el referido, o el sitio que está enviando la visita. La información del referer suele almacenarse en los logs de los servidores web para hacer análisis de tráfico. Lo cierto es que si desde nuestro sitio el usuario legítimo hace click en un enlace externo, aunque nos hallamos cuidado de no imprimir en él es session_id es muy probable que el sitio remoto reciba la información del referer, que en este caso es la URL completa de nuestro sitio incluyendo el query string donde está el session_id. Si el servidor remoto está almacenando en sus logs los referers tendrá allí el session_id de nuestro usuario el cuál podrá ser utilizado para impersonarlo. ¿Podemos evitar que se envíe el referer al sitio remoto? No, de ninguna manera, pues esto es algo que hace el navegador. El único caso en el que los navegadores no envían el referer es en el caso que se proceda desde una conexión segura. Es decir que si nuestro sitio está implementado con HTTP sobre SSL ( o https), el navegador no enviará la información de referer al sitio externo. Así es que si tenemos enlaces externos y no queremos que nuestro session_id llegue a ser revelado por el navegador tendremos que implementar nuestro sitio sobre https. Esto nos protegerá además en el caso de que un atacante muy dedicado esté a la escucha de nuestro tráfico de red con un sniffer, leyendo los requests de HTTP a nuestro servidor web.

2) Brechas introducidas por el usuario.Puede que hayamos hecho todo para que el session_id no pase a sitios externos, pero aún así hay situaciones en las que el URL puede ser tontamente revelada por nuestro propio usuario legítimo. Desconociendo la relevancia de la información en el URL nuestro usuario podría pasar un enlace de nuestro sitio a otra persona, vícitima de ingeniería social, dándole sin saber la información necesaria para que tome su rol. Además, el URL puede ser agregado en los Favoritos por nuestro usuario o quedar guardado en el cache del navegador o historial, donde otro usuario de la misma máquina pueda leerlos y utilizarlos o donde un usuario remoto puede substraerlos en alguna oportunidad. De modo que por parte del usaurio hay también gran riesgo de revelar el session_id a un atacante.

Brechas de seguridad en la propagación del session_id por cookies

En este método el session_id se escribe en un cookie en el sistema del usuario así que la seguridad se comprometerá en cualquier caso en el que el contenido del cookie sea revelado.

En la propagación por cookie existen muy pocos casos en los que el session_id pueda ser revelado. No hay posibilidad de que el visitante revele el cookie al visitar otro sitio o al pasar un enlace de nuestro sitio a otra persona. El atacante tendría que ser muy dedicado y estar escuchando el tráfico de nuestra red para "ver pasar" el cookie o tendría que atacar directamente a la máquina de nuestro usuario para substraerle el cookie. De nuevo, podemos protegernos de la posibilidad de un siffer escuchando el tráfico de red utilizando https y enviando el cookie de la sesión solo sobre https.

Si optamos por propagar la sesión por cookie además de habilitar session.use_cookies en nuestro php.ini o por ini_set(), será bueno deshabilitar la propagación del session_id en las URLs, podremos hacerlo asegurándonos de que esté deshabilitada la directiva session.use_trans_id de nuestro php.ini y en el caso de php 4.3.0 en adelante habilitando session.use_only_cookies para asegurarnos que el session_id solo se propague por cookies.

Permisos de sistema de archivos en nuestro session.save_path

Como ya hemos mencionado la información de cada sesión se almacena en un directorio establecido por la directiva session.save_path, si dicho directorio fuera legible por otros usuarios del sistema estos podrían leer los session_id y la información de cada sesión de usuario. Hemos por lo tanto de establecer restricciones a nivel del sistema de archivos para impedir que usuarios no autorizados de nuestro propio sistema tengan acceso a las sesiones de usuario.

Algunas medidas de defensa

En todos los casos dos buenas medidas de defensa que podemos llevar a cabo son, hacer expirar las sesiones a un tiempo razonable, y conservar información de la sesión que nos ayude a detectar cualquier anomalía.

Un método muy común es llevar un registro de sesiones, (en una tabla o archivo) donde incluyamos de cada sesión de nuestro sistema la hora de última actividad de la sesión y la dirección IP del usuario que comenzó la sesión. En cada acceso del usuario haremos dos controles, que no haya pasado cierto tiempo preestablacido desde la última actividad y que la IP del usuario sea la misma que la IP del usuario que comenzó la sesión. Si la sesión ha expirado podremos destruirla y solicitarle que comienze una nueva sesión. Si la IP de usuario que hace el acceso es distinta a la IP del usuario que comenzó la sesión habrán dos posibilidades, que el usuario haya rotado la IP como sucede bajo ciertas conecciones, como las establecidas sobre PPPoE, o que se trate de alguien impersonando a nuestro usuario legítimo. En todos los casos, no debería suceder que el usuario rote su IP, o es al menos un caso muy poco probable, por lo que sería bueno hacer este control y caducar la sesión para impedir un posible ataque.

¡A blogear se ha dicho!

Jueves, 16 de Marzo de 2006

Desde hoy, elmaster.com que fuera mi sitio de "programador web freelancer" por el cual ofrecía servicios de programación web, y otros servicios de la profesión, ha pasado a ser un blog. De pronto me dí cuenta que estoy trabajando en más proyectos de los que voy a poder abarcar en los próximos años de vida, así que mejor, en vez de seguir promocionandome para trabajos que no podré abarcar, utilizaré este espacio para entretenerme, y de paso, volcar algo de lo que he aprendido, o vaya aprendiendo en lo profesional, y en la vida.

La primer tarea ha sido pasar el artículo de manejo de sesiones en php a mi blog, todavía le faltan detalles pero los iré corrijiendo en los próximos días.

¡A blogear se ha dicho entonces!