Skip to main content

Cómo prevenir el cross-site scripting (XSS) en aplicaciones Java con Snyk Code

Escrito por
Snyk Code - Static Application Security Testing re-imagined for the developer

25 de abril de 2023

0 minutos de lectura

Java es un potente lenguaje de programación para backend que también se puede usar para escribir páginas HTML para aplicaciones web. Sin embargo, al crear estas páginas, los desarrolladores deben conocer los posibles riesgos de seguridad asociados con los ataques de cross-site scripting (XSS). Con el auge de los frameworks de plantillas modernos, prevenir ataques mediante técnicas adecuadas de validación y codificación de entradas se ha vuelto más fácil. Sin embargo, cuando los desarrolladores deciden crear sus propias páginas HTML sin usar un framework de plantillas, aumenta el riesgo de introducir vulnerabilidades.

Por ejemplo, usar el objeto HttpServletResponse en una aplicación Spring MVC para escribir contenido directamente en la respuesta puede darles a usuarios maliciosos la oportunidad de inyectar código en la página y provocar posibles ataques XSS. Por eso, es esencial que los desarrolladores tomen medidas adecuadas para mantener un alto nivel de seguridad en sus aplicaciones web Java y prevenir vulnerabilidades XSS al escribir páginas HTML.

Una solución como la que se muestra a continuación permite implementar fácilmente una página renderizada en el servidor sin usar un framework sofisticado que normalmente incluye instrucciones específicas. Sin embargo, este método tiene algunas desventajas evidentes.

Escribir código HTML en Spring MVC sin un framework de plantillas

Supongamos que tienes una aplicación web que toma el nombre de un producto y lo muestra en una página web mediante el objeto HttpServletResponse. Este es un ejemplo de cómo podrías implementar esta función en un controlador de Spring MVC:

@GetMapping("/direct")
public void directLink (@RequestParam String param, HttpServletResponse response) throws IOException {
   Product prod = productService.getProductByName(param);

   response.setContentType("text/html");
   var writer = response.getWriter();
   writer.write(head);
   writer.write("<div class=\"panel-heading\"><h1>"+ param + "</h1></div>");

   String output = "<div class=\"panel-body\">" +
           "<ul>" +
           "<li>%s</li>" +
           "<li>%s</li>" +
           "<li>%s</li>" +
           "</ul>" +
           "</div>";

   writer.write(String.format(output, prod.getDescription(), prod.getProductType(), prod.getPrice()));
   writer.write(foot);

   response.getWriter().flush();
}

¿Puedes identificar qué tipos de vulnerabilidades de seguridad podría introducir el código Java anterior?

Cómo detectar XSS con Snyk Code

Si analizas detenidamente la función anterior, quizá ya identifiques al menos una vulnerabilidad XSS, e incluso dos. Al analizar mi aplicación con Snyk Code, recibimos una alerta sobre dos problemas distintos de XSS en este método.

Hay varias formas de aprovechar Snyk Code. Veamos tres ejemplos distintos. La manera más directa de que los desarrolladores reciban comentarios de Snyk Code es instalar un complemento en el IDE. Tenemos complementos disponibles para muchos IDE. En el siguiente ejemplo, te muestro cómo el complemento de IntelliJ me ayuda a detectar problemas de XSS durante el desarrollo.

Resultado del complemento de IntelliJ:

Editor de código que muestra ProductController.java con dos vulnerabilidades de secuencias de comandos entre sitios marcadas en las líneas 93 y 103.

Otra opción es ejecutar Snyk Code con la CLI de Snyk. Al ejecutar el comando snyk code test en la terminal, obtendrás un resultado como el siguiente. Este método es útil en tu equipo local o como parte de una compilación automatizada en un pipeline de CI/CD.

Resultado de la CLI:

La salida de la terminal muestra dos vulnerabilidades de secuencias de comandos entre sitios (XSS) de gravedad alta en ProductController.java, en las líneas 93 y 103.

La tercera opción que quiero mostrarte es la interfaz web. Para obtener este resultado, usé la integración de Git con Snyk y conecté mi repositorio de GitHub a la interfaz web de Snyk desde el panel en https://app.snyk.io. Esta solución analiza el código confirmado en mi repositorio para detectar vulnerabilidades de seguridad.

Resultado de la interfaz web:

Informe de Snyk Code que muestra dos hallazgos de secuencias de comandos entre sitios (XSS), con código Java resaltado y una puntuación de 825.

Las tres opciones de análisis muestran que hay dos problemas de seguridad distintos relacionados con XSS que debo resolver. Snyk Code señala su ubicación exacta en mi código. Analicémoslos y veamos cómo podemos mitigarlos.

XSS reflejado

El XSS reflejado es un tipo de ataque XSS que ocurre cuando un usuario inyecta código malicioso en una aplicación web y este se refleja después en una respuesta que se le envía. En el ejemplo que presenté, si la entrada del usuario no se validara ni depurara correctamente antes de escribirse en la respuesta, un usuario malicioso podría inyectar un script que otros usuarios ejecutarían al ver la página web. Este tipo de ataque XSS suele usarse para robar datos de usuarios, modificar el contenido de un sitio web u realizar otras acciones maliciosas.

El código anterior obtiene el nombre del usuario del parámetro de la solicitud HTTP y luego lo escribe directamente en el objeto HttpServletResponse mediante:

writer.write("<div class=\"panel-heading\"><h1>"+ param + "</h1></div>")

Este código es vulnerable a ataques XSS porque no valida ni depura correctamente la entrada del usuario. Por ejemplo, un usuario malicioso podría inyectar código HTML o JavaScript en el parámetro "name", que luego ejecutarían otros usuarios al ver la página web.

