Java EE: Seguridad en aplicaciones web (I)

Hace ya unos meses, publiqué una presentación que hice en la universidad sobre seguridad en las aplicaciones web, en concreto con Java EE. Ahora lo que me dispongo a hacer es repasar las partes más importantes de aquella charla en incluso aportar alguna cosa más sobre el tema después de haber estado trabajando con ello en la tienda online. Al ser un tema bastante amplio será distribuido entre varios artículos.

En esta serie de artículos aprenderemos a usar las herramientas que nos proporcionan las librerías de la empresa OWASP. También aprovecharemos para fijarnos en sus consejos ya que se dedican a estudiar entre otras cosas los problemas que yo voy a nombrar aquí.

Introducción – Principios de seguridad

Antes de nada vamos a dar una guía de buenas prácticas que es interesante conocer antes de empezar a construir aplicaciones para conseguir un trabajo lo más bueno posible.

  • No dar nunca nada por hecho ni en cuestiones de seguridad ni en cuestiones del flujo normal de la aplicación. Todo el riesgo que se corra debe ser por parte del usuario (no hay nada que hacer contra eso). Me explico, no supongamos que si le pido el nombre al usuario no me va a poner un número de teléfono. No se si sería una forma acertada de decirlo pero hay que pensar con pesimismo, en los peores casos, y por remotos que sean pueden ocurrir.
  • Siempre que usemos servicios externos estamos asumiendo riesgos añadidos. Podemos haber hecho una página web muy segura y muy bien construida, pero si incrustamos contenido externo nadie nos asegura que el contenido externo sea vulnerable a algún tipo de ataque.
  • La oscuridad no es seguridad. No poner un botón acceso a la administración no impide que se pueda acceder a ella. Ocultar nuestro código no debe ser parte de nuestra seguridad.
  • Principio del mínimo privilegio: El usuario del sistema debe tener únicamente los privilegios que necesita para llevar a cabo su actividad.
  • Fallar de manera segura: Hay que tratar de manera muy cuidadosa los fallos en la aplicación. Por poner un ejemplo, si se produce un fallo en la aplicación mientras se realizan tareas administrativas no debe seguir iniciada la sesión como administrador. Otro ejemplo, no debe mostrar en un fallo información técnica sobre el mismo al usuario del sistema. Si el usuario sabe datos acerca de nuestro sistema podría tener más fácil la búsqueda de vulnerabilidades.

Los riesgos

Ahora que hemos dado unos pequeños consejos sobre seguridad a la hora de la construcción de aplicaciones ya podemos pasar a explicar los riesgos que hay en las aplicaciones web. No vamos a ponernos a programar nada sin saber los riesgos existentes y el porque de las medidas que se toman para combatirlos.

Voy a nombrar aquí los riesgos que considero más importantes.

  • Inyección SQL: Consiste en intentar “engañar” al sistema para que realice peticiones contra la base de datos que no son las que han sido programadas y que podrían comprometer gravemente la base de datos o incluso mostrar al atacante toda la información de la misma.
  • Cross Site Scripting: El atacante intentará enviar información a nuestro servidor por medio de nuestros formularios u otros medios con la intención de que dicha información sea almacenada en nuestra base de datos y posteriormente sea mostrada a los demás usuarios del sistema. Un ejemplo sencillo: Un código JavaScript que borre el contenido de la página, si eso es mostrado a los demás usuarios de la aplicación verán siempre una página en blanco. Esto es un ejemplo sencillo, pero imaginarios que lo que se consigue introducir es un código que tome el control de los navegadores de los usuarios de la aplicación web.
  • Robo de sesión: Como sabemos HTTP es un protocolo sin estados, lo que significa que las credenciales o información de sesión deberá ir en cada petición; debido a esto dichos datos resultan muy expuestos. Un robo de estos datos podría tener como resultado que alguien se estuviera haciendo pasar por nosotros y realizando acciones con unos privilegios que nos pertenecen. Y tampoco hemos de olvidar que se pude robar la sesión intentando obtener nuestros credenciales de alguna manera (averiguar nuestra contraseña).
  • Acceso a URLs restringidas: Consiste en la observación de una URL e intentar cambiarla para intentar acceder a otras zonas. Estas es una de las razones por las que la seguridad a través de la ocultación no es efectiva.

Solucionando los problemas de seguridad

