Skip to content

Instantly share code, notes, and snippets.

@brenes
Created January 9, 2013 12:20
Show Gist options
  • Select an option

  • Save brenes/4492714 to your computer and use it in GitHub Desktop.

Select an option

Save brenes/4492714 to your computer and use it in GitHub Desktop.
CVE-2013-0156 quick patch for Rails 2.3 apps
This patch fixes vulnerability CVE-2013-0156 in ActionPack[1] and it's intended for those apps that can't be updated and must find a quick solution.
Just add the files to your project, require lib/patch/hash_conversions.rb, and that's all.
This code patches ActiveSupport::CoreExtensions::Hash::Conversions methods so it takes into account the disallowed types and is based on 2.3 patch[2]
[1] https://groups.google.com/forum/#!topic/rubyonrails-security/61bkgvnSGTQ/discussion
[2] https://rubyonrails-security.googlegroups.com/attach/c1432d0f8c70e89d/2-3-xml_parsing.patch?gda=ulUUK0YAAABpGVv8GMoSBgW4J3Kez9oiOxgqxuyBYM9wRi8LhPWtIxxSeo4ig8fNU0gXvTeISk5x40jamwa1UURqDcgHarKEE-Ea7GxYMt0t6nY0uV5FIQ&pli=1&view=1&part=3
ActiveSupport::CoreExtensions::Hash::Conversions.module_eval do
DISALLOWED_XML_TYPES = %w(symbol yaml)
class ActiveSupport::CoreExtensions::Hash::Conversions::DisallowedType < StandardError #:nodoc:
def initialize(type)
super "Disallowed type attribute: #{type.inspect}"
end
end
end
ActiveSupport::CoreExtensions::Hash::Conversions::ClassMethods.module_eval do
def from_xml(xml, disallowed_types = nil) typecast_xml_value(unrename_keys(ActiveSupport::XmlMini.parse(xml)), disallowed_types)
end
def typecast_xml_value(value, disallowed_types = nil)
disallowed_types ||= DISALLOWED_XML_TYPES
case value.class.to_s
when 'Hash'
if value.include?('type') && !value['type'].is_a?(Hash) && disallowed_types.include?(value['type'])
raise ActiveSupport::CoreExtensions::Hash::Conversions::DisallowedType, value['type']
end
if value['type'] == 'array'
child_key, entries = value.detect { |k,v| k != 'type' } # child_key is throwaway
if entries.nil? || (c = value['__content__'] && c.blank?)
[]
else
case entries.class.to_s # something weird with classes not matching here. maybe singleton methods breaking is_a?
when "Array"
entries.collect { |v| typecast_xml_value(v, disallowed_types) }
when "Hash"
[typecast_xml_value(entries, disallowed_types)]
else
raise "can't typecast #{entries.inspect}"
end
end
elsif value.has_key?("__content__")
content = value["__content__"]
if parser = ActiveSupport::CoreExtensions::Hash::Conversions::XML_PARSING[value["type"]]
if parser.arity == 2
ActiveSupport::CoreExtensions::Hash::Conversions::XML_PARSING[value["type"]].call(content, value)
else
ActiveSupport::CoreExtensions::Hash::Conversions::XML_PARSING[value["type"]].call(content)
end
else
content
end
elsif value['type'] == 'string' && value['nil'] != 'true'
""
# blank or nil parsed values are represented by nil
elsif value.blank? || 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 |h,(k,v)|
h[k] = typecast_xml_value(v, disallowed_types)
h
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) }
case value.length
when 0 then nil
when 1 then value.first
else value
end
when 'String'
value
else
raise "can't typecast #{value.class.name} - #{value.inspect}"
end
end
end
require File.dirname(__FILE__) + '/../../test/test_helper'
class WebServiceTest < ActionController::IntegrationTest
def test_post_xml_using_a_disallowed_type_attribute
begin
post '/', '<foo type="symbol">value</foo>', 'CONTENT_TYPE' => 'application/xml'
# assert_response 500 won't work as the exception is raised outside the request (we must catch it!) so if we reach next line it means our app accepts wrong params
assert false
rescue ActiveSupport::CoreExtensions::Hash::Conversions::DisallowedType => ex
end
begin
post '/', '<foo type="yaml">value</foo>', 'CONTENT_TYPE' => 'application/xml'
# assert_response 500 won't work as the exception is raised outside the request (we must catch it!) so if we reach next line it means our app accepts wrong params
assert false
rescue ActiveSupport::CoreExtensions::Hash::Conversions::DisallowedType => ex
end
end
def test_post_xml_using_an_allowed_type_attribute
post '/', '<foo>value</foo>', 'CONTENT_TYPE' => 'application/xml'
assert_response 200
end
end
require File.dirname(__FILE__) + '/../../test/test_helper'
class HashToXmlTest < Test::Unit::TestCase
def test_from_xml_raises_on_disallowed_type_attributes
assert_raise Hash::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 Hash::DisallowedType do
Hash.from_xml '<product><name type="symbol">value</name></product>'
end
assert_raise Hash::DisallowedType do
Hash.from_xml '<product><name type="yaml">value</name></product>'
end
end
def test_empty_string_works_for_typecast_xml_value
assert_nothing_raised do
Hash.__send__(:typecast_xml_value, "")
Hash.__send__(:typecast_xml_value, "", [])
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment