Skip to main content

SnakeYaml 2.0: cómo resolver la vulnerabilidad de deserialización insegura

Escrito por
blog feature supply chain sbom

21 de junio de 2023

0 minutos de lectura

En diciembre del año pasado, te informamos sobre CVE-2022-1471. Este problema de deserialización insegura podía facilitar la ejecución de código arbitrario en las circunstancias adecuadas.

En la publicación detallada del blog “Vulnerabilidad de deserialización insegura en SnakeYaml (CVE-2022-1471)”, expliqué los problemas de esta biblioteca y cómo se podía explotar. En esencia, el problema consistía en que, de manera predeterminada, SnakeYaml analizaba el yaml entrante como un tipo de objeto genérico. Esto abre la posibilidad de deserializar otras clases disponibles en el classpath. Aunque se genere una ClassCastException, si el objeto ya se cargó, el daño está hecho.

El impacto depende en gran medida del uso que le des a la biblioteca. Por ejemplo, muchos desarrolladores solo usan yaml para proporcionar configuración a sus aplicaciones. Esta vulnerabilidad solo se puede explotar si aceptas yaml de fuentes desconocidas, como usuarios finales. Aun así, simplemente no podemos predecir cómo se usará una biblioteca como esta, y la configuración predeterminada debería ser segura.

Actualizar SnakeYaml con Snyk Open Source

Usemos Snyk Open Source para encontrar un reemplazo para la antigua biblioteca SnakeYaml. Si ejecuto localmente snyk test con Snyk CLI, veo que hay un reemplazo disponible.

Salida de terminal que advierte sobre la ejecución de código arbitrario en org.yaml:snakeyaml 1.33 e indica que el problema se corrigió en la versión 2.0

La interfaz web también me indica que hay una versión 2.0 de SnakeYaml disponible para resolver el problema. El único inconveniente es que Spring Boot 3 todavía incluye la versión 1.x, así que tengo que reemplazar la versión manualmente en el archivo de manifiesto de Maven o Gradle.

Detalles de la vulnerabilidad de Snyk para org.yaml:snakeyaml: muestra ejecución de código arbitrario, CVE-2022-1471, gravedad media y puntuación de 437.

Ten en cuenta que cambiar manualmente la biblioteca por una nueva versión principal puede provocar fallas. No hagas estos cambios sin más y considera el posible impacto en el funcionamiento interno de tu aplicación.

Cómo mitigar la deserialización con SnakeYaml 2.0

SnakeYaml 2.0 se lanzó a principios de 2023 para mitigar el comportamiento predeterminado que podía llevar a la ejecución de código arbitrario. En esta versión, el constructor que usa cada nuevo yaml() ahora extiende SafeConstructor. Como resultado, solo podemos analizar un conjunto limitado de tipos. SafeConstrutor de SnakeYaml puede construir clases estándar de Java, como tipos primitivos y clases básicas como string y map.

Los tipos específicos ya no se pueden analizar de forma predeterminada. Por eso, el siguiente archivo yaml generará una excepción:

!!Gadget ["env"]
Exception in thread "main" Global tag is not allowed: tag:yaml.org,2002:Gadget
 in 'reader', line 1, column 1:
    !!Gadget ["env"]
    ^

Ahora, la implementación predeterminada ya no es vulnerable. Sin embargo, este es un cambio incompatible, así que es probable que tu código inicial deje de funcionar.

Cómo corregir la lógica de análisis de Yaml al usar SnakeYaml 2.x

En primer lugar, debemos asegurarnos de dejar de usar SnakeYaml 1.x. Incluso la versión más reciente de Spring Boot, la 3.1, todavía no incluye SnakeYaml 2.x. Esto significa que debemos actualizarlo nosotros mismos en el archivo de manifiesto. Para las implementaciones de Maven, podemos usar la sección <dependencyManagement> del archivo pom entre otras cosas. En Gradle, también es posible actualizar las dependencias transitivas con restricciones de dependencias.

Ten en cuenta que SnakeYaml 2.x introduce cambios incompatibles en la API con respecto a las versiones anteriores. Esto significa que debemos reescribir la implementación del análisis de yaml para usar los nuevos valores predeterminados seguros y hacer que vuelva a funcionar.

Veamos un dominio muy simple con dos clases:

  • Persona

  • Comentario

Person.java:

public class Person {
    private String name;
    private int age;

    private Comment comment;

    public Person() {
    }

    public Person(String name, int age, Comment class2) {
        this.name = name;
        this.age = age;
        this.comment = class2;
    }
    //getters and setters
}

Comment.java:

public class Comment {

    private String text;
    private String dateTime;

    public Comment() {}

    public Comment(String text) {
        this.text = text;
        this.dateTime = LocalDateTime.now().toString();
    }
    //getters and setters
}

Si quieres crear un archivo yaml a partir de una entidad como la anterior, probablemente hayas escrito algo parecido a lo siguiente al usar SnakeYaml 1.x:

var john = new Person("John", 31, new Comment("This is a comment"));
dumpYaml(john, "file.yaml");

public static void dumpYaml(Person pojo, String filename) throws IOException {
    Yaml yaml = new Yaml();

    try (FileWriter writer = new FileWriter(filename)) {
        yaml.dump(pojo, writer);
    }
}

Esto genera el siguiente archivo yaml:

!!mypackage.Person
age: 31
comment: {dateTime: '2023-06-15T16:53:23.175989', text: This is a comment}
name: John

Con SnakeYaml 2.x, !!mypackage.Person ya no se acepta. Ahora podemos eliminar la referencia al objeto al analizarlo para convertirlo en un archivo yaml. Sin embargo, sigue habiendo un problema con los archivos yaml exportados antes de esta migración.

Por suerte, ¡todavía podemos resolver este problema! Al analizar un objeto específico, puedes configurar el constructor que debe usar el analizador. Además, podemos agregar un TagInspector específico a LoaderOptions que permita la etiqueta de nuestro paquete. Así, solo se permiten archivos yaml que coincidan con tu objeto y sean compatibles con versiones anteriores de los archivos yaml creados con las versiones 1.x.

public static Person parseYaml(String filename) throws IOException {
        var loaderoptions = new LoaderOptions();
        TagInspector taginspector = 
                tag -> tag.getClassName().equals(Person.class.getName());
        loaderoptions.setTagInspector(taginspector);

        Yaml yaml = new Yaml(new Constructor(Person.class, loaderoptions));

        try (InputStream in = new FileInputStream(filename)) {
            // Parse the YAML file into a mypackage.MyYamlClass object
            Person obj = yaml.load(in);
            return obj;
        }
    }

Además, sería mejor eliminar por completo de tu archivo yaml la referencia o etiqueta al objeto real. Esto ya era posible en versiones anteriores de SnakeYaml: bastaba con agregar un representer al objeto yaml para asignar la etiqueta del objeto de nivel superior a map. A continuación, verás un ejemplo compatible con SnakeYaml 2.x:

    public static void dumpYaml(Person pojo, String filename) throws IOException {
        Representer customRepresenter = new Representer(new DumperOptions());
        customRepresenter.addClassTag(Person.class, Tag.MAP);

        Yaml yaml = new Yaml(new Constructor(Person.class, new LoaderOptions()), 
                customRepresenter);

        try (FileWriter writer = new FileWriter(filename)) {
            yaml.dump(pojo, writer);
        }
    }

El archivo yaml ya no comenzará con !!mypackage.Person (ni algo similar). Si todos tus archivos yaml están limpios, puedes quitar TagInspector del analizador.

Mantente al día con Snyk

Mantenerte al día con todas las versiones de tus bibliotecas es fundamental para la seguridad del código abierto. Usar SnakeYaml 1.x puede generar problemas de seguridad innecesarios si aceptas archivos yaml de fuentes externas, directa o indirectamente.

Snyk Open Source puede ayudarte a encontrar y corregir estos problemas, o recomendarte una versión alternativa si es necesario. En el siguiente ejemplo, mostramos que actualizar al menos a la versión 2.0 de SnakeYaml elimina la vulnerabilidad de ejecución de código arbitrario.

Detalles de la vulnerabilidad de Snyk para org.yaml:snakeyaml: muestra ejecución de código arbitrario, CVE-2022-1471, gravedad media y versiones afectadas.

Además, si conectas tu repositorio de git a Snyk, podemos ofrecerte pull requests para mantener tus dependencias al día, incluso si no hay un problema de seguridad crítico. Más vale prevenir que lamentar, y mantener tus bibliotecas actualizadas te ayuda a reducir el riesgo de vulnerabilidades y el trabajo adicional que implica actualizar debido a un problema de seguridad.

Comentario de Snyk bot en un pull request que recomienda actualizar de la versión 1.7.0 a la 1.8.1 para mantener las dependencias al día.

Empieza con los desafíos de Capture the Flag

Aprende a resolver desafíos de captura la bandera con nuestro taller virtual introductorio a pedido.

Leer más

feature insights context
Blog

Los ataques autónomos ya están aquí. La defensa debe estar a su altura.

Los atacantes autónomos están reduciendo el tiempo disponible para defenderse. Descubre cómo el descubrimiento, la corrección, la validación y la prevención continuos pueden ayudar a los equipos de seguridad a seguirles el ritmo.

feature insights context
Blog

¿La prevención es, en esencia, un problema ya resuelto?

La prevención en el código generado por agentes está resuelta desde el punto de vista arquitectónico, pero elegir controles que protejan la seguridad sin ralentizar el desarrollo sigue siendo el desafío.

Live Stream

Agentes de remediación, sin misterios: por qué solucionar es mejor que encontrar

Descubre cómo el agente de remediación de Snyk utiliza inteligencia de seguridad, análisis de explotabilidad y validación para convertir las vulnerabilidades en solicitudes de incorporación de cambios listas para fusionarse.