Ahora que ya hemos identificado y visto en que consisten los problemas que nos podemos encontrar en las aplicaciones web, o al menos lo más importantes y peligrosos vamos a ir uno por uno explicando como hemos de solucionarlos. Iremos primero a los dos últimos que son los más sencillos y en posteriores entradas explicaremos la solución a los dos primeros que son los más importantes de los cuatro y quiero dedicarles una entrada completa.

Robo de sesión

Ya hemos visto el riesgo que tiene el robo de una sesión. Podríamos decir de manera resumida que el peligro está en la exposición de los datos de sesión. En la ilustración de la presentación de seguridad que hice podemos ver una posible situación de robo de sesión.

Para solucionar este problema de seguridad hay que atenerse a varios aspectos de la seguridad: la autentificación y la sesión; para cada uno de ellos veremos varios aspectos importantes a cubrir para solucionar problemas con el robo de sesión.

La Autentificación

  • El más importante de todos. Usar SSL sobre HTTP (HTTPS) para transferir los datos y asegurarse de que el cifrado cubre los credenciales y el ID de sesión en todas las comunicaciones. De esta manera los datos de sesión de los que hablábamos siguen estando expuestos pero esta vez se encuentran cifrados, por lo que no se pueden usar. Alguien podría pensar: “¿Y si se obtiene la sesión y se rompe la encriptación?”, la respuesta es sencilla, y es que con los medios actuales para cuando hayas conseguido romper la encriptación esa sesión habrá dejado de existir.
  • Usar un sistema de autentificación simple, centralizado y estandarizado. Es mejor que usemos métodos de autentificación que nos proporcione el propio servidor de aplicaciones en vez de soluciones implementadas por nosotros, debido a que lo que implementa el servidor de aplicaciones es usado en muchos lugares y ha sido suficientemente probado. Por ejemplo los filtros de Java EE (que veremos posteriormente) o los métodos de autentificación que proporciona Java EE (no los he usado).
  • Posibilitar el bloqueo de autentificación después de un número determinado de intentos fallidos. Esto podría evitar ataques de fuerza bruta intentando averiguar la contraseña del usuario.
  • Implementar métodos seguros de recuperación de contraseñas: Es común que se intenten usar estos métodos para intentar ganar acceso a una cuenta del usuario, podemos ver una serie de consejos para implementar estos métodos. Pedir al usuario al menos tres datos o más, obligar a que responda preguntas de seguridad. La contraseña que recuperada deberá generarse de manera aleatoria (con una longitud de al menos 8 caracteres) y enviada al usuario por un canal diferente (E-mail); de esta forma si el atacante consiguió sortear los primeros pasos es difícil que logre sortear el canal usado para transmitir.

La Sesión

  • Usar los métodos de sesión que nos proporciona el servidor de aplicaciones que estemos usando, y digo esto por las mismas razones que aconsejé usar los métodos de autentificación que proporciona el servidor de aplicaciones. En este caso no estamos refiriendo a la sesión y a las cookies.
  • Asegurar que la operación de cierre de sesión realmente destruye dicha sesión. También fijar el periodo de expiración de la sesión (periodo de tiempo en el que no se realice ninguna acción bajo dicha sesión); por ejemplo para aplicaciones críticas de 2 a 5 minutos, mientras que para otras aplicaciones más comunes se podría usar de 15 a 30 minutos.

En el descriptor de despliegue podemos fijar la caducidad de la sesión en minutos.

Ahora que hemos visto las formas de evitar el robo de sesión; o al menos de manera teórica, ahora vamos a ver como se hacen esta serie de cosas en la práctica.

Hemos hablado de usar métodos proporcionados por el servidor de aplicaciones para realizar la autenticación usando filtros y la sesión. Vamos a ver en concreto como podría hacerse. En primer lugar habrá que comprobar si los datos de usuario son correctos y posteriormente se podría hacer algo como añadir algún parámetro a la sesión indicando que esta autenticada. Voy a simplificar mucho un código para que nos centremos en lo fundamental.

Respecto a bloquear el inicio de sesión después de un número determinado de intentos fallidos podría ser tan fácil como añadir a la sesión el número de intentos fallidos y en el caso de que superen un determinado número no ejecutar ningún mecanismo de autenticación ni mostrar el formulario de login, únicamente habría que lanzar un timer para desbloquear el inicio de sesión pasado un tiempo.

