secret
Last active

Addresses a security vulnerability in multi_xml that is identical to the recent rails vulnerability. This is very, very serious and includes the possibility of remote code exploit and SQL injection. UPDATE TO 0.5.2 NOW! <strike-through>PATCH NOW!</strike-through>

  • Download Gist
how_to_use.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14
A new version of multi_xml has been released with this patch. Go get it!
 
https://rubygems.org/gems/multi_xml/versions/0.5.2
 
 
<strike-through>
TO USE WITH RAILS:
 
Put multi_xml_patch.rb in the RAILS_ROOT/config/initializers directory.
 
TO USE OUTSIDE RAILS:
 
Put multi_xml_patch.rb in one of your load paths and require it after loading multi_xml.
</strike-through>
multi_xml_patch.rb
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
# Same vulnerability as CVE-2013-0156
# https://groups.google.com/forum/#!topic/rubyonrails-security/61bkgvnSGTQ/discussion
 
# Code has been submitted back to the project:
# https://github.com/sferik/multi_xml/pull/34
 
# Until the fix is released, use this monkey-patch.
 
require "multi_xml"
 
module MultiXml
class DisallowedTypeError < StandardError
def initialize(type)
super "Disallowed type attribute: #{type.inspect}"
end
end
 
DISALLOWED_XML_TYPES = %w(symbol yaml)
 
class << self
def parse(xml, options={})
xml ||= ''
 
xml.strip! if xml.respond_to?(:strip!)
begin
xml = StringIO.new(xml) unless xml.respond_to?(:read)
 
char = xml.getc
return {} if char.nil?
xml.ungetc(char)
 
hash = typecast_xml_value(undasherize_keys(parser.parse(xml)), options[:disallowed_types]) || {}
rescue DisallowedTypeError
raise
rescue parser.parse_error => error
raise ParseError, error.to_s, error.backtrace
end
hash = symbolize_keys(hash) if options[:symbolize_keys]
hash
end
 
private
 
def typecast_xml_value(value, disallowed_types=nil)
disallowed_types ||= DISALLOWED_XML_TYPES
 
case value
when Hash
if value.include?('type') && !value['type'].is_a?(Hash) && disallowed_types.include?(value['type'])
raise DisallowedTypeError, value['type']
end
 
if value['type'] == 'array'
 
# this commented-out suggestion helps to avoid the multiple attribute
# problem, but it breaks when there is only one item in the array.
#
# from: https://github.com/jnunemaker/httparty/issues/102
#
# _, entries = value.detect { |k, v| k != 'type' && v.is_a?(Array) }
 
# This attempt fails to consider the order that the detect method
# retrieves the entries.
#_, entries = value.detect {|key, _| key != 'type'}
 
# This approach ignores attribute entries that are not convertable
# to an Array which allows attributes to be ignored.
_, entries = value.detect {|k, v| k != 'type' && (v.is_a?(Array) || v.is_a?(Hash)) }
 
if entries.nil? || (entries.is_a?(String) && entries.strip.empty?)
[]
else
case entries
when Array
entries.map {|entry| typecast_xml_value(entry, disallowed_types)}
when Hash
[typecast_xml_value(entries, disallowed_types)]
else
raise "can't typecast #{entries.class.name}: #{entries.inspect}"
end
end
elsif value.has_key?(CONTENT_ROOT)
content = value[CONTENT_ROOT]
if block = PARSING[value['type']]
if block.arity == 1
value.delete('type') if PARSING[value['type']]
if value.keys.size > 1
value[CONTENT_ROOT] = block.call(content)
value
else
block.call(content)
end
else
block.call(content, value)
end
else
value.keys.size > 1 ? value : content
end
elsif value['type'] == 'string' && value['nil'] != 'true'
''
# blank or nil parsed values are represented by nil
elsif value.empty? || value['nil'] == 'true'
nil
# If the type is the only element which makes it then
# this still makes the value nil, except if type is
# a XML node(where type['value'] is a Hash)
elsif value['type'] && value.size == 1 && !value['type'].is_a?(Hash)
nil
else
xml_value = value.inject({}) do |hash, (k, v)|
hash[k] = typecast_xml_value(v, disallowed_types)
hash
end
 
# Turn {:files => {:file => #<StringIO>} into {:files => #<StringIO>} so it is compatible with
# how multipart uploaded files from HTML appear
xml_value['file'].is_a?(StringIO) ? xml_value['file'] : xml_value
end
when Array
value.map!{|i| typecast_xml_value(i, disallowed_types)}
value.length > 1 ? value : value.first
when String
value
else
raise "can't typecast #{value.class.name}: #{value.inspect}"
end
end
end
end

Can this vulnerability be attacked in the exact same manner or does it require more specific knowledge? My concern is if one has updated to the newest rails version but not multi_xml 0.5.2, does the attack need to be formulated differently at all?

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.