Java EE: Seguridad en aplicaciones web (II). Evitando Inyección SQL y XSS con ESAPI

Después de un tiempo un poco más atareado por fin encuentro un hueco para cumplir mi promesa, vamos a continuar la guía de seguridad en aplicaciones web que se inició hace un tiempo. En esta nueva entrada se cubrirán en detalle los aspectos que quedaron por cubrir en la primera entrega: Inyección SQL y Cross Site Scripting (XSS). Aprenderemos a usar la librería ESAPI para evitar estos problemas de seguridad.

Inyección SQL

Comenzaremos hablando por el primero de los riesgos de seguridad que se comentaban en la entrada anterior. Para hacer un breve recordatorio veíamos que se trataba de intentar comprometer de alguna manera la base de datos de la aplicación.

Para ello, se suelen usar los formularios de las páginas web tratando de cerrar en ellos una consulta SQL e iniciando otra a continuación; dicho esto enviar directamente los datos de los formularios hacia una consulta SQL no es una buena práctica. Veremos en este apartado una serie de buenas prácticas que deberíamos llevar a cabo para evitar la inyección SQL.

Uso de PreparedStatements

En primer lugar una muy buena práctica es cambiar el uso de los Statements por los PreparedStatements. Este tipo de Statement obliga a que la consulta tenga exactamente la estructura que se indica, y no deja que se cambie la estructura de la misma o se cierre una consulta y se abra otra nueva. También lleva a cabo un escapado de caracteres, puesto que podrías poner de nombre “O’Donnell” y no habría problema alguno en poner la comilla simple. Ni que decir tiene que también podríamos evitar la inyección SQL usando procedimientos almacenados.

Para hacer una consulta con un PreparedStatement lo primero que se hace es indicar la estructura de la consulta dejando los parámetros marcados con una interrogación “?” y posteriormente se irá indicando que son cada una de esas interrogaciones y su tipo de dato. Y gracias a que se indica el tipo de dato, Java ya se encarga de poner las comillas que hagan falta dependiendo del tipo de dato. Veremos que hay muchos métodos set dependiendo del tipo de dato a introducir.

Veamos un ejemplo. En el ejemplo se muestra un método sencillo para hacer un insert en la base de datos . He puesto el método completo para que también se observe una buena estructura para un método de este tipo. Siguiendo esta estructura es la mejor manera de asegurarse de que todos los recursos van a ser liberados en cualquier caso, tanto si se produce un error como si la ejecución es normal. Voy a explicar al final de esta entrada en que consiste ese método cerrarConexionYStatement (), ya que considero que es un punto importante a tener en cuenta para ahorrarnos unas cuantas líneas de código.

En el código de la tienda online podemos encontrar numerosos métodos de este estilo y se pueden ver ejemplos en los que se use un ResultSet e incluso algún ejemplo en el que se realizan varias transacciones dependientes entre si y se realizan rollbacks y commits manualmente.

Hay que comentar que aunque no se vea siempre se realizan commits en todos los métodos, solo que por el hecho de ser consultas simples la conexión realiza los commit de manera automática, pero esto se puede cambiar como se puede ver en el código de la tienda online.

Realizar validaciones con expresiones regulares

¿Esto es todo lo que podemos hacer? Pues no, podemos usar como medida de apoyo una validación de todos los datos de entrada de los usuarios en los formularios. Para realizar esta validación usaremos la librería que nos proporciona OWASP que se hace denominar ESAPI.

Y ahora como es lógico veremos como usar esta librería y configurarla. En primer lugar hemos de bajar el paquete que nos proporcionan en el que se incluye en jar de ESAPI y todas las librerías que requiere ESAPI para funcionar (carpeta libs del paquete descargado). Hemos de añadir todas esas librerías a nuestro proyecto.

Una cosa importante. Entre las librerías requeridas es posible que se incluya la librería de servlets. Esa librería no la debéis de incluir en el proyecto puesto que es una versión más antigua de los Servlets y cambian métodos como por ejemplo la forma de acceder al contexto de la aplicación, por lo que nos interesa conservar la especificación de Servlets moderna que tenga nuestro servidor de aplicaciones.

La siguiente parte importante son los ficheros de configuración que necesita ESAPI para funcionar: ESAPI.properties y validation.properties. Podemos encontrar una plantilla de cada uno de los ficheros en el paquete descargado en configuration/.esapi.

La siguiente tarea es colocar los ficheros de configuración en un lugar adecuado para que se reconozcan. ESAPI buscará los ficheros en varios lugares en el orden en el que indico a continuación. Encontraremos esta información en la página de ESAPI o de la manera que lo he hecho yo que es mirando la consola del servidor de aplicaciones.

En mi caso me pareció lo más cómodo incluirlo en el classpath. Para ello cree en la raiz del proyecto una carpeta llamada setup, que por otra parte es estándar que los proyectos web puedan tener esta carpeta para archivos de configuraciones, y posteriormente se ha de añadir dicha carpeta al classpath del proyecto.

Una vez que hemos colocado los archivos en su lugar comenzaremos configurando cada uno de ellos. Lo que se va a configurar en estos ficheros principalmente serán las expresiones regulares que se van a usar para validar cada uno de los campos que deseemos.

El primer fichero que hemos nombrado, ESAPI.properties es un fichero que en lo que a nosotros nos concierne no hemos de tocar nada, la librería se sirve de él para hacer sus operaciones; nuestras personalizaciones irán en el segundo fichero nombrado. Pero no está de más echar un ojo al fichero para ver las validaciones que ya incluye y se verá que incluye validación para Email y alguna otra cosa más. Mirándolo también veremos cual es la forma de las expresiones regulares que admite.

Ahora pasemos al segundo fichero en el que hemos de configurar las expresiones regulares que consideremos necesarias. Para ello hemos de poner una clave y una atributo; al estilo de un fichero de propiedades. La clave será siempre Validator.Campo, siendo Campo lo que usaremos posteriormente para referirnos a esa expresión regular.

Esto no pretende ser un tutorial sobre como crear expresiones regulares, sin embargo haré algunas anotaciones. Si alguien necesita más ayuda con el tema puede preguntar y no tendré problema alguno en resolver sus dudas.

El símbolo “^” indica el inicio de la cadena, mientras que el símbolo “$” indica el final de la cadena. Es bueno escapar caracteres como el guión y el punto puesto que el guión se usa para indicar un rango y el punto significa cualquier cosa. Otra anotación importante es que a la hora de poner caracteres regionales debéis ponerlos con su correspondiente código en UTF-16; si no se hace de esta manera después puede que no funcione correctamente. Se puede encontrar una tabla de correspondencia básica en la wikipedia. Por ejemplo, si se quiere poner una ‘a’ acentuada “á” se debe poner así \u00e1.

Si tenéis problemas con las expresiones regulares hay una web que podéis usar que puede resultar interesante. También se puede usar un software específico para expresiones regulares que nos puede ayudar a crearlas en unos sencillos pasos y también nos ayudará a crearlas. Su nombre es RegexBuddy y RegexMagic. El primero de ellos es sobretodo para aprender y verificar expresiones regulares mientras que el segundo está orientado a su creación.

Veamos ahora un pequeño ejemplo que son las que usé yo para la tienda online. Decir que no hace falta crear expresiones regulares para números porque tiene su verificación a parte que ya veremos.

Una vez que estos ficheros han quedado correctamente configurados pasaremos ha explicar como han de usarse dentro de nuestra aplicación.

Veamos por ejemplo un método para validar un nombre usando la expresión regular que hicimos para validar un nombre. He dejado comentados cada uno de los argumentos necesarios y también se encuentra disponible el javadoc en la página de los repositorios del proyecto o en el paquete descargado; por ejemplo puede existir un argumento más para que la cadena de caracteres se pase a forma canónica antes de ser validada.

El método devuelve la cadena validada y en caso de error hay dos tipos de excepciones, ValidationException es lanzada cuando simplemente no se ha pasado la validación, mientras que IntrusionException se lanza cuando se cree de manera muy clara que ha ocurrido un intento de ataque.

El primer argumento es un nombre para nuestro uso personal que posteriormente lo usará si hay un error para indicar donde ha ocurrido. Y el nombre de la expresión regular ha de ser el mismo que dimos en el fichero de propiedades, de esta manera se elige con que expresión regular se desea validar una entrada.

Como dije no hace falta crear expresiones regulares para los números puesto que usan validaciones a parte. Veamos un ejemplo.

Como se puede ver en el ejemplo validar un número es tremendamente sencillo sin tener siquiera la necesidad de especificar una expresión regular para la validación de los mismos.

Ahora veremos un ejemplo de como usar estas validaciones dentro de nuestra aplicación.

Escapar las entradas de usuario

ESAPI también nos permitirá escapar las entradas del usuario para introducirlas en la base de datos; pero la verdad es que usando PreparedStatements este paso no sería necesario, y en caso de querer hacerlo habría que hacerlo después de pasar la validación por expresiones regulares puesto que al escapar una cadena se van a incluir nuevos caracteres como la contrabarra “\”. Veamos a continuación un ejemplo sencillo para escapar las entradas preparado especialmente para MySQL (ESAPI proporciona métodos de escape para MySQL y para Oracle si no recuerdo mal).

Recomiendo que partiendo de los ejemplos y explicaciones que se han dejado aquí se eche un vistazo al javadoc de ESAPI puesto que contiene opciones bastante interesantes. Entre ellas tiene una forma de hacer validaciones seguidas poniendo los errores en una lista para hacer todo el conjunto de validaciones seguidas sin detenerse por el fallo de alguna de ellas.

Cross Site Scripting (XSS)

Ahora que hemos visto como evitar las inyecciones SQL veremos ahora las formas de evitar Cross Site Scripting, que a modo de resumen a lo que se comentó en el capítulo anterior se podría decir que el fin que se persigue con este tipo de ataques es introducir alguna información en la base de datos para que posteriormente sea mostrada a otros usuarios. La información que se suele introducir serán scripts que persiguen realizar alguna actividad ilegítima.

De la misma manera que en el apartado anterior se validaban las entradas de los usuarios para evitar la inyección SQL realizando esa validación es evidente que se puede evitar también el XSS.

Aunque el problema puede venir de un caso en el que no queramos usar una expresión regular para validar, por ejemplo un campo de comentarios para el usuario en el que se permite cualquier caracter. En este caso lo que habrá que hacer es buscar etiquetas html sospechosas como script, pero… ¿realmente vale con buscar este tipo de etiquetas? Pues la respuesta es que no, porque si se usa otra codificación ya no van a aparecer así las etiquetas. A continuación veremos una buena forma de validar las entradas para evitar el XSS.

El problema de las codificaciones

Lo primero veamos el problema de las codificaciones. En el siguiente ejemplo ambas líneas son equivalentes.

Ahora si vemos que no se puede proteger simplemente buscando etiquetas html ¿no?. Por lo tanto lo primero que habrá que hacer es pasar la cadena a una forma canónica.

Obtener la forma canónica de una cadena consiste en convertir toda la cadena a una codificación conocida y admitida para que de esta manera se eviten problemas en la posterior validación.

Usando AntiSamy para validar entradas en HTML

ESAPI también nos proporciona soluciones para todos los problemas comentados mediante un módulo llamado AntiSamy. Este se encarga de procesar código html y verificar si está permitido o por el contrario es un posible intento de intrusión, para ello usa un fichero xml de configuración en el que se especificarán con detalle las etiquetas html permitidas y no permitidas; también será posible especificar que tipo de propiedades de CSS se pueden usar.

El archivo xml de configuración de AntiSamy se puede colocar donde nosotros queramos, aunque, obviamente no sería una buena medida colocarlo en un directorio que sea accesible publicamente. En mi caso lo he colocado en el mismo sitio que los archivos de configuración de ESAPI, que si recordamos estaban en la carpeta setup que habíamos añadido al classpath.

En la página del proyecto de AntiSamy es posible encontrar varios archivos de configuración de ejemplo, de más a menos restrictivos. Lo mejor es partir de alguno de estos archivos y en caso de querer modificarlos hacerlo observando las directivas que ya contiene puesto que no es una tarea sencilla hacer un archivo de estos desde cero, por ello nos proporcionan varios ejemplos. También hay un ejemplo en su página de descargas muy interesante pensado para enviar datos al servidor que provienen de un TextArea con TinyMCE (Librería JQuery para dotar a un TextArea de un editor con WYSIWYG).

Ahora que hemos visto lo referente al fichero de configuración ya estamos en disposición de poner un ejemplo de un método que valida una entrada HTML. Este método que muestro a continuación valida una entrada que se le pasa como argumento y lanza una IntrusionException si la validación no ha pasado, dicha excepción la he lanzado yo para detectar los errores puesto que la librería no lanza excepciones ante una validación fallida, sino que anota los errores que han ocurrido y también tiene la posibilidad de eliminar aquello que no es válido según la configuración que hayamos hecho en el fichero xml.

Es importante ver también que como anoto en los comentarios antes de pasar la validación se obtiene la forma canónica del String usando un método que nos proporciona ESAPI. Y finalmente observar como se ha indicado donde esta el fichero xml de configuración, que se ha realizado usando el classpath y obteniendo su flujo de entrada.


Anexo – Cerrar Conexiones, Statements y ResultSets. Varargs en java

En este apartado vamos a ver una buena manera de hacer esos métodos que decíamos para cerrar las conexiones y Statments. Aquí el problema viene de hacer un método que nos sirva para todos los métodos de la clase manejadora de la base de datos. No van a tener todos los métodos un Statement o un ResultSet, pueden tener varios de cada uno.

Se puede pensar en hacer métodos que reciban listas de Statements, pero estaríamos a lo mejor escribiendo líneas construyendo esas listas en cada método, líneas que se supone intentamos ahorrarnos. Pues bien java tiene algo llamado varargs para facilitarnos esta tarea, aunque internamente este construyendo listas el caso es que no tenemos que construirlas nosotros.

Lo único que hemos de hacer es colocar en la cabecera de los métodos de cierre unos puntos suspensivos detrás del tipo de dato. El argumento que vaya con varargs debe ser el último y debe haber solo uno (esto es debido a que sino java no tiene forma de saber los tipos de datos de los argumentos). Posteriormente dentro del método podemos recorrer los elementos con un bucle for-each, como si de un lista normal se tratara (que es de hecho lo que hace internamente como ya he dicho).

Estos métodos podrían admitir toda una serie de llamadas no dependiendo del número de Statement o ResultSet.

Como podemos suponer nos hemos ahorrado bastantes líneas en cada uno de los métodos, ahora como máximo habrá dos líneas en cada método para cerrar los recursos independientemente del número de recursos que haya.


Bueno, y hasta aquí esta guía de seguridad para aplicaciones web junto con el uso de ESAPI que como vemos llega a ser muy útil para las tareas de seguridad en nuestra aplicación.

También comentar que puede venir bien la lectura de los consejos que nos da OWASP para los dos problemas que hemos tratado en esta entrada (en inglés): Inyección SQL y Cross Site Scripting (XSS).

Comentar que todo lo que hemos aprendido en estos dos capítulos se refiera a la lógica de servidor como es evidente, esta lógica siempre debe existir, pero debo comentar que si se realizan validaciones en el cliente por ejemplo usando JavaScript se puede quitar un poco de carga al servidor, ya que ante una validación fallida esa petición no llegará al servidor.

Espero que os hayan sido útiles estas guías de seguridad, y como siempre digo a vuestra disposición quedo para cualquier duda problema o sugerencia.


10 Comentarios

  1. Hola! no entendí algo, el cerrado de conexiones, statement y resultset, es también una solución al XSS? o solo el uso de AntiSamy? Habria alguna otra propuesta para evitar XSS? Gracias!!

    • Hola:

      Lo del cerrado de conexiones, statement y resultset, no es específico para evitar el XSS, es la forma correcta de trabajar con las Bases de Datos para asegurarte de que los recursos se están liberando siempre correctamente. Si no te aseguras de esta u otra manera al cabo de un tiempo de la aplicación corriendo el servidor se puede caer por haber excedido el número de conexiones a la BD porque no las liberas correctamente cuando terminas de usarlas.

      El close() si estás trabajando con un pool no significa cerrar la conexión, significa decirle al pool que la conexión está libre y la puede usar. Glassfish sobreescribe el método para que funcione de esta manera.

      La clave para evitar el XSS es validar absolutamente todo aquello que venga del cliente, no fiarse de nada, siempre hay que hacer validaciones de lo que viene del usuario en el servidor. Ya sea con Antisamy o con otra herramienta que consideres útil, y ten en cuenta que el usuario puede enviar cosas en otra codificación. Antisamy es una opción pero seguro que hay otras. Las ventajas de hacerlo así son el tema de las codificaciones y la configuración tan amplia que tiene en la que puedes configurar exactamente que etiquetas HTML dejas pasar y cuales no. Pero como digo seguro que hay otras herramientas que son capaces de hacerlo.

      Si te queda alguna duda más no dudes en preguntar. Un saludo!

  2. insert = con.prepareStatement(“SELECT * FROM tabla WHERE nick=?”);
    insert.setString(1, nombre);

    asi estaria bien?, pregunto porque mi duda es respecto a que atributos deben indicarse con el signo de pregunta en prepareStatement

    Muy pero muy buen post, saludos

    • Si la pregunta tuya va conforme al tipo de datos, es decir si se va sustituir por un número o letras la respuesta es que sí. En general todo lo que sea una variable en la consulta va con la interrogación para luego sustituirlo. Tienes muchos ejemplos en la clase de persistencia de la Tienda Online que publiqué.

      Muchas gracias por el comentario y el agradecimiento hacia la entrada. Me alegro de que te haya servido :)

      Un saludo!

    • Impecable muchas gracias
      Un saludo!

    • Muchas gracias por el comentario, me alegro de que haya servido.

      Un saludo!

  3. luis miguel martin pico

    como puedo solucionar este problema que tengo que me sale una pequeña ventana que me pone esto en todas las pestañas de mozilla firefox [Aplicación java script] despues un triangulo de advertencia en amarillo y me pone error.ddomaineis null como puedo quitarlo o alguna solucion me sale continuamente y le das a cerrar y se va pero vuelve al rato muchas gracias

    • No se muy bien cual es tu pregunta, no logro relacionar tu problema con el contenido de la entrada

      Saludos

  4. Hola a todos, estoy trabajando en un proyecto web y toda la interacción con la base de datos la realizamos a través de Stored Procedure… Quisiera que alguien me oriente como hacer para prevenir ataques hacking (SQL Injection, XSS, etc…) tomando en cuenta que estoy trabajando con Stored Procedure… Gracias de antemano!!!

    • Hola:
      En primer lugar comentarte que nunca he trabajado con Procedimientos almacenados, pero sin embargo buscando un poco en la web de OWASP me he encontrado con algo de información que te puede ayudar.

      Como comenté en la entrada el uso de procedimientos almacenados es seguro por la misma razón que lo es usar PreparedStatements, digamos que no hay generación dinámica de código SQL, tu defines de antemano que sentencias van a ser las que se van a ejecutar, sin embargo siempre es bueno que las entradas que reciba ese procedimiento almacenado por parte del usuario sean validadas de la manera que se hace en este artículo o escapando las entradas (también explicado en esta entrada).

      Tienes en este enlace que te dejo un apartado específico para los procedimientos almacenados:
      https://www.owasp.org/index.php/SQL_Injection_Prevention_Cheat_Sheet

      Saludos y espero te sea de ayuda!

Trackbacks/Pingbacks

  1. ESAPI: Evitando Inyección SQL y XSS en J... - [...] Evitar Inyeccion SQL y XSS con ESAPI. PreparedStatement, String canonico, expresiones regulares, uso de AntiSamy, varargs, escapar SQL  [...]
  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: