Entity expansion DoS vulnerability in REXML (XML bomb, CVE-2013-1821)

Unrestricted entity expansion can lead to a DoS vulnerability in REXML. This vulnerability has been assigned the CVE identifier CVE-2013-1821. We strongly recommend to upgrade ruby.

Details

When reading text nodes from an XML document, the REXML parser can be coerced in to allocating extremely large string objects which can consume all of the memory on a machine, causing a denial of service.

Impacted code will look something like this:

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

When the `text` method is called, entities will be expanded. An attacker can send a relatively small XML document that, when the entities are resolved, will consume extreme amounts of memory on the target system.

Note that this attack is similar to, but different from the Billion Laughs attack. This is also related to CVE-2013-1664 of Python.

All users running an affected release should either upgrade or use one of the workarounds immediately.

Workarounds

If you cannot upgrade Ruby, use this monkey patch as a workaround:

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

This monkey patch will limit the size of the entity substitutions to 10k per node. REXML already defaults to only allow 10000 entity substitutions per document, so the maximum amount of text that can be generated by entity substitution will be around 98 megabytes.

Affected versions

  • All ruby 1.9 versions prior to ruby 1.9.3 patchlevel 392
  • All ruby 2.0 versions prior to ruby 2.0.0 patchlevel 0
  • prior to trunk revision 39384

Credits

Thanks to Ben Murphy for reporting this issue.

History

  • Added about CVE number at 2013-03-11 07:45:00 (UTC)
  • Originally published at 2013-02-22 12:00:00 (UTC)