Serialisierung, auch Marshalling genannt, beschreibt die Möglichkeit, strukturierte Daten, meist Objekte, in eine sequenzielle Darstellungsform zu übertragen. Meist geschieht dies, um einen reibungslosen Datenaustausch zwischen verschiedenen Prozessen zu ermöglichen.
Seit den OWASP Top Ten 2017 sind Schwachstellen in Serialisierungformaten direkt vertreten, können aber, sollten welche gefunden werden, meist bis zu einer RCE ausgenutzt werden. Oft handelt es sich bei diesen Schwachstellen um Features des jeweiligen Formats, welche nicht sehr bekannt sind.
Viele haben sicher schon von XML und JSON gehört, aber wie sieht es mit YAML, Java Serialization oder Messagepack aus?
Problematische Formate
XML
XML (Extensible Markup Language) ist eine der bekannteren unter den Serialisierungsformaten. Es ist eine Auszeichnungssprache, die Daten durch hierarchische Struktur in Textform darstellt.
Doch beinhaltet XML, mit internen Doctypes und Entitäten, kritische Strukturen, um durch sogenannte XXE-Angriffe den Rechner durch DoS-Attacken (Billion Laugh Attack) außer Gefecht zu setzen, Dateien mit Rechten des Parsers auf dem Server auszulesen oder im schlimmsten Fall Prozesse auf dem Server über eine RCE zu starten.
Hierbei wird ein neuer Doctype definiert, welcher Entitäten festlegt. Eine Entität kann nicht nur eine einfache Ausgabe eines Zeichens sein, sondern auch Pfade auf interne oder externe Dateien.
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<foo>&xxe;</foo>
Durch geschicktes Verbinden von internen und externen Doctypes können Daten auch ohne Ausgabe an einen vom Angreifer kontrollierten Server extrahiert werden.
Der sicherste Weg, diesen Angriff zu verhindern, ist, Doctypes komplett zu deaktivieren. Sollte dies nicht möglich sein, sollten externe Entitäten und interne Doctypes deaktiviert werden.
YAML
YAML versucht ein menschenlesbares Serialisierungsformat zu sein, angelehnt an XML, welches hauptsächlich für Konfigurationsdateien eingesetzt wird. Es kann natürlich in jeder Form des Datenaustauschs verwendet werden.
Des Weiteren hat YAML Erweiterungen, die sehr sinnvoll sein können. Im folgenden Beispiel wird in YAML ein Python Datetime-Objekt geladen:
‘first_name’: Jan ‘last_name’: Hoersch ‘email_adress’: test@example.org ‘birthday’: !!python/object/apply:datetime.date [1989, 1, 1]
Wenn das Modul in Python nicht geladen wurde, lädt YAML das freundlicherweise nach. Dies lässt sich natürlich leicht ausnutzen über folgendes Beispiel:
‘contents_of_cwd’: !!python/object/apply:subprocess.check_output [‘ls -la’]
Dies kann umgangen werden, indem man in Python die Funktion yaml.safe_load statt yaml.load verwendet. Auch in Ruby und anderen Sprachen gibt es diverse Mechanismen zum Deaktivieren dieser Erweiterungen.
Java Serialization
Am 6.11.2015 wurde ein Artikel auf FoxGloveSecurity veröffentlicht, der das Problem der Java Deserialisierung aufgreift. Dieses Problem wurde bereits auf der AppSecCali in dem Talk „Marshalling Pickles“ beschrieben und in einem Artikel von IBM 2013 thematisiert. FoxGloveSecurity beschreibt eine Möglichkeit, um mit Hilfe von Deserialisierung von Java-Objekten fremden Programmcode in bekannter Software auszuführen.
Dies ist aber nicht vollständig. Hier wird sich nur auf eine Library (die commons-collection) beschränkt, welche vorgeschlagen wird zu patchen. Die Commons-Collection ist aber an dieser Stelle nicht das Problem, sondern nur ein Mittel zur Ausführung. Das Patchen der Commons wird nicht das Problem der Deserialisierung von nicht vertrauenswürdigen Objekten („deserialization of untrusted data“: CWE-502) beheben.
Objekte werden nur deserialisiert, wenn die Klasse, von der das Objekt abstammt, im Classpath liegt und die angreifbare Klasse serialisierbar ist.
Durch Codeanalysen kann festgestellt werden, ob die Methode ObjectInputStream.readObject und davon abgeleitete Methoden in der Software zum Einsatz kommen.
Wie oben schon beschrieben, kann die Software das Objekt nicht verifizieren, wenn es wieder zusammengebaut wird. Dazu gibt es eine Art „Look-Ahead“-Variante, um Objekte zu deserialisieren. Durch dieses Look-Ahead können bestimmte Objekte ausgeschlossen werden. Dadurch betreibt man eine Art Whitelisting und umgeht das Problem, indem nur auf wenige bekannte und überprüfte Objekte zugegriffen werden darf.
Um diese Validierung durchzuführen, gibt es mehrere Tools, die man als Software-Entwickler nutzen kann: wsargent/paranoid-java-serialization, ikkisoft/SerialKiller, kantega/NotSoSerial
Die andere, und etwas radikalere Möglichkeit, wäre ein komplettes Deaktivieren bzw. Firewallen von Diensten, die nicht-vertrauenswürdige Daten zulassen.
Einfachere Formate
All diese Probleme aus den oben genannten Serialisierungsformaten können umschifft werden, wenn weniger mächtige Varianten wie JSON oder MessagePack eingesetzt werden würden. Dies setzt natürlich voraus, dass die Daten in diesen einfachen Formaten wiedergegeben werden können. Beide haben ihre speziellen Einsatzgebiete. Natürlich können auch hier bei schlechter Implementierung Sicherheitslücken auftreten, aber dennoch sind beide Varianten so einfach gebaut, dass sie wenig Angriffsfläche aufweisen.
JSON
JSON (JavaScript Object Notation) ist ein kompaktes Datenaustauschformat. Jedes JSON-Dokument ist gültiges Javascript und kennt daher nur ein paar grundlegende Datentypen, darunter Objekte, Arrays, Strings, Integer, Boolean und Null. Andere Datentypen werden bei der Serialisierung verworfen.
{ "Name": "Mustermann", "Vorname": "Max", "maennlich": true, "Hobbys": [ "Reiten", "Golfen", "Lesen" ], }
Hierbei ist darauf zu achten, dass man natürlich nicht das Objekt mit eval deserialisiert, sondern einen sinnvollen Parser verwendet.
Auch JSON ist nicht vor Unsinn gefeit, denn es hat einen bösen Abkömmling namens JSONP.
JSONP wurde entwickelt, um Sicherheitsmechanismen der Browser zu umgehen und Daten über Domaingrenzen hinweg auszutauschen. Hierbei wird ein JSON-Dokument als Parameter eines Funktionsaufrufs in einem Script-Tag übergeben.
<script type="text/javascript" src="http://example.com/getjson?jsonp=Rueckruf"> </script>
Da hier die Antwort in einem Script-Tag geladen wird, kann natürlich beliebiger Code eingeschleust werden. Zudem beachtet das Script-Tag nicht die Same-Origin-Policy, weshalb diese JSONP-Daten von einer bösartigen Seite angefordert und ausgewertet werden können.
Im schlimmsten Fall werden dadurch sensible Daten an fremde Server übertragen.
MessagePack
MessagePack ist ein binäres Serialisierungsformat, welches an JSON angelehnt, aber durch die binäre Repräsentation wesentlich kleiner ist.
Es wurde ursprünglich als Serialisierungsformat zum Datenaustausch eines verteilten Dateisystems geschaffen, wird aber nun auch zum effizienten Abspeichern von Memcache oder Redis-Einträgen eingesetzt. Auch dieses Format kennt nur wenige Basisdatentypen, was die Angriffsfläche verkleinert.
Da JSON bei binären Daten an seine Grenzen stößt, ist hier MessagePack zu bevorzugen. Auch unterstützt es die Deserialisierung bei Streaming, was insbesondere bei Netzwerkkommunikation sinnvoll ist.
Wann welches Format?
Jedes Serialisierungsformat hat Vor- und Nachteile. Am Ende kommt es auf die Daten an, die serialisiert werden sollen, und die Sprachen, die unterstützt werden müssen. So wie es nicht immer sinnvoll ist, eine “Eierlegendewollmilchsau” wie XML oder YAML zu benutzen, so stößt man auch mit JSON an seine Grenzen. Manchmal kann es nützlich sein, eine mächtige Variante zu nutzen, um Vorgänge durch Erweiterungen zu vereinfachen. Wenn diese Erweiterungen aber sowieso ausgeschaltet werden, oder man Daten handhabt, welche man selber nicht kontrolliert, sollte zu einem einfacheren Serialisierungsformat wie JSON oder MessagePack ausgewichen werden.
Bei diesen Formaten ist die Gefahr, dass unbekannte Erweiterungen ausgenutzt werden, weitaus geringer, da der Angriffsvektor signifikant kleiner ist. Wenn Sie Unterstützung bei der Auswahl benötigen, können Sie sich gerne von uns beraten lassen.