Luego, para comprobar este parámetro (el que se ha añadido a la sesión para comprobar si se ha autentificado o no) sería factible usar filtros para todas las URLs para las que se necesite permiso y en ellos comprobar si existe el parámetro añadido en la sesión o no.

Lo primero de todo en el descriptor de despliegue (web.xml) hemos de configurar el filtro. Definiremos el filtro para un patrón de URL, en este caso todas las estén dentro del directorio /admin. Como sabemos los filtros actúan ante las peticiones de cliente para los patrones de URL para los que estén definidos. ¿Es esto del todo cierto? Pues no, en Java EE 6 esto ha cambiado un poco y podemos especificar que un filtro se ejecute sin necesidad de que el cliente haga una petición, simplemente con que el servidor haga una redirección porque así lo especifique el código (repito, sin que el cliente tenga nada que ver y sin que sepa nada de esa redirección). Podemos hacer esto mediante las sentencias que he dejado resaltadas.

Ahora veremos la clase que implementa el filtro y veremos que es a la misma clase a la que se hace referencia en el descriptor de despliegue. En ella simplemente se comprueba la existencia de los parámetros que se añadieron a la sesión en el proceso de autenticación. Los nombres de las variables son claros por lo que no he hecho comentarios, me parece que se ve claro el objetivo del código

Unicamente comentar que un Servlet no es exclusivo de una aplicación web, un ServletHttp es una clase de Servlet especial por así decirlo, de ahí que con el ServletRequest que nos da el filtro no podamos obtener la sesión y por eso necesitamos la primera línea del  método doFilter en la que se hace un casting.

Ahora veremos como cerrar la sesión de la manera más correcta. Lo más correcto sería destruir la sesión, de esta manera nuestra sesión dejará de existir a todos los efectos. Para ello usaremos la siguiente sentencia, cuya función es destruir la sesión. La podríamos usar por ejemplo en un Servlet encargado del cierre de sesión.

Respecto al tema de las contraseñas voy a dedicar al final de esta entrada un apartado donde explicaré como trabajar correctamente con ellas.

Acceso a URLs restringidas

Como ya hemos visto en el apartado teórico sobre este problema la seguridad a través de la ocultación no sirve de nada. Si por ejemplo para la parte pública se usara un patrón de URL /public el atacante podría pensar que la parte privada pudiera ser /admin o cosas parecidas, con no dar a conocer el detalle de ese directorio no es suficiente, hay que protegerlo.

Como a estas alturas supondremos no hay que hacer nada especial si se ha realizado lo anterior, es decir, si hemos realizado una autenticación y un control de sesión correctos. Con el uso de los filtros podríamos solucionar este problema perfectamente.

Se que es un problema que a la vista parece bastante evidente y con una solución sencilla; pero mientras siga apareciendo como uno de los problemas más graves de seguridad será porque no está tan bien solucionado aunque sea evidente el problema y la solución.

Trabajando correctamente con contraseñas

Las contraseñas son un dato delicado, la palabra secreta que usa el usuario para acceder a su cuenta en el sitio web, algo suyo y personal. Por este motivo únicamente el usuario debería conocer su contraseña. Ahora bien, si solo conoce él la contraseña… ¿Cómo la comprobamos cuando inicie sesión? Vamos a ver que si que es posible hacerlo sin conocer la contraseña; lo haremos usando una huella digital MD5 o SHA1 (he puesto los enlaces en inglés, me parecían más completos).

En definitiva estos dos son los algoritmos más usados para la generación de huellas digitales, consistentes en crear una suma siempre de igual longitud independientemente del tamaño del mensaje y con la particularidad de que no se puede volver al mensaje original a partir del mensaje encriptado; pero un mismo mensaje siempre genera el mismo código.

A sabiendas de lo anterior podríamos darnos cuenta de que si en vez de guardar la contraseña de los usuarios guardamos la huella digital de la misma podremos verificar su identidad cuando él inicie sesión, y no tendremos su contraseña guardada ni modo alguno de obtenerla. Lo que habrá que hacer es obtener la huella de su contraseña cuando el usuario la introduzca y la compararemos con la huella que nosotros teníamos guardada.

Así estaremos protegiendo al usuario; si nuestra base de datos se viese comprometida y llegamos a tener guardadas las contraseñas todos nuestros usuarios estarían expuestos, mientras que de esta manera que explico esto no ocurriría.

¿Es este método infalible? Pues no, existen tablas de huellas de palabras clave que se suelen usar, ya se sabe que no solemos ser bastante ocurrentes poniendo las palabras clave (solemos usar 1234, password, etc…), así que si disponen de una tabla de este tipo podrían obtener las contraseñas a partir de las huellas de nuestra base de datos comprometida (si bien no obtendrán todas, alguna seguro que sí).

Pues bien, aún con estos problemas nos quedan armas a usar. Si en vez de guardar las huellas de las contraseñas, guardamos las huellas de las contraseñas sumado con algo más. Si obtienen el mensaje que formó esa huella con una tabla de esas obtendrían la contraseña más eso que añadimos nosotros, luego no obtienen la contraseña. Y esto podría admitir muchas variaciones, como poner caracteres por enmedio de la contraseña, al final, al principio, lo que se añada a la contraseña podría ser tan complejo como desearamos. Pero sin olvidarse que al comprobar la contraseña habrá que hacer el mismo proceso.

Vamos a ver un ejemplo sencillo, mostraremos como se introduce la contraseña cuando el usuario se registra y como se comprueba cuando inicia sesión. En este caso vamos a añadir al final de la contraseña el nombre de usuario del sistema (Podría haber problemas si se cambiara el nombre de usuario en la máquina, pero no me voy a centrar en eso ahora, esto pretende ser solo un ejemplo ilustrativo).

Veamos ahora el proceso de comprobación (user será el usuario que tenemos guardado ya en la base de datos y del cual tenemos su huella de la contraseña más el añadido).

Ahora la cuestión viene cuando hacemos un método para recuperar la contraseña, no la podemos recuperar de forma alguna, no la hemos guardado. Por lo tanto, lo que habrá que hacer es generar una nueva y asignarla al usuario y mandarle la nueva contraseña por correo.

Hasta aquí llegamos con este artículo, como ya he comentado, trataré los otros problemas de seguridad de artículos posteriores. Espero que os haya sido de utilidad y os ayude a hacer aplicaciones web más seguras. También podeis encontrar más información en la web de OWASP sobre la autentificación y sobre más cosas que hemos estado tratando.

Digo lo mismo de siempre, estoy a vuestra disposición para dudas, comentarios o lo que sea; por supuesto también podeis aportar alguna otra cosa que consideréis interesante.


