L’installation de gems Ruby peut vous exposer aux attaques par injection de fichiers lock
17 août 2022
0 minutes de lectureDans cet article, nous examinons les angles morts de sécurité liés à l’injection de fichiers lock à laquelle une gem Ruby peut vous exposer via son fichier Gemfile.lock. En préambule, nous présenterons brièvement Ruby et la gestion des dépendances tierces avec RubyGems et Bundler.
Les développeurs web travaillent souvent sur des projets Ruby, mais les désignent généralement par le nom du framework open source populaire de développement d’applications web Ruby on Rails. Ruby est le langage dynamique open source sous-jacent qui fait fonctionner ce projet.
Pour rendre le code Ruby modulaire et réutilisable, on le regroupe à l’aide du projet RubyGems, avec lequel on interagit souvent via l’interface en ligne de commande, au moyen de l’exécutable gem. Ces packages sont appelés des gems et sont référencés dans un fichier manifeste de package appelé Gemfile.
RubyGems est désormais fourni avec le langage Ruby et n’a pas besoin d’être installé séparément. Un développeur peut installer une gem avec une commande aussi simple que gem install <gem-name>, mais la gestion globale des dépendances transitives, le verrouillage des versions et leur gestion sont bien plus complexes.
C’est là qu’intervient le projet Bundler, le gestionnaire de packages Ruby préféré de la communauté.
Manifeste Gemfile et dépendances transitives de Ruby
Les projets Ruby gèrent leurs dépendances tierces en définissant un fichier Gemfile. Ce fichier contient les métadonnées du projet et répertorie également toutes les dépendances utilisées pour le construire.
Voici un exemple de projet Ruby avec un Gemfile :
La définition source indique où l’outil bundler peut trouver ces bibliothèques gem. Le dépôt https://rubygems.org est le registre Rubygems par défaut, qui est open source.
Chaque dépendance est ensuite déclarée dans ce fichier sous la forme d’une définition de gem, avec ou sans contrainte de version.
Utiliser Bundler pour installer et gérer les gems Ruby
Bundler, le projet open source de gestion des gems, est chargé d’installer et de résoudre les dépendances. Pour commencer à utiliser Bundler afin de gérer vos dépendances Ruby, vous devez d’abord installer Ruby et RubyGems dans votre environnement. Il vous suffit ensuite d’installer l’outil bundler comme suit :
Installation des dépendances Ruby et fichier Gemfile.lock
Une fois l’outil bundler installé, vous pouvez installer les dépendances déclarées dans le manifeste Gemfile en exécutant la commande suivante :
Cela revient également à exécuter bundle sans argument supplémentaire, comme ceci :
Pour suivre les dépendances installées, un fichier Gemfile.lock est consulté afin de recréer exactement les versions des dépendances dans l’arborescence des dépendances imbriquées. Si ce fichier n’existe pas, bundler installe les dépendances, puis crée le fichier Gemfile.lock.
Notez que dans certains cas, vous devrez préciser l’option --path à la commande bundle install pour permettre la création de fichiers.
Après son exécution, le journal d’installation de bundler se présente comme suit :
L’installation avec bundler produit principalement les deux résultats suivants :
Les dépendances de gems tierces sont installées dans le répertoire
vendor/bundle.Le fichier
Gemfile.lockest créé pour consigner précisément l’état des dépendances dans l’arborescence des dépendances imbriquées, s’il n’existait pas déjà.
En examinant le fichier de verrouillage des dépendances, Gemfile.lock, on trouve les informations suivantes :
Injection dans le fichier lock Gemfile.lock de RubyGems
L’injection de fichier lock est un vecteur d’attaque dans lequel un utilisateur malveillant modifie le fichier Gemfile.lock pour qu’il contienne un ensemble de dépendances différent de celui du manifeste Gemfile. En raison du format généré automatiquement des fichiers lock, comme Gemfile.lock, ceux-ci sont rarement examinés par des humains lors des revues de code. Pour aggraver encore le problème, lorsque d’importantes modifications de code ont été apportées, la vue d’une pull request présentant les changements apportés au fichier affiche le fichier lock de manière réduite.
Pour illustrer la gravité et la fréquence de cette attaque, l’extrait de code ci-dessus représentant un fichier Gemfile.lock a été modifié pour contenir un ensemble de dépendances différent. Pouvez-vous repérer les packages malveillants qui ont été ajoutés ? Ce n’est pas aussi facile qu’on pourrait le croire ! J’ai ajouté la fausse gem Ruby rspec-assertions (~> 2.9.0) comme dépendance dans l’arborescence de rspec. Cette dépendance n’existe pas, mais pourrait exister si je créais une gem Ruby malveillante pour l’intégrer aux dépendances que vous installez.
L’attaque consiste à modifier le fichier Gemfile.lock et à ajouter une nouvelle dépendance imbriquée dans l’arborescence d’une autre dépendance. Prenons, par exemple, le contenu suivant d’un fichier lock (seul le début est affiché) :
Le fichier Gemfile.lock ci-dessus représente une résolution valide de l’arborescence des dépendances. Toutefois, un utilisateur malveillant peut modifier cette arborescence générée par bundler pour y ajouter la gem légitime appelée digest, comme suit :
Si vous exécutiez maintenant bundle dans l’invite de commande pour maintenir vos packages à jour, bundler installerait la gem digest. Nous pouvons examiner le répertoire vendor/ pour vérifier la présence de la nouvelle gem :
Dans ce cas, la surface d’attaque permet à des développeurs peu méfiants, qui peuvent recevoir des contributions de mise à jour de packages de la communauté ou d’autres sources non vérifiées, d’injecter de nouvelles dépendances de gems malveillantes dans le fichier Gemfile.lock, empoisonnant ainsi le fichier lock et ciblant les responsables du projet.
Surface d’attaque liée à l’injection de fichiers lock dans les gems Ruby
Quel est donc l’impact de l’ajout de gems Ruby arbitraires contrôlées par un attaquant à l’arborescence des dépendances d’une application Ruby ?
Comme la nouvelle dépendance gem a été conçue à des fins malveillantes, il est peu probable que le code source en ait besoin et qu’une autre dépendance l’appelle à l’exécution. Mais nous pourrions exploiter un vecteur d’attaque similaire dans le gestionnaire de packages npm de JavaScript, qui permet d’exécuter des commandes arbitraires lors de l’installation des dépendances.
Dès 2011, l’écosystème Ruby a pris une décision judicieuse : ne pas permettre aux responsables de packages de s’insérer dans le processus d’installation des dépendances pour exécuter des commandes arbitraires. Vous trouverez les références à cette discussion dans une issue GitHub, où les arguments contre un script postinstall à la npm sont exposés, à juste titre.
Cela dit, les gems Ruby peuvent inclure du code C natif qui sera compilé lors de l’installation. C’est la faille que nous cherchons à exploiter pour exécuter du code arbitraire par injection de fichier lock. En fait, cette méthode est utilisée dans la nature depuis 2008 et permet toujours de s’insérer dans le processus d’installation des gems Ruby.
Créons une gem Ruby à des fins malveillantes
Commençons par créer une gem Ruby dans laquelle nous ajouterons des instructions permettant de contourner le mécanisme intégré de la spécification Gemfile, qui ne permet pas nativement l’exécution de code pendant l’installation.
Il est difficile de trouver un nom en programmation, mais pas pour nous. Nous appellerons notre gem Ruby woof. Voici le fichier gemspec de notre gem Ruby, nommé woof.gemspec :
Créez ensuite le fichier source principal de notre gem Ruby, lib/woof.rb :
Nous pouvons ensuite créer le fichier d’extension natif de Ruby, extconf.rb, qui nous permet de déclarer les informations nécessaires à la création de makefiles, de fournir des instructions pour étendre Ruby avec des extensions C natives et, de manière générale, d’exécuter n’importe quel code Ruby.
Créez le fichier ext/woof/extconf.rb avec le contenu suivant :
L’extrait de code d’extension ci-dessus est un exemple simple de création d’une extension C native pour Ruby. La méthode create_makefile est une méthode Ruby utilisée pour créer un makefile pour une extension C native. La méthode create_makefile prend un seul argument : le nom de l’extension. Celui-ci doit être identique au nom de la gem Ruby.
La toute première ligne du code de l’extension appelle la méthode File.write, une méthode Ruby qui écrit un fichier dans le système de fichiers. Le premier argument de File.write est le chemin du fichier à écrire, et le second est le contenu à y écrire.
Pour simuler un package malveillant (inoffensif), nous constatons qu’à l’installation de notre gem Ruby woof, un nouveau fichier est créé dans le système de fichiers. Il s’appelle /tmp/woof et est vide.
Pour que la gem Ruby s’installe correctement, nous devons fournir un fichier hello.c valide. Il peut s’agir d’un simple programme *hello world* tiré de la documentation Ruby standard, comme suit :
Enfin, nous pouvons créer cette gem Ruby. Dans le répertoire racine de ce projet, exécutez la commande suivante :
Une fois l’opération réussie, une nouvelle gem nommée woof-1.0.0.gem est créée dans le répertoire racine du projet.
Pour vérifier que cette gem Ruby fonctionne comme prévu, nous pouvons l’installer et constater la création d’un nouveau fichier /tmp/woof (vous pouvez vérifier qu’il n’existe pas avant l’installation du package). Procédez comme suit pour installer la gem Ruby dans un dossier local :
Créez un nouveau répertoire temporaire, par exemple
mkdir ~/ruby-gem-test.Dans ce nouveau répertoire, créez un dossier nommé
vendor.Indiquez à Ruby d’utiliser le nouveau répertoire
vendorcomme répertoire des gems et d’y installer la gemwoof, comme suit :gem install --local /path/to/woof-1.0.0.gem --install-dir 'vendor'
Pour confirmer que notre attaque par exécution de code arbitraire a bien été menée à bien comme méthode d’installation d’une gem Ruby, vérifiez qu’un nouveau fichier /tmp/woof a été créé.
Prévenir les attaques par injection de fichiers lock dans les gems Ruby
Comme le montre cet article, le processus d’installation des gems Ruby permet aux responsables de packages d’exécuter du code arbitraire lors de l’installation de leurs dépendances. Il s’agit d’une surface d’attaque importante, qui peut être exploitée conjointement à l’injection de fichiers lock dans une application Ruby, mais aussi plus généralement dans le domaine de la sécurité de la chaîne d’approvisionnement de l’écosystème Ruby.
Malheureusement, contrairement au gestionnaire de packages npm, qui propose l’option de ligne de commande --ignore-script, la CLI gem de Ruby ne permet pas de désactiver la compilation des extensions C natives pendant l’installation. Les utilisateurs restent donc exposés à la possibilité que des responsables de packages tiers exécutent du code arbitraire.
Pour contribuer à atténuer ce type d’attaque, nous vous recommandons de suivre ces conseils :
Gérez les dépendances de vos gems Ruby et leurs mises à jour, ainsi que les modifications du fichier lock
Gemfile.lock, à l’aide d’un bot automatisé. Celui-ci mettra à niveau les versions de vos gems Ruby selon les besoins et vous fournira, dans la mesure du possible, des correctifs de sécurité.Si vous acceptez des contributions de développeurs à vos fichiers
GemfileetGemfile.lock, veillez à examiner attentivement ces modifications de code et à passer chaque ligne modifiée au crible.Privilégiez l’installation des dépendances avec
bundle install --deployment. Cette commande calcule l’arborescence complète des dépendances de toutes les gems Ruby et interrompt l’installation si le fichierGemfile.lockne correspond pas exactement aux gems à installer d’après le fichierGemfile.
Cette recherche s’appuie sur des travaux originaux antérieurs que j’ai menés (Liran Tal) en 2019 pour révéler les risques d’injection de fichiers lock dans l’écosystème JavaScript.
Autres ressources sur la sécurité Ruby :
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.



