CVE-2013-0156: Safe XML params parsing. Doesn't allow symbols or yaml.

This commit is contained in:
Jeremy Kemper 2013-01-05 17:46:26 -07:00 committed by Aaron Patterson
parent d99e8c9e16
commit 2ced6f2f8a
4 changed files with 65 additions and 10 deletions

@ -116,6 +116,19 @@ def test_post_xml_using_an_attributted_node_named_type
end
end
def test_post_xml_using_a_disallowed_type_attribute
$stderr = StringIO.new
with_test_route_set do
post '/', '<foo type="symbol">value</foo>', 'CONTENT_TYPE' => 'application/xml'
assert_response 500
post '/', '<foo type="yaml">value</foo>', 'CONTENT_TYPE' => 'application/xml'
assert_response 500
end
ensure
$stderr = STDERR
end
def test_register_and_use_yaml
with_test_route_set do
with_params_parsers Mime::YAML => Proc.new { |d| YAML.load(d) } do

@ -1,5 +1,12 @@
## Rails 4.0.0 (unreleased) ##
* Hash.from_xml raises when it encounters type="symbol" or type="yaml".
Use Hash.from_trusted_xml to parse this XML.
CVE-2013-0156
*Jeremy Kemper*
* Deprecate `assert_present` and `assert_blank` in favor of
`assert object.blank?` and `assert object.present?`

@ -101,17 +101,33 @@ class << self
#
# hash = Hash.from_xml(xml)
# # => {"hash"=>{"foo"=>1, "bar"=>2}}
def from_xml(xml)
ActiveSupport::XMLConverter.new(xml).to_h
#
# DisallowedType is raise if the XML contains attributes with <tt>type="yaml"</tt> or
# <tt>type="symbol"</tt>. Use <tt>Hash.from_trusted_xml</tt> to parse this XML.
def from_xml(xml, disallowed_types = nil)
ActiveSupport::XMLConverter.new(xml, disallowed_types).to_h
end
# Builds a Hash from XML just like <tt>Hash.from_xml</tt>, but also allows Symbol and YAML.
def from_trusted_xml(xml)
from_xml xml, []
end
end
end
module ActiveSupport
class XMLConverter # :nodoc:
def initialize(xml)
class DisallowedType < StandardError
def initialize(type)
super "Disallowed type attribute: #{type.inspect}"
end
end
DISALLOWED_TYPES = %w(symbol yaml)
def initialize(xml, disallowed_types = nil)
@xml = normalize_keys(XmlMini.parse(xml))
@disallowed_types = disallowed_types || DISALLOWED_TYPES
end
def to_h
@ -119,7 +135,6 @@ def to_h
end
private
def normalize_keys(params)
case params
when Hash
@ -145,6 +160,10 @@ def deep_to_h(value)
end
def process_hash(value)
if value.include?('type') && !value['type'].is_a?(Hash) && @disallowed_types.include?(value['type'])
raise DisallowedType, value['type']
end
if become_array?(value)
_, entries = Array.wrap(value.detect { |k,v| not v.is_a?(String) })
if entries.nil? || value['__content__'].try(:empty?)

@ -1015,12 +1015,10 @@ def test_single_record_from_xml
<replies-close-in type="integer">2592000000</replies-close-in>
<written-on type="date">2003-07-16</written-on>
<viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
<content type="yaml">--- \n1: should be an integer\n:message: Have a nice day\narray: \n- should-have-dashes: true\n should_have_underscores: true\n</content>
<author-email-address>david@loudthinking.com</author-email-address>
<parent-id></parent-id>
<ad-revenue type="decimal">1.5</ad-revenue>
<optimum-viewing-angle type="float">135</optimum-viewing-angle>
<resident type="symbol">yes</resident>
</topic>
EOT
@ -1033,12 +1031,10 @@ def test_single_record_from_xml
:replies_close_in => 2592000000,
:written_on => Date.new(2003, 7, 16),
:viewed_at => Time.utc(2003, 7, 16, 9, 28),
:content => { :message => "Have a nice day", 1 => "should be an integer", "array" => [{ "should-have-dashes" => true, "should_have_underscores" => true }] },
:author_email_address => "david@loudthinking.com",
:parent_id => nil,
:ad_revenue => BigDecimal("1.50"),
:optimum_viewing_angle => 135.0,
:resident => :yes
}.stringify_keys
assert_equal expected_topic_hash, Hash.from_xml(topic_xml)["topic"]
@ -1052,7 +1048,6 @@ def test_single_record_from_xml_with_nil_values
<approved type="boolean"></approved>
<written-on type="date"></written-on>
<viewed-at type="datetime"></viewed-at>
<content type="yaml"></content>
<parent-id></parent-id>
</topic>
EOT
@ -1063,7 +1058,6 @@ def test_single_record_from_xml_with_nil_values
:approved => nil,
:written_on => nil,
:viewed_at => nil,
:content => nil,
:parent_id => nil
}.stringify_keys
@ -1290,6 +1284,28 @@ def test_type_trickles_through_when_unknown
assert_equal expected_product_hash, Hash.from_xml(product_xml)["product"]
end
def test_from_xml_raises_on_disallowed_type_attributes
assert_raise ActiveSupport::XMLConverter::DisallowedType do
Hash.from_xml '<product><name type="foo">value</name></product>', %w(foo)
end
end
def test_from_xml_disallows_symbol_and_yaml_types_by_default
assert_raise ActiveSupport::XMLConverter::DisallowedType do
Hash.from_xml '<product><name type="symbol">value</name></product>'
end
assert_raise ActiveSupport::XMLConverter::DisallowedType do
Hash.from_xml '<product><name type="yaml">value</name></product>'
end
end
def test_from_trusted_xml_allows_symbol_and_yaml_types
expected = { 'product' => { 'name' => :value }}
assert_equal expected, Hash.from_trusted_xml('<product><name type="symbol">value</name></product>')
assert_equal expected, Hash.from_trusted_xml('<product><name type="yaml">:value</name></product>')
end
def test_should_use_default_value_for_unknown_key
hash_wia = HashWithIndifferentAccess.new(3)
assert_equal 3, hash_wia[:new_key]