Por ejemplo: .../direct?param=<script>alert(document.cookie);</script> podría revelar tu información personal de cookies. Esto significa que también podemos enviar esa información a otro servidor sin que lo sepas.

Snyk Code detectó este error y me indicó la vulnerabilidad XSS en la línea 93.

XSS almacenado

Por otro lado, el XSS almacenado es un tipo de ataque XSS en el que el código malicioso se guarda en el servidor y luego se entrega a todos los usuarios que acceden a la página afectada. En el ejemplo que presenté, si la entrada del usuario no se validara ni depurara correctamente y, en cambio, se guardara en una base de datos, un usuario malicioso podría inyectar un script que se mostraría a todos los usuarios que visitaran la página afectada. Este tipo de ataque XSS puede ser especialmente peligroso porque afecta a muchos usuarios y puede persistir incluso después de corregir la inyección inicial.

El código anterior obtiene un producto de ProductService y luego lo muestra en los campos como parte de la cadena de salida. Sin embargo, este código es vulnerable a ataques de XSS almacenado porque no valida ni depura correctamente los datos provenientes de la base de datos. La depuración es especialmente importante si no tienes certeza de quién tiene permiso para escribir en la base de datos. Por ejemplo, un usuario malicioso podría enviar una descripción del producto que incluya código HTML o JavaScript, que se guardaría en la base de datos y se mostraría a todos los usuarios que visiten la página del producto.

Snyk Code señaló este posible problema de XSS en la línea 103, donde insertamos product.description en la cadena de salida sin validarla ni depurarla.

Cómo mitigar las vulnerabilidades XSS con Snyk Code

Para prevenir vulnerabilidades XSS, es importante validar y depurar correctamente las entradas de los usuarios antes de escribirlas en la respuesta. Snyk Code ya nos ayuda al señalar posibles soluciones. Una forma de hacerlo es usar una biblioteca como Apache Commons Text para codificar la entrada y evitar que se ejecute código malicioso.

Diferencia de código que muestra cómo se escapa una ruta de solicitud en HTML con StringEscapeUtils.escapeHtml4 para ayudar a prevenir ataques de cross-site scripting.

Con la función escapeHtml4(), podemos asegurarnos de que el código de los ataques XSS, tanto reflejados como almacenados, quede escapado y no se ejecute al cargar la página.

Hay otras bibliotecas que pueden realizar un escape similar. Además de Apache Commons Text, puedes consultar OWASP Encoder. Si trabajas con Spring, también puedes usar HtmlUtils.htmlEscape de Spring.

Al usar Apache Commons Text, el código correctamente escapado podría verse así:

@GetMapping("/direct")
public void directLink (@RequestParam String param, HttpServletResponse response) throws IOException {
   Product prod = productService.getProductByName(param);

   response.setContentType("text/html");
   var writer = response.getWriter();
   writer.write(head);
   writer.write("<div class=\"panel-heading\"><h1>"+ StringEscapeUtils.escapeHtml4(param) + "</h1></div>");

   String output = "<div class=\"panel-body\">" +
           "<ul>" +
           "<li>%s</li>" +
           "<li>%s</li>" +
           "<li>%s</li>" +
           "</ul>" +
           "</div>";

   writer.write(String.format(output,
             StringEscapeUtils.escapeHtml4(prod.getDescription()),
             StringEscapeUtils.escapeHtml4(prod.getProductType()),
             StringEscapeUtils.escapeHtml4(prod.getPrice())));

   writer.write(foot);

Ten cuidado con los frameworks de plantillas

Los frameworks de plantillas como Thymeleaf pueden ayudar a protegerte contra las vulnerabilidades XSS. Thymeleaf es un motor de plantillas popular para Java que incluye compatibilidad integrada con el escape de HTML, lo que ayuda a prevenir ataques XSS al codificar cualquier entrada del usuario que se incluya en el HTML renderizado.

Sin embargo, depende mucho de cómo crees la plantilla. Por ejemplo, así podrías usar Thymeleaf para renderizar un producto similar al del ejemplo anterior:

<div class="product" th:each="product : ${products}">
  <p th:text="${product.name}"></p>
  <p th:text="${product.type}"></p>
  <p th:text="${product.pric}"></p>
  <p th:utext="${product.descriptiont}"></p>
</div>

En este ejemplo, los atributos th:text escaparán el contenido, pero el atributo th:utext no lo hará. Este atributo th:utext renderiza el texto del comentario sin escapar las etiquetas HTML ni los caracteres especiales, por lo que puede ser vulnerable a XSS. Al usar un framework, es esencial conocer el comportamiento de ciertos elementos.

Detectar XSS antes de implementar en producción

Prevenir los ataques XSS es una prioridad fundamental para los desarrolladores que trabajan en aplicaciones web Java. Es esencial identificar y corregir las vulnerabilidades XSS lo antes posible durante el proceso de desarrollo. Aunque depurar las entradas de los usuarios puede mitigar eficazmente los ataques XSS, no siempre es suficiente.

Consulta recursos como la guía de prevención de XSS de OWASP y la lección de Snyk Learn sobre XSS para mantenerte al día con las amenazas más recientes y las prácticas recomendadas para prevenir XSS.

Además, es importante aprovechar las herramientas adecuadas para detectar errores de XSS y otros problemas de seguridad antes de que lleguen a producción. Snyk Code es una herramienta gratuita y valiosa para identificar posibles vulnerabilidades de seguridad en las primeras etapas del ciclo de desarrollo. Con un enfoque proactivo para prevenir XSS y los recursos y herramientas adecuados, los desarrolladores pueden ayudar a garantizar la seguridad e integridad de sus aplicaciones web Java.

Comienza con Capture the Flag

Aprende a resolver desafíos de Capture the Flag con nuestro taller virtual 101 a pedido.