Skip to main content

Prévenir les vulnérabilités liées à l’analyse YAML avec snakeyaml en Java

Écrit par
Blog Design Yaml parsing issues

30 mars 2021

0 minutes de lecture

Qu’est-ce que YAML ?

YAML est un langage lisible par les humains qui permet de sérialiser des données. Il est souvent utilisé pour les fichiers de configuration. Le mot YAML est l’acronyme de « YAML Ain’t a Markup Language » et a été publié pour la première fois en 2001. YAML, JSON et XML sont tous des formats structurés textuels.

Quelles sont les différences entre YAML, JSON et XML ?

Bien qu’il soit similaire à ces langages, YAML est conçu pour être plus lisible que JSON et moins verbeux que XML. Par exemple, les trois langages utilisent une syntaxe différente pour gérer la structure et l’imbrication, mais YAML s’appuie sur l’indentation et les espaces pour cela.

Les fichiers YAML servent souvent à configurer des applications, des serveurs d’applications ou des clusters. C’est un format très courant dans les applications Spring Boot et, bien sûr, pour configurer Kubernetes. Toutefois, comme JSON et XML, YAML permet de sérialiser et de désérialiser des données.

Bien que YAML semble être une excellente alternative à XML et JSON, beaucoup n’apprécient pas vraiment sa structure. Comme ce langage est basé sur des lignes et utilise l’indentation pour représenter la structure et l’imbrication, celle-ci entraîne souvent des problèmes lors de l’analyse de structures de données complexes. Un espace manquant (ou supplémentaire) dans une structure complexe contenant beaucoup de données suffit à faire échouer l’analyse YAML. Cela peut entraîner des problèmes inattendus, et il est difficile de trouver l’erreur dans un fichier YAML.

Point particulièrement important : importer manuellement YAML dans votre application Java à l’aide d’une version obsolète de snakeyaml peut vous exposer à des problèmes.

En bref

Les versions obsolètes de snakeyaml présentent une vulnérabilité par déni de service. Nous vous recommandons vivement de mettre à jour snakeyaml vers la version 1.26 ou ultérieure pour éviter ce problème.

Analyser des fichiers YAML en Java avec snakeyaml

Pour analyser des fichiers YAML dans votre application Java, vous pouvez utiliser la bibliothèque bien connue snakeyaml. Simple et légère, elle permet de convertir du YAML en objets et inversement.

Intéressons-nous à la lecture de YAML dans notre programme Java. Il existe deux façons de procéder. La première consiste à lire une entrée YAML de manière générique avec snakeyaml. Dans l’extrait ci-dessous, je lis un fichier YAML depuis le dossier resources présent dans mon classpath.

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

En chargeant le YAML de cette manière, on obtient une LinkedHashMap qui représente le fichier YAML sous forme structurée. Elle peut contenir n’importe quel élément de n’importe quel type, car il s’agit d’une structure générique qui n’est associée à aucun type particulier.

La deuxième méthode de lecture du YAML est plus spécifique. Vous pouvez analyser votre entrée YAML pour la convertir en un objet particulier. Snakeyaml tente d’associer les variables YAML aux champs de l’objet en fonction de leur nom. Une exception sera levée si le fichier YAML ne correspond pas à la structure de l’objet ou à l’objet cible désérialisé. Dans l’extrait ci-dessous, j’analyse mon entrée YAML pour la convertir en type 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"

Les deux méthodes d’analyse du YAML pour le convertir en objet fonctionnent parfaitement. Si vous savez exactement à quoi doit ressembler l’entrée, vous pouvez la convertir en un objet spécifique. Sinon, préférez la méthode plus générique et parcourez la liste manuellement.

L’attaque « Billion Laughs » en YAML

YAML permet notamment de créer des ancres. Vous pouvez les réutiliser à différents endroits pour éviter les répétitions. Dans l’exemple simplifié ci-dessous, je crée deux variables : var1 et var2. Grâce aux ancres, var2 a la même valeur que var1.

var1: &anchor value
var2: *anchor

Poussons le concept à l’extrême et créons la célèbre attaque « Billion Laughs » en YAML. En appliquant ce principe de manière imbriquée, je peux effectivement créer un milliard de « lol ».

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]

Comme vous pouvez le voir, lol1 est une liste de 10 chaînes "lol". La variable lol2 est une liste contenant 10 fois lol1. En répétant ce principe plusieurs fois, on obtient lolz = 10^9 fois "lol". Autrement dit, un milliard de « lol ».

Les ancres permettent de créer une bombe YAML ! L’énorme quantité d’objets imbriqués générés par une telle entrée YAML provoque une saturation de la mémoire.

Avec snakeyaml, et plus particulièrement les versions antérieures à 1.26, cela peut poser problème. Si vous analysez le YAML de manière générique, comme dans le premier exemple, cette bombe YAML provoquera un java.lang.OutOfMemoryError sur l’espace de tas Java. En général, votre application plante et devient indisponible : il s’agit donc d’un déni de service.

Toutefois, si vous analysez votre YAML pour le convertir en un objet spécifique, comme dans le deuxième exemple, le problème peut sembler moins grave. La bibliothèque snakeyaml tente de faire correspondre le nom de la variable à un champ de votre objet. Comme c’est impossible, vous obtenez une exception YAML. Cette solution peut sembler efficace, mais elle n’est pas infaillible.

Imaginons que nous ayons un type person avec un prénom et un nom, comme précédemment. Il peut aussi contenir des enfants représentés par une collection de type person.

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

Nous retrouvons alors le même problème que dans l’attaque « Billion Laughs » d’origine. Je peux créer un fichier YAML similaire à l’aide d’ancres qui s’étend sur plusieurs niveaux. Voici à quoi pourrait ressembler ce fichier. Notez qu’il n’est pas nécessaire de renseigner tous les champs d’un type.

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} ..]
...

J’ai créé plusieurs enfants pour le parent racine, et les enfants de chacun d’eux pointent vers une ancre précédente, créant un effet boule de neige. Ainsi, que j’analyse le fichier YAML de manière générique ou que je le convertisse en objet Person de manière spécifique, je provoque une saturation du tas.

Corriger les attaques « Billion Laughs » en YAML en Java

La solution à ce problème est bien plus simple que vous ne le pensez et beaucoup moins laborieuse que de rechercher l’espace manquant dans un fichier YAML. Il vous suffit de mettre à jour votre version de snakeyaml vers la version 1.26 ou ultérieure. L’équipe de snakeyaml a corrigé le problème en limitant à 50 le nombre d’alias pour les nœuds non scalaires. Avec la version récente de snakeyaml, l’analyse d’une bombe YAML comme celle décrite précédemment déclenche simplement une exception contenant ce message. Votre tas ne déborde donc pas et votre application continue de fonctionner.

Cela montre une fois de plus à quel point il est important de suivre les bibliothèques dont vous dépendez. Dans ce cas, la mise à jour vers la version la plus récente résout le problème. Comme vous le savez peut-être, Snyk peut vous aider si vous connectez votre dépôt de code. Pensez également à analyser régulièrement les bibliothèques dont vous dépendez avec Snyk Open Source, afin de ne pas être pris au dépourvu par une telle vulnérabilité.

Lancez-vous dans les compétitions Capture The Flag

Apprenez à résoudre des défis de capture du drapeau en regardant à la demande notre atelier virtuel d’initiation.

Lire la suite

blog feature snyk container custom base recommendations
Blog

Découvrez Evo Agentic AppSec : une sécurité au rythme du développement de l’IA

Le développement piloté par l’IA dépasse les workflows de sécurité traditionnels. L’AppSec agentique de Snyk s’appuie sur des agents de sécurité pour détecter, corriger et vérifier les vulnérabilités en continu, tandis que les équipes définissent les exigences.

blog feature ai violet
Blog

VulnBump : les agents IA peuvent-ils corriger les dépendances vulnérables sans casser le build ?

VulnBump évalue des agents IA sur 307 mises à niveau de dépendances réelles. Une correction n’est comptabilisée que si la vulnérabilité disparaît et que la compilation et les tests restent au vert.

Blog

Les modèles de pointe ont trouvé les vulnérabilités. Seul l’attaquant a trouvé les chaînes d’exploitation.

L’analyse statique a détecté les failles, mais seuls des tests d’attaque en conditions réelles ont prouvé comment elles pouvaient être enchaînées pour provoquer des compromissions. Comparaison d’Evo COS, de Claude Security et de Claude Code Security.