Open Source zeigt Wirkung: Sicherheitslücke aufgedeckt und bereinigt
Letzte Woche informierte ich das Ruby on Rails Security-Team über eine große Sicherheitslücke in der aktuellsten Version von Ruby und den zugehörigen Gems. Das Rails-Core-Team reagierte heute mit der Veröffentlichung einer Sicherheitsempfehlung, welche nahelegt schnellstmöglich ein Upgrade des json-Gems auf die letzte stabile Version durchzuführen.
Im Wesentlichen geht es um folgendes: Der Default-JSON-Parser kann verwendet werden, um bösartige Objekte in den params-Hash einer Rails-Anwendung einzuschleusen. Dies ermöglicht die Beeinflussung von ActiveRecord::Base-Funktionalität, wie dynamischer Finder und Attributzuweisung. Mögliche Folgen sind das Umgehen von Mass-Assignment-Regeln oder sogar SQL Injection.
Neben der Deserialisierung simpler Datentypen unterstützt der Default-JSON-Parser von Rails auch die Deserialisierung komplexer Klassen. Beim Parsen eines JSON String akzeptiert der Parser einen speziellen Schlüssel namen_s json_class._ Der Parser versucht hierbei diese Klasse durch den Aufruf vo_n .json_create_ zu instanziieren. Nachfolgend ein Beispiel:
Dieser Mechanismus kann verwendet werden, um JSON in Instanzen komplexerer Ruby-Klassen zu deserialisieren. Dies ist kein unmittelbares Problem, da nahezu keine Klasse des Standard-Rails-Stacks eine .json_create-Methode implementiert und somit normalerweise auch keine interne Rails-Klasse auf diesem Weg instanziiert werden kann (im Gegensatz zur YAML-Parser-Sicherheitslücke, welche es ermöglichte, beliebige Klassen zu instanziieren). Unglücklicherweise wird die JSON-Version 1.6.7 standardmäßig mit der von OpenStruct erbenden Klasse JSON::GenericObject ausgeliefert:
Wie oben ersichtlich wird, kann diese Klasse missbraucht werden, um gefälschte Rückgabewerte zu erzeugen: Wenn ich in meinem Code erwarte, mit einem String zu arbeiten, aber in Wirklichkeit auf einer JSON::GenericObject-Instanz arbeite, verhalten sich Methoden wie #strip nicht wie erwartet. Ein Angreifer kann sich dieses Verhalten zu Nutze machen und Rails dazu verleiten, Werte für vor Mass Assignment geschützte Attribute zu akzeptieren. Dies funktioniert im Detail durch die Übergabe einer verschachtelten Datenstruktur bestehend aus JSON::GenericObject-Instanzen an den Initializer eines Models. Nachfolgend ein Beispiel aus meiner Blog-in-10-Minuten-App:
Wie man sieht war es möglich, eine Post-Instanz zu erzeugen und direkt ein geschütztes Attribut zuzuordnen - alles andere als gut! Rails' Schutzmechanismus vor Mass Assignment denkt er würde immer auf simplen Datentypen wie Strings oder Hashes arbeiten. Es versucht durch den Aufruf #stringify_keys auf dem Hash die bereitgestellten Hash-Keys zu Strings zu konvertieren. Anschließend lehnt es durch #reject auf dem Hash jedes Attribut ab, welches sich auf der Mass-Assignment-Blacklist befindet. Wenn ich die obige Datenstruktur zu Post.create übergebe, werden alle diese Methodenaufrufe von meinem JSON::GenericObject verarbeitet. Rails denkt es arbeitet mit einem gesäuberten Hash, obwohl ich frei über die Werte aller Attribute bestimmen kann.
Dieser Mechanismus funktioniert mindestens für ActiveRecord und Mongoid, andere ORMs wurden nicht getestet. ActiveRecord ist sogar anfällig für SQL Injection: Das ID-Quoting des Abstract-Database-Adapter kann umgangen werden, indem Argumente bereitgestellt werden, die bereits "vorgequoted" sind:
Die Verifizierung dieser Sicherheitslücke erfolgte bei einer Rails 3.2.11 - App bestehende aus einem simplen Scaffold. Aufgrund der Schwere dieser Sicherheitslücke sollten all Rails applications sofort auf die aktuellste stabile json-Version geupdated werden.
Vielen Dank an Michael Koziarski vom Rails Security Team und Aaron Patterson für die konstruktive Zusammenarbeit zur Lösung dieses Problems.