Skip to main content

Cómo prevenir vulnerabilidades al analizar YAML con snakeyaml en Java

Escrito por
Blog Design Yaml parsing issues

30 de marzo de 2021

0 minutos de lectura

¿Qué es YAML?

YAML es un lenguaje legible por humanos para serializar datos que se usa comúnmente en archivos de configuración. La palabra YAML es un acrónimo de «YAML ain’t a markup language» (YAML no es un lenguaje de marcado) y se publicó por primera vez en 2001. Puedes comparar YAML con JSON o XML, ya que los tres son formatos estructurados basados en texto.

¿En qué se diferencian YAML, JSON y XML?

Aunque es similar a esos lenguajes, YAML está diseñado para ser más legible que JSON y menos verboso que XML. Por ejemplo, los tres lenguajes tienen una sintaxis diferente para manejar la estructura y la anidación, pero YAML usa la indentación con espacios en blanco.

Los archivos YAML suelen usarse para configurar aplicaciones, servidores de aplicaciones o clústeres. Es un formato muy común en las aplicaciones Spring Boot y, por supuesto, para configurar Kubernetes. Sin embargo, al igual que JSON y XML, puedes usar YAML para serializar y deserializar datos.

Aunque YAML parece una excelente alternativa a XML y JSON, a muchas personas no les gusta demasiado su estructura. Como el lenguaje está basado en líneas y usa la indentación para representar la estructura y la anidación, esta suele causar problemas al analizar estructuras de datos complejas. La falta (o el exceso) de un solo espacio en blanco en una estructura compleja con muchos datos puede provocar errores al analizar YAML. Esto causa problemas inesperados, y encontrar el problema en un archivo YAML es difícil.

Lo más importante que debes tener en cuenta es que importar YAML manualmente en tu aplicación Java con una versión desactualizada de snakeyaml puede causarte problemas.

En resumen

La versión desactualizada de snakeyaml contiene una vulnerabilidad de denegación de servicio. Te recomendamos actualizar snakeyaml a la versión 1.26 o posterior para prevenir este problema.

Cómo analizar archivos YAML en Java con snakeyaml.

Para analizar archivos YAML en tu aplicación Java, puedes usar la conocida biblioteca snakeyaml. Es una biblioteca ligera y sencilla que puedes usar para convertir YAML en objetos y viceversa.

Enfoquémonos en leer YAML en nuestro programa Java. Básicamente, puedes hacerlo de dos maneras distintas. La primera es la forma genérica de leer entradas YAML con snakeyaml. En el fragmento de código de abajo, leeré un archivo YAML de la carpeta de recursos que está en mi classpath.

InputStream is = getClass().getClassLoader().getResourceAsStream(filename);
Yaml yaml = new Yaml();
var lhm = (LinkedHashMap) yaml.load(is);

Al cargar YAML de esta manera, el resultado será un LinkedHashMap que representa el archivo YAML de forma estructurada. Esto significa que puede contener cualquier elemento de cualquier tipo, porque es una estructura genérica que no está vinculada a un tipo específico.

La segunda forma de leer YAML es más específica. Puedes analizar tu entrada YAML y convertirla en un objeto particular. Snakeyaml intentará vincular las variables YAML a los campos del objeto según una convención de nombres. Esto generará una excepción si el archivo YAML no coincide con la estructura del objeto o con el objeto de destino deserializado. En el fragmento de código de abajo, analizaré mi entrada YAML y la convertiré en un objeto de tipo Person.

InputStream is = getClass().getClassLoader().getResourceAsStream("person.yaml");
Yaml yaml = new Yaml(new Constructor(Person.class));
Person person = yaml.load(is);

Person.java

public class Person {
   private String firstname;
   private String lastname;
//getters and setters
}

Person.yaml

firstname: "Matt"
lastname: "Murdock"

Ambas formas de analizar YAML y convertirlo en un objeto funcionan perfectamente. Si tienes absoluta certeza de cómo debería ser la entrada, puedes convertirla en un objeto específico. Si no es así, quizás prefieras la forma más genérica y buscar manualmente en la lista.

Ataque de los mil millones de risas en YAML

Una de las funciones de YAML es que puedes crear anclas. Puedes reutilizarlas en distintos lugares para no tener que repetir lo mismo. En el ejemplo simplificado de abajo, creo dos variables: var1 y var2. Al usar anclas, var2 tiene el mismo valor que var1.

var1: &anchor value
var2: *anchor

Llevemos esto al extremo y creemos el famoso ataque de los mil millones de risas para YAML. Al aplicar este concepto de forma anidada, puedo realmente crear mil millones de risas.

