YAML-Parsing-Schwachstellen mit snakeyaml in Java verhindern
30. März 2021
0 Min. LesezeitWas ist YAML?
YAML ist eine menschenlesbare Sprache zur Serialisierung von Daten und wird häufig für Konfigurationsdateien verwendet. Das Wort YAML ist ein Akronym für „YAML Ain’t a Markup Language“ und wurde erstmals 2001 veröffentlicht. YAML lässt sich mit JSON oder XML vergleichen, da es sich bei allen um textbasierte strukturierte Formate handelt.
Worin unterscheiden sich YAML, JSON und XML?
Wie YAML ist auch JSON und XML eine Sprache, doch YAML wurde so entwickelt, dass es besser lesbar als JSON und weniger ausführlich als XML ist. Beispielsweise verwenden alle drei Sprachen unterschiedliche Syntax für Strukturen und Verschachtelungen. YAML nutzt dafür Einrückungen mit Leerzeichen.
YAML-Dateien werden häufig zur Konfiguration von Anwendungen, Anwendungsservern oder Clustern verwendet. Das Format ist in Spring-Boot-Anwendungen und natürlich auch zur Konfiguration von Kubernetes weit verbreitet. Wie bei JSON und XML können Sie YAML jedoch auch zur Serialisierung und Deserialisierung von Daten verwenden.
Obwohl YAML eine hervorragende Alternative zu XML und JSON zu sein scheint, sind viele kein großer Fan seiner Struktur. Da die Sprache zeilenbasiert ist und Einrückungen verwendet, um Strukturen und Verschachtelungen darzustellen, führen Einrückungen beim Parsen komplexer Datenstrukturen oft zu Problemen. Ein einziges fehlendes (oder zusätzliches) Leerzeichen in einer komplexen, datenintensiven Struktur lässt das Parsen von YAML fehlschlagen. Das führt zu unerwarteten Problemen, und es ist schwierig, den Fehler in einer YAML-Datei zu finden.
Besonders wichtig ist: Wenn Sie YAML manuell in Ihre Java-Anwendung importieren und dabei eine veraltete Version von snakeyaml verwenden, kann das zu Problemen führen.
Kurzfassung
Die veraltete Version von snakeyaml enthält eine Denial-of-Service-Schwachstelle. Wir empfehlen dringend, snakeyaml auf Version 1.26 oder höher zu aktualisieren, um dieses Problem zu verhindern.
YAML-Dateien mit snakeyaml in Java parsen
Zum Parsen von YAML-Dateien in Ihrer Java-Anwendung können Sie die bekannte Bibliothek snakeyaml verwenden. Diese leichtgewichtige, unkomplizierte Bibliothek ermöglicht es Ihnen, YAML in Objekte und umgekehrt umzuwandeln.
Sehen wir uns an, wie Sie YAML in unser Java-Programm einlesen. Dafür gibt es grundsätzlich zwei verschiedene Möglichkeiten. Die erste ist die allgemeine Methode, YAML-Eingaben mit snakeyaml einzulesen. Im folgenden Codeausschnitt lese ich eine YAML-Datei aus meinem Ressourcenordner ein, der sich im Classpath befindet.
Wenn Sie YAML auf diese Weise laden, erhalten Sie als Ergebnis eine LinkedHashMap, die die YAML-Datei strukturiert abbildet. Sie kann also beliebige Inhalte beliebigen Typs enthalten, da es sich um eine generische Struktur handelt, die nicht an einen bestimmten Typ gebunden ist.
Die zweite Möglichkeit, YAML einzulesen, ist spezifischer. Sie können Ihre YAML-Eingabe in ein bestimmtes Objekt parsen. Snakeyaml versucht, die YAML-Variablen anhand der Namenskonvention den Feldern des Objekts zuzuordnen. Passt die YAML-Datei nicht zur Objektstruktur oder zum deserialisierten Zielobjekt, wird eine Ausnahme ausgelöst. Im folgenden Codeausschnitt parse ich meine YAML-Eingabe in den Typ Person.
Person.java
Person.yaml
Beide Methoden zum Parsen von YAML in ein Objekt funktionieren einwandfrei. Wenn Sie genau wissen, wie die Eingabe aussehen sollte, können Sie sie in ein bestimmtes Objekt umwandeln. Andernfalls ist möglicherweise die generische Methode besser geeignet, sodass Sie die Liste manuell durchsuchen können.
Billion-Laughs-Angriff mit YAML
Eine Funktion von YAML sind sogenannte Anker. Sie können diese an verschiedenen Stellen wiederverwenden, sodass Sie Inhalte nicht wiederholen müssen. Im vereinfachten Beispiel unten erstelle ich zwei Variablen: var1 und var2. Mithilfe von Ankern erhält var2 denselben Wert wie var1.
Gehen wir einen Schritt weiter und erstellen den bekannten Billion-Laughs-Angriff für YAML. Wenn ich dieses Konzept verschachtelt anwende, kann ich tatsächlich eine Milliarde „Lacher“ erzeugen.
Wie Sie sehen, ist lol1 eine Liste mit 10 Zeichenfolgen "lol". Die Variable lol2 ist eine Liste mit lol1 in 10-facher Ausführung. Wiederholen wir dieses Prinzip mehrmals, erhalten wir lolz = 10^9-mal "lol". Anders gesagt: eine Milliarde „Lacher“.
Mit Ankern können Sie eine YAML-Bombe erstellen! Die enorme Anzahl verschachtelter Objekte, die eine solche YAML-Eingabe erzeugt, führt zu einer Speicherüberlastung.
Bei snakeyaml und insbesondere bei Versionen vor 1.26 kann das zum Problem werden. Wenn Sie YAML wie im ersten Beispiel generisch parsen, führt diese YAML-Bombe zu einem java.lang.OutOfMemoryError im Java-Heap-Speicher. Das bedeutet in der Regel, dass Ihre Anwendung abstürzt und nicht mehr verfügbar ist – also zu einem Denial-of-Service-Angriff.
Wenn Sie Ihr YAML jedoch wie im zweiten Beispiel in ein bestimmtes Objekt parsen, scheint das Problem möglicherweise weniger gravierend. Die snakeyaml-Bibliothek versucht, den Variablennamen einem Feld in Ihrem Objekt zuzuordnen. Da das nicht möglich ist, erhalten Sie eine YAML-Ausnahme. Das mag zwar wie eine gute Lösung erscheinen, ist aber nicht narrensicher.
Nehmen wir wie zuvor einen Typ Person mit einem Vornamen und einem Nachnamen. Außerdem kann er Kinder enthalten, die durch eine Sammlung des Typs Person dargestellt werden.
Nun tritt dasselbe Problem wie beim ursprünglichen Billion-Laughs-Angriff auf. Ich kann mithilfe von Ankern eine ähnliche YAML-Datei erstellen, die mehrere Ebenen durchläuft. Die YAML-Datei könnte wie folgt aussehen. Beachten Sie, dass Sie nicht alle Felder eines Typs ausfüllen müssen.
Ich habe mehrere Kinder des Wurzelelements erstellt, und die Kinder aller Kinder verweisen auf einen vorherigen Anker, wodurch ein Schneeballeffekt entsteht. Unabhängig davon, ob ich die YAML-Datei generisch oder spezifisch in das Person-Objekt parse, führt das zu einer Überlastung des Heaps.
Billion-Laughs-Angriffe mit YAML in Java beheben
Die Lösung für dieses Problem ist viel einfacher, als Sie denken, und deutlich weniger mühsam, als das fehlende Leerzeichen in einer YAML-Datei zu finden. Sie müssen lediglich Ihre snakeyaml-Version auf 1.26 oder höher aktualisieren. Das Team hinter snakeyaml hat dieses Problem hervorragend behoben, indem es die Anzahl der Aliase für Nicht-Skalar-Knoten auf maximal 50 begrenzt hat. Wenn Sie eine YAML-Bombe wie oben beschrieben mit einer neueren Version von snakeyaml parsen, erhalten Sie einfach eine Ausnahme mit dieser Meldung. Das bedeutet auch, dass Ihr Heap nicht überläuft und Ihre Anwendung weiterläuft.
Das zeigt einmal mehr, wie wichtig es ist, die von Ihnen verwendeten Bibliotheken im Blick zu behalten. In diesem Fall lässt sich das Problem durch ein Update auf die neuere Version beheben. Wie Sie vielleicht wissen, kann Snyk Sie dabei unterstützen, wenn Sie Ihr Code-Repository verbinden. Scannen Sie außerdem die Bibliotheken, von denen Sie abhängig sind, mit Snyk Open Source, damit Sie nicht von einer solchen Schwachstelle überrascht werden.
Starten Sie mit Capture-the-Flag
Erfahren Sie in unserem virtuellen On-Demand-Workshop für Einsteiger, wie Sie Capture-the-Flag-Challenges lösen.



