Created
January 9, 2013 12:20
-
-
Save brenes/4492714 to your computer and use it in GitHub Desktop.
CVE-2013-0156 quick patch for Rails 2.3 apps
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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