lol1: &lol1 ["lol","lol","lol","lol","lol","lol","lol","lol","lol"]
lol2: &lol2 [*lol1,*lol1,*lol1,*lol1,*lol1,*lol1,*lol1,*lol1,*lol1]
lol3: &lol3 [*lol2,*lol2,*lol2,*lol2,*lol2,*lol2,*lol2,*lol2,*lol2]
lol4: &lol4 [*lol3,*lol3,*lol3,*lol3,*lol3,*lol3,*lol3,*lol3,*lol3]
lol5: &lol5 [*lol4,*lol4,*lol4,*lol4,*lol4,*lol4,*lol4,*lol4,*lol4]
lol6: &lol6 [*lol5,*lol5,*lol5,*lol5,*lol5,*lol5,*lol5,*lol5,*lol5]
lol7: &lol7 [*lol6,*lol6,*lol6,*lol6,*lol6,*lol6,*lol6,*lol6,*lol6]
lol8: &lol8 [*lol7,*lol7,*lol7,*lol7,*lol7,*lol7,*lol7,*lol7,*lol7]
lol9: &lol9 [*lol8,*lol8,*lol8,*lol8,*lol8,*lol8,*lol8,*lol8,*lol8]
lolz: &lolz [*lol9]

Como puedes ver, lol1 es una lista de 10 cadenas "lol". La variable lol2 es una lista de 10 elementos lol1. Al repetir este principio varias veces, terminamos con lolz = 10^9 veces "lol". En otras palabras, mil millones de risas.

¡Con las anclas puedes crear una bomba YAML! La enorme cantidad de objetos (anidados) que crea una entrada YAML de este tipo provocará una sobrecarga de memoria.

Esto puede ser un problema con snakeyaml, específicamente en las versiones anteriores a la 1.26. Si analizas YAML de la forma genérica descrita en el primer ejemplo, esta bomba YAML terminará provocando un java.lang.OutOfMemoryError en el espacio de heap de Java. Por lo general, esto significa que tu aplicación se bloquea y deja de estar disponible: una denegación de servicio.

Sin embargo, si analizas YAML y lo conviertes en un objeto específico, como en el segundo ejemplo, puede parecer que el problema es menos grave. La biblioteca snakeyaml intenta hacer coincidir el nombre de la variable con un campo de tu objeto. Como esto es imposible, obtendrás una excepción de YAML. Aunque parezca una buena solución, no es infalible.

Supongamos que tenemos un tipo person con nombre y apellido, como antes. Pero, además, también puede incluir hijos representados por una colección de tipo person.

public class Person {
   private String firstname;
   private String lastname;
   private List<Person> children;
   //getters and setters
}

Ahora tengo el mismo problema que en el ataque original de los mil millones de risas. Puedo crear un archivo YAML similar con anclas que pasa por varias capas. El archivo YAML podría verse así. Ten en cuenta que no es obligatorio completar todos los campos de un tipo.

firstname: X
lastname: XX
children:
   - children: &a [{firstname: a},{firstname: a ..]
   - children: &b [{children: *a},{children: *a} ..]
   - children: &c [{children: *b},{children: *b} ..]
   - children: &d [{children: *c},{children: *c} ..]
   - children: &e [{children: *d}, {children: *d} ..]
...

Creé varios hijos del padre raíz, y los hijos de todos ellos apuntan a un ancla anterior, lo que crea un efecto de bola de nieve. Así que, independientemente de si analizo el archivo YAML de forma genérica o lo convierto específicamente en el objeto Person, terminaré sobrecargando el heap.

Cómo corregir los ataques de los mil millones de risas en YAML en Java

La solución a este problema es mucho más sencilla de lo que piensas y mucho menos dolorosa que encontrar el espacio en blanco que falta en un archivo YAML. Solo tienes que actualizar snakeyaml a la versión 1.26 o posterior. El equipo de snakeyaml hizo un gran trabajo al corregir este problema y limitar a un máximo de 50 la cantidad de alias para nodos no escalares. Al analizar una bomba YAML como la descrita anteriormente con la versión más reciente de snakeyaml, simplemente obtendrás una excepción con este mensaje. Esto también significa que el heap no se desbordará y que tu aplicación seguirá funcionando.

Esto vuelve a demostrar lo importante que es llevar un control de las bibliotecas de las que dependes. En este caso, actualizar a la versión más reciente resuelve el problema. Como quizás ya sepas, Snyk puede ayudarte con esto si conectas tu repositorio de código. Además, sigue analizando las bibliotecas de las que dependes con Snyk Open Source para que vulnerabilidades como esta no te tomen por sorpresa.

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

blog feature snyk container custom base recommendations
Blog

Presentamos Evo Agentic AppSec: seguridad al ritmo del desarrollo con IA

El desarrollo impulsado por IA supera el ritmo de los flujos de trabajo de seguridad tradicionales. Agentic AppSec de Snyk usa agentes de seguridad para encontrar, corregir y verificar vulnerabilidades de forma continua, mientras las personas definen el estándar.

blog feature ai violet
Blog

VulnBump: ¿Pueden los agentes de IA corregir dependencias vulnerables sin romper la compilación?

VulnBump prueba agentes de IA en 307 actualizaciones reales de dependencias. Una corrección cuenta solo si elimina la vulnerabilidad y mantiene en verde la compilación y las pruebas.

Blog

Los modelos de frontera encontraron las vulnerabilidades. Solo el atacante encontró las cadenas.

El análisis estático encontró las fallas, pero solo las pruebas de ataque en vivo demostraron cómo podían encadenarse para provocar brechas. Una comparación de Evo COS, Claude Security y Claude Code Security.