Autenticación segura usando Ajax Imprimir
Escrito por edgaragg   
Martes, 14 de Abril de 2009 10:26

Los ultimos días he estado dedicado a investigar un poco acerca de un problema que a estado dando vueltas en mi cabeza.  Hace un tiempo apareció una noticia acerca de un nuevo ataque que hace al protocolo SSL inseguro.  Especificamente en Black Hat 2009, se publicó la herramienta SSLStrip que permite realizar un ataque de tipo "man in the middle" engañando al usuario para hacerlo creer que se encuentra en un sitio de Internet con cifrado SSL (HTTPS). En realidad tus datos se están transmitiendo sin cifrado alguno (HTTP), pero la cosa no para ahí, SSLStrip también permite  engañar el servidor HTTP, haciéndolo pensar que el cifrado ha sido anulado pero en realidad el sitio sigue como si todavía utilizara SSL.

Si esto es así, resultaría realmente fácil obtener las contraseñas de cualquier sitio web.  Entonces ¿cómo podemos protegernos?

Investigando un poco he dado con una solución que si bien no es perfecta me parece que es lo suficientemente segura para ser incluso usada sin necesidad de SSL.  La solución está basada en el uso de autenticación por desafio/respuesta y mi propuesta se usa en combinación con AJAX para el intercambio de información con el servidor.

En primer lugar voy a explicar en que consiste la autenticación por desafío/respuesta y posteriormente pasaré a dar una explicación del algoritmo propuesto, así como algunas recomendaciones que tomo en cuenta actualmente para proveer de mayor seguridad el proceso de autenticación.


¿Qué es la autenticación por desafío/respuesta?

La autenticación por desafío/respuesta (CRA por sus siglas en inglés) es un método que permite prover la identidad de un usuario a través de un medio inseguro sin revelar información sensible que pueda ser usada por usuarios malintencionados para impersonar la identidad de otros usuarios.

Este método hace uso de los algoritmos de encriptamiento de una sola vía o de hash, como por ejemplo MD5, SHA-1 o SHA-512 y funciona de la siguiente manera:

Supongamos que tenemos una función de hash h(), cuando el cliente se conecta al servidor, este genera un valor X de forma aleatoría conocida como el desafío.  Este desafío es enviado al cliente.  Cuando el cliente tiene el valor de X calcula h(P+X) donde P es el password o contraseña y + es la concatenación de cadenas.  Este valor es la respuesta al desafío y se envía al servidor junto con el nombre de usuario.  Luego el servidor calcula de nuevo el valor h(P+X) para el nombre de usuario enviado y lo compara con el valor que envió el cliente, si coinciden entonces el usuario queda autenticado.

Como puede verse la contraseña nunca es enviada al servidor, en su lugar se envia un valor encriptado en una sola vía, por lo que la única forma de obtener la contraseña es usando fuerza bruta.  La otra ventaja es que el valor encriptado solo es valida para esa sesión puesto que si el mismo usuario intenta autenticarse posteriormente en otra sesión, se generaría otro desafío y como resultado se obtendría otra resuesta.


Algoritmo propuesto.

En primer lugar debo indicar que actualmente no acostumbro a almacenar la contraseña de ningún usuario como texto plano.  En su lugar la contraseña del usuario es almacenada usando un algoritmo de hash en combinación con un valor aleatorio generado al momento del registro del usuario conocido como salt. Esto provee una seguridad extra puesto que hace practicamente imposible obtener la contraseña de los usuarios si por alguna razón un atacante obtiene acceso al servidor (y especificamente a la tabla de usuarios en la base de datos)

De esta manera el pseudo-algoritmo indicado en el apartado anterior se ve modificado para aceptar el uso del valor salt.

El algoritmo de autenticación sería el siguiente:

  1. Una vez que el usuario llena el formulario de autenticación y presiona el botón para hacer login, se hace una llamada usando ajax enviando unicamente el nombre del usuario.
  2. El servidor valida la existencia del usuario, en caso de que el usuario exista se crea un valor aleatorio (el desafío) y se envía al cliente conjuntamente con el valor salt del usuario, el cual será usado para obtener el valor de la contraseña encriptada en el servidor.  El valor del desafío se almacena en sesión.
  3. El cliente obtiene el valor de la contraseña encriptada, primero concatenando el valor del salt y posteriormente aplicandole la función de hash.  En otras palabras:

    Contraseña encriptada = h(contraseña + salt)
  4. Se concatena el valor del desafío con la contraseña encriptada, obteniendose así el valor de la respuesta al desafío.  En otras palabras:

    Respuesta desafío = h(contraseña encriptada + desafío)

  5. La respuesta es enviada al servidor en conjunto con el nombre de usuario usando otra llamada ajax.
  6. El servidor calcula la respuesta al desafío de forma similar al paso 4, con la diferencia de que la contraseña encriptada es la almacenada en la base de datos para ese usuario.
  7. El servidor compara el valor de la respuesta calculado con el enviado por el cliente.  Si coinciden entonces el cliente conoce la contraseña del usuario y por lo tanto el mismo esta autenticado.  En caso contrario la autenticación falla.
En el paso 5 no es 100% necesario efectuar otra llamada ajax, podría hacerse un Post de un formulario, pero en este caso hay que asegurarse (usando JScript) que se envíe la respuesta del desafio y se evite enviar la contraseña en claro.  Lo que si es importante es hacer la llamada Ajax en el paso 1 con la intención de obtener el valor del salt y el desafío.

Registro de usuario seguro

Ahora tenemos un proceso de autenticación en principio segura, sin embargo, no es el único lugar en la aplicación donde un usuario puede enviar una contraseña como texto a través de la red.  Esto puede pasar también en el momento del registro, por lo que este proceso también debe llevarse a cabo de forma segura.  A continuación presento un algoritmo para llevar a cabo el registro del usuario de forma segura, este proceso debe hacerse de una forma muy  similar para el caso de cambio de contraseña:

  1. El cliente llena los datos de registro y presiona el botón para registrar. Al hacer clic sobre dicho botón se hace una llamada ajax al servidor enviando como parámetro unicamente el nombre de usuario  (Puede enviarse también el correo electrónico si se desea validar que el correo sea único)
  2. El servidor valida que el nombre de usuario no este repetido. (tambien verifica el correo si este fué enviado) Si no está repetido se genera un código aleatorio que será el salt y se envía al cliente.
  3. El cliente encripta la  contraseña concatenandola con el salt y aplicandole posteriormente la función de hash.  En otras palabras:

    Contraseña encriptada =  h(contraseña + salt)

  4. Se envía la contraseña encriptada al servidor junto con los otros datos del registro haciendo otra llamada ajax.
  5. El servidor almacena los datos del usuario junto con la contraseña encriptada.
En este caso, en el paso 4 se puede cambiar la llamada ajax por un post de un formulario.  De ser así, hay que asegurarse de enviar la contraseña encriptada en lugar de la contraseña en texto plano.


¿Cómo encriptar en el cliente?

El último obstaculo que debemos superar es como hacer la encriptación en el cliente.  Es recomendable usar un algoritmo de encriptación reconocido como MD5 o SHA1, SHA256 o SHA512 en lugar de implementar uno por cuenta propia.

Como la encriptación se debe llevar a cabo del lado del cliente hay que usar Javascript para llevarla a cabo.  Se puede conseguir la implementación de los algoritmos en Internet.   Aqui hay algunos enlaces donde se pueden conseguir:

Implementación de SHA-256, SHA-384 y SHA-512

Implementación de MD5 y SHA-1

Las implementaciones de MD5 y SHA-1 tambien posee funciones para aplicar el mecanismo de HMAC, el cual es bastante seguro y puede ser usado en una variación de los algoritmos propuestos anteriormente.

Actualizado ( Martes, 14 de Abril de 2009 15:07 )