Mise à jour de sécurité critique pour Rails

Publié le 10 janvier 2013 par Nicolas Cavigneaux

Cet article est publié sous licence CC BY-NC-SA

Récemment plusieurs bugs de sécurité critiques ont été trouvés dans le code de Ruby on Rails.

Trop nombreux sont les développeurs qui ne se soucient pas de ce genre d’annonces. Ils pensent que leur application n’est pas assez visible pour être visée, que la faille n’est pas réellement importante. Le fix sera donc intégré dans le meilleur des cas lorsqu’un développeur décidera de passer à une version plus récente de Rails, notamment pour bénéficier de nouvelles fonctionnalités.

C’est une pratique dangeureuse qui peut vous conduire à de très mauvaises surprises. J’attire votre attention sur le fait que les dernières failles trouvées permettent d’aller très loin dans l’exploitation de votre application (injections SQL, injection et éxecution de code, DoS et encore contournement des systèmes d’identification).

Nous vous conseillons donc vivement de mettre à jour toutes vos applications en production récentes et anciennes.

Les failles découvertes

Toutes les versions de Rails inférieures à 3.2.11 sont vulnérables à ces failles, c’est donc tout le parc applicatif Rails qui est concerné. Bien sûr certaines conditions doivent être réunies pour que votre application soit vulnérable. Malheureusement ces conditions sont remplies dans la majorité des applications existantes.

Une version patchée est déjà disponible mais elle introduit une régression dont nous parlerons à la fin. Elle a en tout cas le mérite de palier à l’ensemble des failles découvertes.

Injection SQL

Vous êtes concerné par cette faille si vous utilisez les finder dynamiques #find_by.

La manière dont ActiveRecord extrait les paramètres des finders dynamiques rend possible les injections SQL. En ajoutant des paramètres à la requête, l’attaquant peut modifier les options passées au finder et donc commencer à s’attaquer au serveur SQL.

Un code aussi simple que le suivant est vulnérable :

Post.find_by_id(params[:id])

Si vous ne souhaitez pas mettre à jour vos applications, vous pouvez contourner cette faille en vous assurant que les paramètres passés sont bien des chaînes :

Post.find_by_id(params[:id].to_s)

Injection de code et DoS

Le traitement des paramètres utilisateurs (params) fonctionne de manière à caster automatiquement les valeurs de type String vers le type qui leur correspond le mieux. Il s’avère que certains types de données ne doivent pas être créés par l’utilisateur. Un utilisateur malicieux qui voudrait exploiter l’application peut modifier les formulaires pour y inclure des symbol ou encore pour parser du contenu en YAML ce qui peut lui permettre de créer des classes par exemple.

Rails charge les contenus YAML via YAML::Load qui ne fait aucune vérification particulière et se contente de désérialiser ce qu’on lui donne en entrée. Il devient donc possible d’exécuter du code à distance.

De même pour la lecture des contenus XML qui permet de désérialiser des arbres XML vers toute sorte de données. Le code XML suivant est valide et compris par Rails :

<?xml version="1.0" encoding="UTF-8"?>
  <hash>
    <age type="integer">30</age>
    <name type="string">foo</name>
  </hash>
</xml>

ce qui permet très facilement de communiquer avec une API en rails via XML tout en supportant les types de données. Malheureusement aucune vérification / blacklistage des types n’est fait. Il est donc tout à fait possible de passer des symbols ou du YAML qui sera parfaitement interprété.

Les symboles sont un type spécial en rails qui ont notamment la particularité d’être instancié de manière unique, un peu comme une chaîne qui serait toujours le même objet en mémoire, chaque fois que vous vous en servez.

Rails se sert des symboles pour son fonctionnement interne et même si l’application ici ne paraît pas simple, il s’avère que plusieurs exploits utilisants ce type de faille ont déjà touchés Rails. Il est donc possible de voir apparaître des exploits utilisants cette faille.

Concernant YAML, il permet la sérialisation de données dans un format simple assez similaire au JSON. De plus, le parser XML de Rails permet de gérer du YAML inséré dans du XML. On peut donc envoyer toute sorte de données au travers de XML.

On peut donc imaginer envoyer un XML contenant un objet YAML :

— !ruby/object:Crafted\nfoo: "admin"\nbar: true\n

ce qui aurait pour effet de créer une instance de la classe Crafted et de définir les variables d’instance @foo à “admin” et @bar à true. L’attaquant peut donc créer des instances de vos modèles ou de n’importe quelle autre classe Ruby et donc potentiellement exploiter toute votre application et même votre serveur.

Il est donc conseillé de désactiver totalement le parsing YAML si votre application ne l’utilise pas ou d’upgrader votre version de Rails.

Bypass des systèmes d’identification

Le mécanisme d’interprétation des paramètres par Active Record en conjonction avec la façon donc sont parsées les données JSON permettent à l’attaquant de d’injecter du SQL (IS NULL ou bypasser des WHERE).

Cette faille ne permet en aucun cas d’injecter du contenu mais seulement de modifier des conditions.

Elle permet notamment à l’attaquant de modifier la requête pour en passant un paramètre [nil] au lieu de la valeur légitime. Il envoit donc un tableau non-vide contenant un élément nil. Ce tableau est passé à la condition Active Record ce qui a pour effet de transformer le WHERE attribut = valeur en WHERE attribut IN (NULL). Ce qui est encore plus intéressant pour l’attaquant est que s’il passe un hash à la place du tableau, celà aura pour effet de faire sauter la clause WHERE.

Les régressions

Comme dit précédemment, des mises-à-jour correctives ont été publiées mais il est à noter qu’une régression à été remarquée.

Pour ceux qui mettent à disposition une API JSON, il est à noter que l’application des patchs de sécurité peut perturber une application existante. En effet, désormais quand un objet nil est passé dans un tableau, celui-ci est filtré.

En envoyant le JSON suivant :

{"foo":[1,2,3,null]}

vous devriez en toute logique obtenir le params[:foo] #=> [1, 2, 3, nil] mais la régression vous donnera à la place params[:foo] #=> [1, 2, 3].

Ce bug sera très certainement corrigé rapidement.

Conclusion

Nous ne pouvons pas ici et pour le moment, donner des exemples d’exploits mais nous y reviendrons dans quinze jours avec une suite à cet article. Ce délai permet aux développeurs de se mettre à jour et patcher leurs applications.

Si la démonstration de ces exploits vous intéresse, n’hésitez pas à nous laisser un commentaire nous encourageant à écrire un article sur ce sujet.

Quoi qu’il en soit prenez ces informations en considération et assurez vous que vos applications ne sont pas vulnérables.

L’équipe Synbioz.

Libres d’être ensemble.