Ruby-Gem-Installationen können Sie Lockfile-Injection-Angriffen aussetzen
17. August 2022
0 Min. LesezeitIn diesem Beitrag betrachten wir die Sicherheitslücken durch Lockfile-Injection, denen ein Ruby-Gem Sie über seine Gemfile.lock aussetzen kann. Zunächst geben wir eine kurze Einführung in Ruby und die Verwaltung von Drittanbieterabhängigkeiten mit RubyGems und Bundler.
Webentwickler arbeiten häufig an Ruby-Projekten, meinen damit aber meist das beliebte Open-Source-Webanwendungsframework Ruby on Rails. Ruby selbst ist die zugrunde liegende dynamische Open-Source-Sprache, auf der das Projekt basiert.
Damit Ruby-Code modular und wiederverwendbar ist, wird er mit dem Projekt RubyGems paketiert, das häufig über die Befehlszeile mit der ausführbaren Datei gem bedient wird. Diese Pakete werden als Gems bezeichnet und in einer Paketmanifestdatei namens Gemfile aufgeführt.
RubyGems ist inzwischen in die Ruby-Sprache integriert und muss nicht separat installiert werden. Entwickler können ein Gem ganz einfach mit gem install <gem-name> installieren. Die umfassende Verwaltung transitiver Abhängigkeiten, das Festlegen von Versionen und die Versionierung sind jedoch deutlich komplexer.
Hier kommt das Projekt Bundler ins Spiel – der beliebteste Ruby-Paketmanager der Community.
Das Gemfile-Paketmanifest von Ruby und transitive Abhängigkeiten
Ruby-Projekte verwalten ihre Drittanbieterabhängigkeiten, indem sie eine Datei namens Gemfile definieren. Diese Datei enthält Projektmetadaten und führt außerdem alle Abhängigkeiten auf, die zum Erstellen des Projekts verwendet werden.
Hier sehen Sie ein Beispiel für ein Ruby-Projekt mit einem Gemfile:
Die Definition source gibt an, wo das Tool bundler diese Gem-Bibliotheken findet. Das Repository unter https://rubygems.org ist die Standard- und Open-Source-Rubygems-Registry.
Jede Abhängigkeit wird in dieser Datei als Gem-Definition aufgeführt, optional mit einer Versionsbeschränkung.
Gems mit Rubys Bundler installieren und verwalten
Bundler – das Open-Source-Projekt zur Gem-Verwaltung – ist für die Installation und Auflösung von Abhängigkeiten zuständig. Um mit Bundler die Abhängigkeiten Ihres Ruby-Projekts zu verwalten, müssen zunächst Ruby und RubyGems in Ihrer Umgebung installiert sein. Anschließend installieren Sie einfach das Tool bundler wie folgt:
Ruby-Abhängigkeiten und Gemfile.lock installieren
Sobald das Tool Bundler installiert ist, können Sie die im Manifest Gemfile aufgeführten Abhängigkeiten mit folgendem Befehl installieren:
Das entspricht auch der Ausführung von bundle ohne zusätzliche Argumente, zum Beispiel:
Um die installierten Abhängigkeiten nachzuverfolgen, wird eine Datei namens Gemfile.lock herangezogen. Sie ermöglicht es, den exakten Stand der Abhängigkeitsversionen im gesamten Baum verschachtelter Abhängigkeiten wiederherzustellen. Existiert diese Datei nicht, installiert Bundler die Abhängigkeiten und erstellt anschließend die Datei Gemfile.lock.
Beachten Sie, dass Sie in manchen Fällen die Option --path für bundle install angeben müssen, damit Dateien erstellt werden können.
Nach der Ausführung sieht ein Bundler-Installationsprotokoll etwa so aus:
Die Installation mit Bundler führt hauptsächlich zu zwei Ergebnissen:
Drittanbieter-Gem-Abhängigkeiten werden im Verzeichnis
vendor/bundleinstalliert.Die Datei
Gemfile.lockwird erstellt, um den exakten Stand der Abhängigkeiten im gesamten Baum verschachtelter Abhängigkeiten festzuhalten – sofern sie nicht bereits vorhanden ist.
Wenn wir die Abhängigkeitssperrdatei Gemfile.lock untersuchen, sehen wir folgende Informationen:
Lockfile-Injection in RubyGems über Gemfile.lock
Lockfile-Injection ist ein Angriffsvektor, bei dem ein böswilliger Nutzer die Datei Gemfile.lock so verändert, dass sie andere Abhängigkeiten enthält als das Manifest Gemfile. Da Lockfiles wie Gemfile.lock maschinell generiert werden, werden sie bei Code-Reviews häufig nicht von Menschen geprüft. Hinzu kommt, dass in der Pull-Request-Ansicht die Änderungen am Lockfile eingeklappt dargestellt werden, wenn umfangreiche Codeänderungen vorgenommen wurden.
Um die Schwere und Verbreitung dieses Angriffs zu veranschaulichen, wurde das oben gezeigte Codebeispiel einer Gemfile.lock geändert und mit anderen Abhängigkeiten versehen. Können Sie erkennen, welche bösartigen Pakete hinzugefügt wurden? Das ist schwieriger, als Sie vielleicht denken! Ich habe das erfundene Ruby-Gem rspec-assertions (~> 2.9.0) als Abhängigkeit in den rspec-Baum eingefügt. Diese Abhängigkeit existiert überhaupt nicht. Sie könnte aber existieren, wenn ich ein bösartiges Ruby-Gem erstellte, um es in die von Ihnen installierten Abhängigkeiten einzuschleusen.
Für den Angriff wird die Datei Gemfile.lock geändert und eine neue verschachtelte Abhängigkeit in den Baum einer anderen Abhängigkeit eingefügt. Betrachten Sie beispielsweise den folgenden Inhalt eines Lockfiles (hier ist nur der Anfang zu sehen):
Die oben gezeigte Gemfile.lock enthält eine gültige Auflösung des Abhängigkeitsbaums. Ein böswilliger Nutzer kann diesen von Bundler generierten Abhängigkeitsbaum jedoch so ändern, dass das legitime Gem digest wie folgt hinzugefügt wird:
Wenn Sie nun bundle an der Befehlszeile ausführen, um Paketupdates zu übernehmen, installiert Bundler das Gem digest. Im Verzeichnis vendor/ können wir das neue Gem überprüfen:
Dieser Angriffsvektor ermöglicht es, dass arglose Entwickler, die Paketupdate-Beiträge aus der Community oder anderen ungeprüften Quellen erhalten, neue bösartige Gem-Abhängigkeiten in die Gemfile.lock einschleusen. So wird das Lockfile kompromittiert und gezielt Projektbetreibern geschadet.
Angriffsfläche für Lockfile-Injection bei Ruby-Gems
Welche Auswirkungen hat es also, wenn beliebige, von einem Angreifer kontrollierte Ruby-Gems Teil des Abhängigkeitsbaums einer Ruby-Anwendung werden?
Da die neu hinzugefügte Gem-Abhängigkeit eigens für böswillige Zwecke erstellt wurde, wird sie wahrscheinlich weder vom Quellcode benötigt noch zur Laufzeit von einer anderen Abhängigkeit aufgerufen. Wir könnten jedoch einen ähnlichen Angriffsvektor wie im JavaScript-Paketmanager npm ausnutzen, bei dem beliebige Befehle während der Installation von Abhängigkeiten ausgeführt werden können.
Schon 2011 traf das Ruby-Ökosystem die kluge Entscheidung, Paketbetreibern nicht zu erlauben, sich in den Installationsprozess ihrer Abhängigkeiten einzuklinken, um beliebige Befehle auszuführen. In einem GitHub-Issue finden Sie Verweise auf die Diskussion, in der erläutert wird, warum ein npm-ähnliches postinstall-Skript eine schlechte Idee ist – und das zu Recht.
Allerdings erlauben Ruby-Gems, nativen C-Code einzubinden, der während der Installation kompiliert wird. Genau diese Möglichkeit lässt sich für die Lockfile-Injection und die Ausführung beliebigen Codes ausnutzen. Tatsächlich wird diese Methode seit 2008 in freier Wildbahn eingesetzt und funktioniert weiterhin, um sich in den Installationsprozess von Ruby-Gems einzuklinken.
Erstellen wir ein Ruby-Gem für böswillige Zwecke
Beginnen wir mit dem Erstellen eines Ruby-Gems, das Anweisungen enthält, um den integrierten Mechanismus der Gemfile-Spezifikation zu umgehen, der die Codeausführung während der Installation standardmäßig nicht zulässt.
Das Benennen von Dingen ist in der Softwareentwicklung schwierig – für uns aber nicht. Unser Ruby-Gem soll woof heißen. Hier sehen Sie die gemspec-Datei unseres Ruby-Gems namens woof.gemspec:
Erstellen Sie anschließend die primäre Quelldatei für unser Ruby-Gem: lib/woof.rb:
Anschließend erstellen wir Rubys native Erweiterungsdatei extconf.rb. Mit ihr können wir die für Makefiles erforderlichen Informationen angeben, Anweisungen zur Erweiterung von Ruby mit nativen C-Erweiterungen bereitstellen und allgemein beliebigen Ruby-Code ausführen.
Erstellen Sie die Datei ext/woof/extconf.rb mit folgendem Inhalt:
Das obige Codebeispiel für die Erweiterung zeigt auf einfache Weise, wie sich eine native C-Erweiterung für Ruby erstellen lässt. Die Methode create_makefile ist eine Ruby-Methode, mit der ein Makefile für eine native C-Erweiterung erstellt wird. Die Methode create_makefile erwartet ein einzelnes Argument: den Namen der Erweiterung. Dieser sollte mit dem Namen des Ruby-Gems übereinstimmen.
Die erste Zeile des Erweiterungscodes ruft die Methode File.write auf. Dabei handelt es sich um eine Ruby-Methode, die eine Datei in das Dateisystem schreibt. Das erste Argument für File.write ist der Pfad der zu schreibenden Datei, das zweite Argument ist der Inhalt, der in die Datei geschrieben werden soll.
Bei unserem Versuch, ein (harmloses) bösartiges Paket zu simulieren, sehen wir, dass bei der Installation unseres Ruby-Gems woof eine neue Datei im Dateisystem erstellt wird. Sie heißt /tmp/woof und ist leer.
Damit sich das Ruby-Gem erfolgreich installieren lässt, benötigen wir eine gültige Datei hello.c. Dafür genügt ein einfaches *Hello-World*-Programm aus der Ruby-Dokumentation:
Zum Schluss können wir dieses Ruby-Gem erstellen. Führen Sie im Stammverzeichnis dieses Projekts den folgenden Befehl aus:
Nach erfolgreicher Ausführung wird im Stammverzeichnis des Projekts ein neues Gem mit dem Namen woof-1.0.0.gem erstellt.
Um zu prüfen, ob dieses Ruby-Gem wie erwartet funktioniert, können wir es installieren und feststellen, ob die neue Datei /tmp/woof erstellt wurde (Sie können vor der Installation überprüfen, ob sie noch nicht existiert). Gehen Sie wie folgt vor, um das Ruby-Gem in einem lokalen Ordner zu installieren:
Erstellen Sie ein neues temporäres Verzeichnis, zum Beispiel mit
mkdir ~/ruby-gem-test.Erstellen Sie in diesem neuen Verzeichnis ein Verzeichnis namens
vendor.Weisen Sie Ruby an, das neue Verzeichnis
vendorals Gem-Verzeichnis zu verwenden, und installieren Sie das Gemwoofwie folgt darin:gem install --local /path/to/woof-1.0.0.gem --install-dir 'vendor'
Um zu bestätigen, dass unser Angriff zur Ausführung beliebigen Codes als Ruby-Gem-Installationsmethode erfolgreich war, prüfen Sie, ob eine neue Datei /tmp/woof erstellt wurde.
Lockfile-Injection bei Ruby-Gems verhindern
Wie dieser Artikel zeigt, können Paketbetreuer während der Installation ihrer Abhängigkeiten beliebigen Code ausführen. Das ist eine mächtige Angriffsfläche, die in Kombination mit Lockfile-Injection in Ruby-Anwendungen ausgenutzt werden kann – und ganz allgemein auch die Supply-Chain-Sicherheit im Ruby-Ökosystem betrifft.
Leider bietet die eigene gem-CLI von Rub – anders als der npm-Paketmanager mit seinem Befehlszeilen-Flag --ignore-script – keine Möglichkeit, die Kompilierung nativer C-Erweiterungen während der Installation zu überspringen. Dadurch können Drittanbieter-Paketbetreuer beliebigen Code ausführen und Nutzer bleiben gefährdet.
Um diese Art von Angriff einzudämmen, empfehlen wir Folgendes:
Verwalten Sie Ihre Ruby-Gem-Abhängigkeiten und deren Updates ausschließlich mit einem automatisierten Bot. Dieser kann Ihre Ruby-Gem-Versionen bei Bedarf aktualisieren und Ihnen, sofern möglich, Sicherheitskorrekturen bereitstellen. Verwenden Sie ihn auch für Änderungen am Lockfile
Gemfile.lock.Wenn Sie Beiträge von Entwicklern zu Ihren Dateien
GemfileundGemfile.lockannehmen, prüfen Sie diese Codeänderungen sorgfältig und untersuchen Sie jede einzelne geänderte Zeile genau.Installieren Sie Abhängigkeiten vorzugsweise mit
bundle install --deployment. Damit wird der vollständige Abhängigkeitsbaum aller Ruby-Gems berechnet und der Installationsprozess angehalten, wenn dieGemfile.locknicht genau den Gems entspricht, die gemäß derGemfileinstalliert werden sollten.
Diese Recherche basiert auf meiner früheren Originalarbeit aus dem Jahr 2019 (Liran Tal) zur Offenlegung der Risiken von Lockfile-Injection im JavaScript-Ökosystem.
Weitere Ressourcen zur Ruby-Sicherheit:
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.



