Vulnérabilité : Object taint bypassing in DL and Fiddle in Ruby (CVE-2013-2065)

Les modules DL et Fiddle de Ruby comportaient une vulnérabilité mettant en œuvre une chaîne de caractère « contaminée » (tainted string, marquée avec la méthode Object#tain). Des appels systèmes pouvaient être fait en utilisant de telles chaînes de caractères, ignorant le niveau $SAFE de Ruby. Cette vulnérabilité porte l’identifiant CVE-2013-2065.

Explications et impact

Les fonctions natives exposées à Ruby via DL ou Fiddle ne vérifiaient pas la valeur du drapeau taint des objets manipulées. Cela pouvait causer des situations où des chaînes de caractères marquées comme contaminées étaient tout de même acceptées comme des données valides pour des exceptions de type SecurityError.

Un exemple de code utilisant DL illustrant cette vulnérabilité :

def my_function(user_input)
  handle    = DL.dlopen(nil)
  sys_cfunc = DL::CFunc.new(handle['system'], DL::TYPE_INT, 'system')
  sys       = DL::Function.new(sys_cfunc, [DL::TYPE_VOIDP])
  sys.call user_input
end

$SAFE = 1
my_function "uname -rs".taint

Un autre exemple, avec Fiddle :

def my_function(user_input)
  handle    = DL.dlopen(nil)
  sys = Fiddle::Function.new(handle['system'],
                          [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
  sys.call user_input
end

$SAFE = 1
my_function "uname -rs".taint

Tous les rubyistes utilisant une version affectée par cette vulnérabilité sont invités à mettre à jour Ruby, ou à utiliser le palliatif décrit ci-après.

Veuillez notez que le correctif n’empêche pas l’utilisation d’offset numériques, car les nombres ne peuvent être marqués par Object#taint. Dans l’exemple suivant :

def my_function(input)
  handle    = DL.dlopen(nil)
  sys = Fiddle::Function.new(handle['system'],
                          [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
  sys.call input
end

$SAFE = 1
user_input = "uname -rs".taint
my_function DL::CPtr[user_input].to_i

l’adresse mémoire est utilisée sans qu’il soit possible de déterminer si elle est valide ou non. Dans ce cas, il est recommandé de vérifier l’état de la donnée en entrée avant de passer l’adresse mémoire :

user_input = "uname -rs".taint
raise if $SAFE >= 1 && user_input.tainted?
my_function DL::CPtr[user_input].to_i

Palliatif

Si vous êtes dans l’impossibilité de mettre à jour Ruby, il est tout de même possible de palier cette vulnérabilité avec le monkey patching suivant :

class Fiddle::Function
  alias :old_call :call
  def call(*args)
    if $SAFE >= 1 && args.any? { |x| x.tainted? }
      raise SecurityError, "tainted parameter not allowed"
    end
    old_call(*args)
  end
end

Versions affectées

  • Toutes les versions de la branche 1.9 avant la 1.9.3-p426
  • Toutes les versions de la branche 2.0 avant la 2.0.0-p195
  • Les versions de développement avant la révision 40728

La branche 1.8 n’est pas concernée.

Remerciements

Merci à Vit Ondruch pour avoir mis en lumière cette vulnérabilité.

Historique

  • Initialement publié le 14 mai 2013 à 13:00:00 (UTC)