
Informe de investigación sobre las vulnerabilidades S2-045 y S2-055 de Struts2, y las vulnerabilidades CVE-2017-7525 y CVE-2017-15095 de Jackson
Publicamos un artículo resumido que resume los puntos clave y lo hace fácil de leer. Recomendado para quienes deseen conocer primero el panorama general o no tengan tiempo suficiente.
El 1 de diciembre de 2017 se publicó una actualización de seguridad de Struts2. Antes de la publicación, corría el rumor en la lista de correo de que las vulnerabilidades de Jackson (una popular biblioteca JSON para Java) estaban relacionadas, y el autor, que también utiliza Jackson en sistemas y herramientas internas, estaba interesado en los detalles concretos.
En cuanto al contenido realmente publicado, se corrigieron los siguientes dos problemas de seguridad. La vulnerabilidad de jackson-databind, un componente de Jackson, solo afecta a S2-055.
En el plugin REST, parece que desde antes se incluían tanto un manejador que usaba JSON-lib como uno que usaba Jackson, y el usuario podía elegir entre ellos. Se cree que el panorama completo de esta corrección es que en S2-054 se cambió el manejador predeterminado a Jackson, y además, en S2-055 se actualizó la versión anterior de Jackson a la más reciente.
Entonces, ¿qué tipo de vulnerabilidad es CVE-2017-7525? Dado que el propio autor utiliza Jackson habitualmente para procesar JSON en Java, investigó este problema durante el fin de semana del 2 y 3 de diciembre, y ese es el contenido de este artículo.
Entorno del autor utilizado para la verificación del código de muestra:
En el blog de Adam Caudill se publicó una explicación de CVE-2017-7525.
Resumiendo en mis propias palabras, jackson-databind proporciona la función de mapear JSON a objetos Java (clase ObjectMapper).
Al llamar a ObjectMapper.enableDefaultTyping(), es posible mapear usando nombres de clases incrustados de forma arbitraria en el JSON.
Creo que algunos de ustedes ya habrán tenido un mal presentimiento al saber que "se puede especificar un nombre de clase desde el JSON de entrada", y precisamente ese mal presentimiento se hizo realidad con CVE-2017-7525.
Antes de entrar en la explicación de la vulnerabilidad, explicaré por qué se implementó dicha función en primer lugar.
ObjectMapper.enableDefaultTyping()Consulte el siguiente código de muestra para conocer el uso básico de la deserialización con jackson-databind. (En este artículo, se usa Groovy para los códigos de muestra de Jackson. Es conveniente poder cambiar fácilmente la versión de jackson-databind con @Grab).
En el código de muestra anterior, simplemente la clave "animal" se puede mapear directamente a la clase Animal. Entonces, ¿qué pasa en el siguiente caso?```java class Zoo { Animal animal; }
abstract class Animal { String name; protected Animal() { } }
class Dog extends Animal { double barkVolume; Dog() { } }
class Cat extends Animal { boolean likesCream; int lives; Cat() { } }
En esta configuración, aparecen dos casos: el contenido de la clave "animal" puede referirse a la clase Dog o a la clase Cat. Por lo tanto, se necesita información adicional para saber con qué clase realizar el mapeo.
Para resolver esto, jackson-databind incorporó un procesamiento personalizado que permite incrustar el nombre de la clase de mapeo en el JSON.
Por ejemplo, como se muestra a continuación, el contenido de la clave "animal" se convierte en un arreglo, y el primer elemento especifica el nombre de la clase.```
{"animal":["Dog",{"name":"dog1","barkVolume":1.2}]}
Esto permite que ObjectMapper.readValue() reconozca el contenido de la clave "animal" como la clase Dog y realice el mapeo.
Por supuesto, de esta manera no se puede distinguir si el contenido de la clave "animal" era originalmente un array o si contenía información de nombre de clase propia de jackson-databind.
Para cambiar esto se utiliza el método ObjectMapper.enableDefaultTyping().
También existe la posibilidad de definir la anotación @JsonTypeInfo en la clase. Para más detalles, consulte la documentación de Jackson a continuación:
A continuación se muestra un código de ejemplo que utiliza el método ObjectMapper.enableDefaultTyping().
Como hemos visto, proporcionando un nombre de clase seguido de sus propiedades en JSON, es posible, aunque con ciertas limitaciones, instanciar cualquier clase con cualquier propiedad. La vulnerabilidad CVE-2017-7525 explota esto, y el informe que probablemente la desencadenó es el siguiente:
Se ha informado sobre el peligro de que la manipulación de nombres de clase en bibliotecas de serialización/deserialización muy utilizadas en Java, como Jackson, pueda conducir a la ejecución de código arbitrario, y se han enumerado nombres de clase concretos que son peligrosos.
No sabemos si fue en respuesta a esto, pero cronológicamente, justo después del primer commit del repositorio anterior, se creó el siguiente issue en jackson-databind y comenzó la mitigación.
¿Cómo serían realmente los datos JSON y el código Java que explotan esta vulnerabilidad? El código de prueba de jackson-databind 2.8.9, que mitiga este issue, contiene pistas:
Basándonos en este código de prueba, hemos preparado un código de ejemplo ajustado para poder probar su funcionamiento: