Vulnérabilité de type Déni de service par expansion des entités dans REXML (bombe XML, CVE-2013-1821)

L'expansion d'entités XML sans restriction peut conduire à un déni de service dans REXML. Cette vulnérabilité a reçu l'assignation CVE-2013-1821. Nous recommandons fortement de mettre à jour Ruby.

Détails

Lors de la lecture de nœuds texte au sein d'un document XML, le parseur REXML peut être forcé à allouer des chaînes de caractères extrêmement larges, ce qui peut consommer toute la mémoire de la machine, entraînant un déni de service.

Le code concerné ressemble à ceci :

document = REXML::Document.new some_xml_doc
document.root.text

Quand la méthode `text` est appelée, les entités sont étendues. Un attaquant peut envoyer un document XML relativement petit qui, lors de cette étape, consommera d'énormes quantités de mémoire sur le système cible.

Veuillez noter que cette attaque est similaire, bien que différente, à celle de l'attaque Billion Laughs. C'est aussi en rapport avec le CVE-2013-1664 de Python.

Tous les utilisateurs d'une version concernée sont incités à mettre à jour Ruby ou utiliser l'un des contournements ci-dessous immédiatement.

Contournements

Si vous ne pouvez pas mettre à jour Ruby, utiliser ce monkey patch comme contournement :

class REXML::Document
  @@entity_expansion_text_limit = 10_240

  def self.entity_expansion_text_limit=( val )
    @@entity_expansion_text_limit = val
  end

  def self.entity_expansion_text_limit
    @@entity_expansion_text_limit
  end
end

class REXML::Text
  def self.unnormalize(string, doctype=nil, filter=nil, illegal=nil)
    sum = 0
    string.gsub( /\r\n?/, "\n" ).gsub( REFERENCE ) {
      s = self.expand($&, doctype, filter)
      if sum + s.bytesize > REXML::Document.entity_expansion_text_limit
        raise "entity expansion has grown too large"
      else
        sum += s.bytesize
      end
      s
    }
  end

  def self.expand(ref, doctype, filter)
    if ref[1] == ?#
      if ref[2] == ?x
        [ref[3...-1].to_i(16)].pack('U*')
      else
        [ref[2...-1].to_i].pack('U*')
      end
    elsif ref == '&'
      '&'
    elsif filter and filter.include?( ref[1...-1] )
      ref
    elsif doctype
      doctype.entity( ref[1...-1] ) or ref
    else
      entity_value = DocType::DEFAULT_ENTITIES[ ref[1...-1] ]
      entity_value ? entity_value.value : ref
    end
  end
end

Ce monkey patch limitera la taille des remplacements d'entités à 10k par nœud. REXML n'autorise déjà que 10000 remplacements d'entités par document (valeur par défaut). Ainsi la quantité maximale de mémoire pour le texte généré par les remplacements d'entités ne sera au maximum que d'environ 98 Mo.

Les versions de Ruby concernées

  • Toutes les versions de Ruby 1.9 antérieures à la version 1.9.3 patchlevel 392
  • Toutes les versions de Ruby 2.0 antérieures à la version 2.0.0 patchlevel 0
  • Toutes les versions trunk avant la révision 39384

Crédits

Merci à Ben Murphy pour avoir remonté cette vulnérabilité.

Historique

  • Publié initialement à 2013-02-22 12:00:00 (UTC)