12 Comentarios

  1. Es un articulo muy interesante me ha servido mucho como base para entender mejor la parte de seguridad de mis aplicaciones, actualmente estoy desarrollando en un proyecto en Java EE 7 sabes de libros o material actualizado para esta versión que me puedas recomendar?

    • Hola:
      Muchas gracias por tu comentario. Respecto a Java EE 7 pues la verdad es que no conozco ninguna documentación específica de Java EE 7. Te recomiendo que mires en JavaHispano que seguro que allí encuentras algo :)
      Un saludo!

  2. Merling

    Excelente artículo, muchas GRACIAS!

    • Hola Merling, muchas gracias por tu comentario, me alegro de que sea de utilidad. Un saludo!

  3. g91v9v3r

    hola..
    el problema que veo es que la contraseña viaja en claro desde el navegador hasta el server…
    que piensas de esto?

    Saludos !!!!

    • Entendiendo que con viajar en claro quieres decir viajar en plano.

      Como ya he escrito en el artículo contra esto no hay otra solución que poner a la aplicación a trabajar bajo una conexión https, es la única manera de que los datos viajen cifrados.

      Otra cosa que se podría hacer es obtener la huella MD5 en el cliente mediante el uso de JavaScript pero tiene en primer lugar el problema de que no puedes validar la entrada en el lado del servidor, que siempre se ha de validar en el servidor y otro problema añadido es que viendo el código fuente de la página se podría intuir nuestro mecanismo de seguridad que expliqué al obtener la huella MD5 (recuerda que no solo sacamos el MD5 de la contraseña).

      En cualquier caso al viajar en plano la mayor parte del peligro la tienes en que alguien de la misma red pudiera verla, fuera de tu red ya no habría demasiados problemas; y te sorprenderías de la cantidad de aplicaciones que hay en la red que transportan contraseñas en plano.

      Así que, la mejor solución es usar https.

      Espero que te haya sido de ayuda esta respuesta :)

      ¡Un saludo!

    • g919v3r

      Definitivamente estoy de acuerdo contigo…
      algo que no me quedo claro es cuando dices ‘..el problema de que no puedes validar la entrada en el lado del servidor, que siempre se ha de validar en el servidor’
      No se exactamente a que te refieres…

      Bueno
      Por otro lado, tengo algo atorado…
      basicamente es que dentro de una aplicacion web se debe ejecutar una consulta, la cual dura algo asi como 4 horas… casi nada… lo quiero ejecutar en un hilo asincrono, pero al parecer no se puede, esta todo implementado, lo unico que necesito es que una vez que se hizo el submit se quede trabajando el hilo y me regrese el control a la pagina en 1 o 2 segundos…. el resultado se guarda en una tabla y despues se consulta directamente de esta tabla, por eso no hay problema, pero el CABRON hilo me detiene todo el proceso, estuve investigando y no veo como solucionarlo…
      La aplicacion usa struts 1.2, mybatis, ext js 4, servidor tomcat, java 6…..
      Alguna idea????

      Gracias y saludotessssssssssssssssssssssssss

    • Siempre se han de validar los formularios en el servidor, aunque se use JavaScrip para validarlos.

      La idea del uso de JavaScript es liberar al servidor de cierta carga, ya que en los casos en que la validación sea negativa liberarás al servidor de dicha comprobación.

      Pero siempre ha de existir la validación en el servidor, imagina que desactivas el JavaScript del navegador, si no tienes validación en el servidor te has quedado sin validación y expones la base de datos a datos del usuario sin comprobar. No debes asumir nunca riesgos, todo lo que dependa de ti lo debes asegurar; es decir no debes depositar toda tu seguridad en algo que dependa si el usuario activa o no el JavaScript.

      Espero que te haya convencido un poco más esta explicación :)

      Respecto a tu otra pregunta la verdad es nunca me he planteado ese problema, intentaré mirarte buscar información sobre ello, pero no tiene pinta de algo trivial puesto que supongo que lanzar hilos en Java EE no será lo mismo que en el escritorio porque el servidor de aplicaciones ya tiene varios hilos lanzados y una aplicación web ya es concurrente de por sí, por lo que habría que ver la forma en que el servidor de aplicaciones gestiona los hilos, como digo esto son suposiciones mias e intentaré buscar información al respecto, si averiguo algo te lo comento.

      Saludos

    • La contraseña nunca debe viajar en plano. Lo que se hace es aplicarle el MD5 del lado cliente y enviarla al servidor. En el servidor debe estar almacenada utilizando MD5 tambien. Lo unico que se hace despues del lado del servidor es comprara los hash MD5 para ver si corresponden a la misma contraseña.
      Lo mismo seria si queremos almacenar la contraseña en una variable de sesion , se debe guardar el hash MD5 y no la contraseña en plano.

    • Hola Pablo:

      Muy buena anotación la que comentas, el único problema que yo le veo a esto es que estás confiando parte de la lógica de negocio de la aplicación en terceros (el cliente), pudiera ser que no tenga activado JavaScript.

      Veo buena la opción quitando esa pega, aunque si se dispone de la posibilidad de usar https me parece que es mejor opción.

      Muchas gracias por el comentario y por dar pie al debate Pablo :) Un saludo!

  4. macarena

    holaa buenas tardes me gustaria saber si alguien me puede echar una mano para hacer un proyecto que tengo que presentar en clase sobre una tienda gracias de antemano

    • Hola:

      Se te hubiera leido con una vez que lo escribieses. Se te puede echar una mano, pero nos tienes que decir en lo que tienes dudas.

      Pero, si he interpretado bien tus preguntas da la impresión que quieras que alguien te haga el proyecto y se te van a resolver dudas, no creo que nadie te vaya a hacer el proyecto. Si no he interpretado bien tus preguntas pido disculpas.

      Quedo a tu disposición para las dudas que tengas.

      Saludos

Trackbacks/Pingbacks

  1. Seguridade de aplicaciones web con java | Business World TI - [...] http://jdiezfoto.es/informatica/java-ee-seguridad-en-aplicaciones-web-i/ [...]
  2. Bitacoras.com - Información Bitacoras.com...Valora en Bitacoras.com: No hay resumen disponible para esta anotación...

Deja un comentario

A %d blogueros les gusta esto: