Quantcast
Channel: Hacker News
Viewing all articles
Browse latest Browse all 10943

Ronin - Rails PoC exploits for CVE-2013-0156 and CVE-2013-0155

$
0
0

Comments:"Ronin - Rails PoC exploits for CVE-2013-0156 and CVE-2013-0155"

URL:http://ronin-ruby.github.com/blog/2013/01/09/rails-pocs.html


TL;DR: Exploits are out, update Rails!

On January 8th, Aaron Patterson announced CVE-2013-0156, multiple vulnerabilities in parameter parsing in Action Pack allowing attackers to:

  • Bypass Authentication systems
  • Inject Arbitrary SQL
  • Perform a Denial of Service (DoS)
  • Execute arbitrary code

However, rumors of this vulnerability had been circulating on twitter as far back as [CVE-2012-5664]. Others also claimed to have working PoC exploits, but would not release them for fear of the PoCs being used maliciously. Naturally, I was interested in investigating the vulnerability.

It all started when Phenoelit discovered a vulnerability in how authentication plugins (such as AuthLogic) pass parameters tofind_by_* methods. [CVE-2012-5664] was then posted, stirring Twitter into a frenzy. However, the possibility of exploitation was limited, as detailed on the Phusion Corporate Blog. Thus the hunt began.

Intro to params in Rails

Params are first parsed by ActionDispatch::Middleware::ParamsParser, which detects the MIME type of the request and parses the body appropriately. By default ParamsParser only supports parsing XML and JSON requests. After the request body is parsed, the resulting data is coerced into a HashWithIndifferentAccess, ensuring all Hash keys are Strings.

Next, [ActionDispatch::Http::Parameters] takes the parsed request parameters and merges them with the path parameters. Note that the path parameters are first merged into the request parameters, to ensure that the request parameters cannot override the path parameters. Also note that when a Hash is merged into a HashWithIndifferentAccess, all keys are converted to Strings and all sub-Hashes converted to Indifferent ones. This ensures thatparams contains no Symbol keys and cannot be passed to find_by_* methods; despite what [CVE-2012-5664] claims.

XML Deserialization

The Rails XML module (ActiveSupport::XmlMini) supports deserializing various primitives such as Integer, Symbol, String, Date, Time, etc. However,XmlMini also supports deserializing embedded YAML blobs. One might wonder, why this would be a good idea? Apparently, to support serializing/deserializing ActiveRecord models that contain serialized YAML.

What can we do with this? Deserialize arbitrary Objects for Classes already loaded by the Rails application.

YAML

When Psych parses !ruby/object:MyClass objects, it will callMyClass.allocate which returns a blank uninitialized instances of MyClass. Next, Psych will call instance_variable_set to set various instance variables.

Interestingly, Psych allows for arbitrary classes to be specified with!ruby/string and !ruby/hash declarations:

!ruby/hash:MyHashLikeClass
key1: value1
key2: value2

When Psych parses !ruby/hash:Class, it will actually call #initialize and then call #[]= to populate the objects fields.

PoCs

The Proof of Concept (PoC) exploits rely on abusing the Psych YAML parser and how it allows specifying arbitrary classes for !ruby/string and!ruby/hash YAML objects.

All of the following PoCs require the ronin-support gem and licensed underGPLv3.

Symbol DoS

rails_dos.rb

The Denial of Service vulnerability relies on the fact that even in Ruby 1.9 Symbols are not Garbage Collected. Even if HashWithIndifferentAccess converts the Symbols to Strings, the Symbols will remain in memory.

All we have to do is repeatedly send requests containing unique Symbols. To accomplish this we use the String.generate method to generate alphabetic Symbol names of varying length.

Unsafe Query Generation via JSON

rails_jsonq.rb

ActionDispatch::Middleware::ParamsParser also supports parsing JSON params from requests. However, it does not normalize the parsed params. Values such as [nil] or [""] are not normalized to nil and "". This allows us to bypass #nil? or #empty? checks, such as described in CVE-2013-0155:

unless params[:token].nil?
 user = User.find_by_token(params[:token])
 user.reset_password!
end

SQL Injection

rails_sqli.rb

Knowing that we cannot simply craft a XML+YAML request containing the :select option with some raw SQL, we have to look for an alternate code-path. As the Insinuator blog post points out, find_by_* methods can actually accept Arel::Node objects! Potentially, we can inject any of theArel::Nodes. The most promising of these is Arel::Nodes::SqlLiteral. Unfortunately, calling Arel::Nodes::SqlLiteral#to_yaml does not work, so we must hand craft specific YAML:

--- !ruby/string:Arel::Nodes::SqlLiteral "SQL here"

Note that since Arel::Nodes::SqlLiteral inherits from String,!ruby/object:Arel::Nodes::SqlLiteral actually deserializes to a plain String; thus !ruby/string is necessary.

We could get creative and inject in an Abstract Syntax Tree (AST) of our desired SQL:

--- !ruby/object:Arel::Nodes::Or
left: 0
right: !ruby/object:Arel::Nodes::Equality
 left: 1
 right: 1

Remote Code Execution

rails_rce.rb

As discussed in this Insinuator blog post, it may be possible to override an instance variable that is later passed to instance_eval, class_eval,module_eval or send. One such example is using ERB:

--- !ruby/object:ERB
src: _erbout = puts 'lol'

However, this relies on Rails calling #run or #result. This turns out to be rather difficult, since ActiveRecord/Arel will only allow certain types of objects be passed to find_by_* methods.

Since, we know Psych will call #initialize when parsing !ruby/hash:MyClass we just need to find a Hash like class. Luckily an anonymous contributor discovered such a class and told the Metasploit developers, which got published on the Rapid7 Community blog. The blog post then circulated Twitter and a friend pointed me to the class.

Update: After publishing the PoCs, lian contacted me and identified himself as the anonymous contributor who told HD Moore about the class. I then convinced him to take credit for his work on this vulnerability. Thanks to lian's solution, I was able to finish writing the exploit. In my opinion, if you give a famous Security Research your own research, you should publish it yourself to receive proper recognition and inform us not-so-famous Security researchers. ;)

The class in question is ActionDispatch::Routing::RouteSet::NamedRouteCollection. The class initializes variables in #initialize and aliases #[]= to theadd method. The add method then leads to define_named_route_methods, which leads to define_named_route_methods, then to define_url_helper and finally module_eval. We are in business.

Now to figure out how to escape our Ruby code, such that def #{name} is ignored. Luckily, Ruby provides a special keyword (__END__) which causes the remainder of Ruby code to be treated as inline data.

code = "puts 'lol'"
escaped_code = "foo; #{code}\n__END__\n"

Now we need a convincing route Object for define_url_helper. Inspecting the method, our route must respond to defaults, requirements,required_parts, segment_keys. Luckily, all of these methods appear to be reader methods, so we can mock up a route using an OpenStruct.

After some massaging of the YAML, victory! In fact, our method worked so well that no exceptions were raised and our code is evaluated for each url helper that is defined (four times):

lol
lol
lol
lol
Started POST "/secrets/search" for 127.0.0.1 at 2013-01-09 19:35:48 -0800
Processing by SecretsController#search as */*
 Parameters: {"secret"=>#<ActionDispatch::Routing::RouteSet::NamedRouteCollection:0x007f5474264218 @routes={:"foo; puts 'lol'\n__END__\n"=>#<OpenStruct defaults={:action=>"create", :controller=>"foos"}, required_parts=[], requirements={:action=>"create", :controller=>"foos"}, segment_keys=[:format]>}, @helpers=[:"hash_for_foo; puts 'lol'\n__END__\n_url", :"foo; puts 'lol'\n__END__\n_url", :"hash_for_foo; puts 'lol'\n__END__\n_path", :"foo; puts 'lol'\n__END__\n_path"], @module=#<Module:0x007f54742641a0>>}
WARNING: Can't verify CSRF token authenticity
Completed 500 Internal Server Error in 1ms

Viewing all articles
Browse latest Browse all 10943

Trending Articles