Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/windows/gather/ad_to_sqlite.rb
19592 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
require 'sqlite3'
7
require 'tempfile'
8
9
class MetasploitModule < Msf::Post
10
include Msf::Post::Windows::LDAP
11
12
def initialize(info = {})
13
super(
14
update_info(
15
info,
16
'Name' => 'AD Computer, Group and Recursive User Membership to Local SQLite DB',
17
'Description' => %q{
18
This module will gather a list of AD groups, identify the users (taking into account recursion)
19
and write this to a SQLite database for offline analysis and query using normal SQL syntax.
20
},
21
'License' => MSF_LICENSE,
22
'Author' => [
23
'Stuart Morgan <stuart.morgan[at]mwrinfosecurity.com>'
24
],
25
'Platform' => [ 'win' ],
26
'SessionTypes' => [ 'meterpreter' ],
27
'Notes' => {
28
'Stability' => [CRASH_SAFE],
29
'SideEffects' => [],
30
'Reliability' => []
31
}
32
)
33
)
34
35
register_options([
36
OptString.new('GROUP_FILTER', [false, 'Additional LDAP filters to use when searching for initial groups', '']),
37
OptBool.new('SHOW_USERGROUPS', [true, 'Show the user/group membership in a greppable form to the console.', false]),
38
OptBool.new('SHOW_COMPUTERS', [true, 'Show basic computer information in a greppable form to the console.', false]),
39
OptInt.new('THREADS', [true, 'Number of threads to spawn to gather membership of each group.', 20])
40
])
41
end
42
43
def run
44
max_search = datastore['MAX_SEARCH']
45
46
db, dbfile = create_sqlite_db
47
print_status("Temporary database created: #{dbfile.path}")
48
49
# Download the list of groups from Active Directory
50
vprint_status('Retrieving AD Groups')
51
begin
52
group_fields = ['distinguishedName', 'objectSid', 'samAccountType', 'sAMAccountName', 'whenChanged', 'whenCreated', 'description', 'groupType', 'adminCount', 'comment', 'managedBy', 'cn']
53
if datastore['GROUP_FILTER'].nil? || datastore['GROUP_FILTER'].empty?
54
group_query = '(objectClass=group)'
55
else
56
group_query = "(&(objectClass=group)(#{datastore['GROUP_FILTER']}))"
57
end
58
groups = query(group_query, max_search, group_fields)
59
rescue ::RuntimeError, ::Rex::Post::Meterpreter::RequestError => e
60
print_error("Error(Group): #{e.message}")
61
return
62
end
63
64
# If no groups were downloaded, there's no point carrying on
65
if groups.nil? || groups[:results].empty?
66
print_error('No AD groups were discovered')
67
return
68
end
69
70
# Go through each of the groups and identify the individual users in each group
71
vprint_status("Groups retrieval completed: #{groups[:results].size} group(s)")
72
vprint_status('Retrieving AD Group Membership')
73
users_fields = ['distinguishedName', 'objectSid', 'sAMAccountType', 'sAMAccountName', 'displayName', 'description', 'logonCount', 'userAccountControl', 'userPrincipalName', 'whenChanged', 'whenCreated', 'primaryGroupID', 'badPwdCount', 'comment', 'title', 'cn', 'adminCount', 'manager']
74
75
remaining_groups = groups[:results]
76
77
# If the number of threads exceeds the number of groups, reduce them down to the correct number
78
threadcount = remaining_groups.count < datastore['THREADS'] ? remaining_groups.count : datastore['THREADS']
79
80
# Loop through each of the groups, creating threads where necessary
81
while !remaining_groups.nil? && !remaining_groups.empty?
82
group_gather = []
83
1.upto(threadcount) do
84
group_gather << framework.threads.spawn("Module(#{refname})", false, remaining_groups.shift) do |individual_group|
85
next if !individual_group || individual_group.empty? || individual_group.nil?
86
87
# Get the Group RID
88
group_rid = get_rid(individual_group[1][:value]).to_i
89
90
# Perform the ADSI query to retrieve the effective users in each group (recursion)
91
vprint_status "Retrieving members of #{individual_group[3][:value]}"
92
users_filter = "(&(objectCategory=person)(objectClass=user)(|(memberOf:1.2.840.113556.1.4.1941:=#{individual_group[0][:value]})(primaryGroupID=#{group_rid})))"
93
users_in_group = query(users_filter, max_search, users_fields)
94
95
grouptype_int = individual_group[7][:value].to_i # Set this here because it is used a lot below
96
sat_int = individual_group[2][:value].to_i
97
98
# Add the group to the database
99
# groupType parameter interpretation: https://msdn.microsoft.com/en-us/library/windows/desktop/ms675935(v=vs.85).aspx
100
# Note that the conversions to UTF-8 are necessary because of the way SQLite detects column type affinity
101
# Turns out that the 'fix' is documented in https://github.com/rails/rails/issues/1965
102
sql_param_group = {
103
g_rid: group_rid,
104
g_distinguishedName: individual_group[0][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
105
g_sAMAccountType: sat_int,
106
g_sAMAccountName: individual_group[3][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
107
g_whenChanged: individual_group[4][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
108
g_whenCreated: individual_group[5][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
109
g_description: individual_group[6][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
110
g_groupType: grouptype_int,
111
g_adminCount: individual_group[8][:value].to_i,
112
g_comment: individual_group[9][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
113
g_managedBy: individual_group[10][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
114
g_cn: individual_group[11][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
115
# Specifies a group that is created by the system.
116
g_GT_GROUP_CREATED_BY_SYSTEM: (grouptype_int & 0x00000001).zero? ? 0 : 1,
117
# Specifies a group with global scope.
118
g_GT_GROUP_SCOPE_GLOBAL: (grouptype_int & 0x00000002).zero? ? 0 : 1,
119
# Specifies a group with local scope.
120
g_GT_GROUP_SCOPE_LOCAL: (grouptype_int & 0x00000004).zero? ? 0 : 1,
121
# Specifies a group with universal scope.
122
g_GT_GROUP_SCOPE_UNIVERSAL: (grouptype_int & 0x00000008).zero? ? 0 : 1,
123
# Specifies an APP_BASIC group for Windows Server Authorization Manager.
124
g_GT_GROUP_SAM_APP_BASIC: (grouptype_int & 0x00000010).zero? ? 0 : 1,
125
# Specifies an APP_QUERY group for Windows Server Authorization Manager.
126
g_GT_GROUP_SAM_APP_QUERY: (grouptype_int & 0x00000020).zero? ? 0 : 1,
127
# Specifies a security group. If this flag is not set, then the group is a distribution group.
128
g_GT_GROUP_SECURITY: (grouptype_int & 0x80000000).zero? ? 0 : 1,
129
# The inverse of the flag above. Technically GT_GROUP_SECURITY=0 makes it a distribution
130
# group so this is arguably redundant, but I have included it for ease. It makes a lot more sense
131
# to set DISTRIBUTION=1 in a query when your mind is on other things to remember that
132
# DISTRIBUTION is in fact the inverse of SECURITY...:)
133
g_GT_GROUP_DISTRIBUTION: (grouptype_int & 0x80000000).zero? ? 1 : 0,
134
# Now add sAMAccountType constants
135
g_SAM_DOMAIN_OBJECT: (sat_int == 0) ? 1 : 0,
136
g_SAM_GROUP_OBJECT: (sat_int == 0x10000000) ? 1 : 0,
137
g_SAM_NON_SECURITY_GROUP_OBJECT: (sat_int == 0x10000001) ? 1 : 0,
138
g_SAM_ALIAS_OBJECT: (sat_int == 0x20000000) ? 1 : 0,
139
g_SAM_NON_SECURITY_ALIAS_OBJECT: (sat_int == 0x20000001) ? 1 : 0,
140
g_SAM_NORMAL_USER_ACCOUNT: (sat_int == 0x30000000) ? 1 : 0,
141
g_SAM_MACHINE_ACCOUNT: (sat_int == 0x30000001) ? 1 : 0,
142
g_SAM_TRUST_ACCOUNT: (sat_int == 0x30000002) ? 1 : 0,
143
g_SAM_APP_BASIC_GROUP: (sat_int == 0x40000000) ? 1 : 0,
144
g_SAM_APP_QUERY_GROUP: (sat_int == 0x40000001) ? 1 : 0,
145
g_SAM_ACCOUNT_TYPE_MAX: (sat_int == 0x7fffffff) ? 1 : 0
146
}
147
run_sqlite_query(db, 'ad_groups', sql_param_group)
148
149
# Go through each group user
150
next if users_in_group[:results].empty?
151
152
users_in_group[:results].each do |group_user|
153
user_rid = get_rid(group_user[1][:value]).to_i
154
print_line "Group [#{individual_group[3][:value]}][#{group_rid}] has member [#{group_user[3][:value]}][#{user_rid}]" if datastore['SHOW_USERGROUPS']
155
156
uac_int = group_user[7][:value].to_i # Set this because it is used so frequently below
157
sat_int = group_user[2][:value].to_i
158
159
# Add the group to the database
160
# Also parse the ADF_ flags from userAccountControl: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680832(v=vs.85).aspx
161
sql_param_user = {
162
u_rid: user_rid,
163
u_distinguishedName: group_user[0][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
164
u_sAMAccountType: group_user[2][:value].to_i,
165
u_sAMAccountName: group_user[3][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
166
u_displayName: group_user[4][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
167
u_description: group_user[5][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
168
u_logonCount: group_user[6][:value].to_i,
169
u_userAccountControl: uac_int,
170
u_userPrincipalName: group_user[8][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
171
u_whenChanged: group_user[9][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
172
u_whenCreated: group_user[10][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
173
u_primaryGroupID: group_user[11][:value].to_i,
174
u_badPwdCount: group_user[12][:value].to_i,
175
u_comment: group_user[13][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
176
u_title: group_user[14][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
177
u_cn: group_user[15][:value].to_s.encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
178
# Indicates that a given object has had its ACLs changed to a more secure value by the
179
# system because it was a member of one of the administrative groups (directly or transitively).
180
u_adminCount: group_user[16][:value].to_i,
181
u_manager: group_user[17][:value].to_s.encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
182
# The login script is executed
183
u_ADS_UF_SCRIPT: (uac_int & 0x00000001).zero? ? 0 : 1,
184
# The user account is disabled.
185
u_ADS_UF_ACCOUNTDISABLE: (uac_int & 0x00000002).zero? ? 0 : 1,
186
# The home directory is required.
187
u_ADS_UF_HOMEDIR_REQUIRED: (uac_int & 0x00000008).zero? ? 0 : 1,
188
# The account is currently locked out.
189
u_ADS_UF_LOCKOUT: (uac_int & 0x00000010).zero? ? 0 : 1,
190
# No password is required.
191
u_ADS_UF_PASSWD_NOTREQD: (uac_int & 0x00000020).zero? ? 0 : 1,
192
# The user cannot change the password.
193
u_ADS_UF_PASSWD_CANT_CHANGE: (uac_int & 0x00000040).zero? ? 0 : 1,
194
# The user can send an encrypted password.
195
u_ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED: (uac_int & 0x00000080).zero? ? 0 : 1,
196
# This is an account for users whose primary account is in another domain. This account
197
# provides user access to this domain, but not to any domain that trusts this domain.
198
# Also known as a local user account.
199
u_ADS_UF_TEMP_DUPLICATE_ACCOUNT: (uac_int & 0x00000100).zero? ? 0 : 1,
200
# This is a default account type that represents a typical user.
201
u_ADS_UF_NORMAL_ACCOUNT: (uac_int & 0x00000200).zero? ? 0 : 1,
202
# This is a permit to trust account for a system domain that trusts other domains.
203
u_ADS_UF_INTERDOMAIN_TRUST_ACCOUNT: (uac_int & 0x00000800).zero? ? 0 : 1,
204
# This is a computer account for a computer that is a member of this domain.
205
u_ADS_UF_WORKSTATION_TRUST_ACCOUNT: (uac_int & 0x00001000).zero? ? 0 : 1,
206
# This is a computer account for a system backup domain controller that is a member of this domain.
207
u_ADS_UF_SERVER_TRUST_ACCOUNT: (uac_int & 0x00002000).zero? ? 0 : 1,
208
# The password for this account will never expire.
209
u_ADS_UF_DONT_EXPIRE_PASSWD: (uac_int & 0x00010000).zero? ? 0 : 1,
210
# This is an MNS logon account.
211
u_ADS_UF_MNS_LOGON_ACCOUNT: (uac_int & 0x00020000).zero? ? 0 : 1,
212
# The user must log on using a smart card.
213
u_ADS_UF_SMARTCARD_REQUIRED: (uac_int & 0x00040000).zero? ? 0 : 1,
214
# The service account (user or computer account), under which a service runs, is trusted for Kerberos delegation.
215
# Any such service can impersonate a client requesting the service.
216
u_ADS_UF_TRUSTED_FOR_DELEGATION: (uac_int & 0x00080000).zero? ? 0 : 1,
217
# The security context of the user will not be delegated to a service even if the service
218
# account is set as trusted for Kerberos delegation.
219
u_ADS_UF_NOT_DELEGATED: (uac_int & 0x00100000).zero? ? 0 : 1,
220
# Restrict this principal to use only Data #Encryption Standard (DES) encryption types for keys.
221
u_ADS_UF_USE_DES_KEY_ONLY: (uac_int & 0x00200000).zero? ? 0 : 1,
222
# This account does not require Kerberos pre-authentication for logon.
223
u_ADS_UF_DONT_REQUIRE_PREAUTH: (uac_int & 0x00400000).zero? ? 0 : 1,
224
# The password has expired
225
u_ADS_UF_PASSWORD_EXPIRED: (uac_int & 0x00800000).zero? ? 0 : 1,
226
# The account is enabled for delegation. This is a security-sensitive setting; accounts with
227
# this option enabled should be strictly controlled. This setting enables a service running
228
# under the account to assume a client identity and authenticate as that user to other remote
229
# servers on the network.
230
u_ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: (uac_int & 0x01000000).zero? ? 0 : 1,
231
# Now add sAMAccountType constants
232
u_SAM_DOMAIN_OBJECT: (sat_int == 0) ? 1 : 0,
233
u_SAM_GROUP_OBJECT: (sat_int == 0x10000000) ? 1 : 0,
234
u_SAM_NON_SECURITY_GROUP_OBJECT: (sat_int == 0x10000001) ? 1 : 0,
235
u_SAM_ALIAS_OBJECT: (sat_int == 0x20000000) ? 1 : 0,
236
u_SAM_NON_SECURITY_ALIAS_OBJECT: (sat_int == 0x20000001) ? 1 : 0,
237
u_SAM_NORMAL_USER_ACCOUNT: (sat_int == 0x30000000) ? 1 : 0,
238
u_SAM_MACHINE_ACCOUNT: (sat_int == 0x30000001) ? 1 : 0,
239
u_SAM_TRUST_ACCOUNT: (sat_int == 0x30000002) ? 1 : 0,
240
u_SAM_APP_BASIC_GROUP: (sat_int == 0x40000000) ? 1 : 0,
241
u_SAM_APP_QUERY_GROUP: (sat_int == 0x40000001) ? 1 : 0,
242
u_SAM_ACCOUNT_TYPE_MAX: (sat_int == 0x7fffffff) ? 1 : 0
243
}
244
run_sqlite_query(db, 'ad_users', sql_param_user)
245
246
# Now associate the user with the group
247
sql_param_mapping = {
248
user_rid: user_rid,
249
group_rid: group_rid
250
}
251
run_sqlite_query(db, 'ad_mapping', sql_param_mapping)
252
end
253
rescue ::RuntimeError, ::Rex::Post::Meterpreter::RequestError => e
254
print_error("Error(Users): #{e.message}")
255
next
256
end
257
end
258
group_gather.map(&:join)
259
end
260
261
vprint_status('Retrieving computers')
262
begin
263
computer_filter = '(objectClass=computer)'
264
computer_fields = ['distinguishedName', 'objectSid', 'cn', 'dNSHostName', 'sAMAccountType', 'sAMAccountName', 'displayName', 'logonCount', 'userAccountControl', 'whenChanged', 'whenCreated', 'primaryGroupID', 'badPwdCount', 'operatingSystem', 'operatingSystemServicePack', 'operatingSystemVersion', 'description', 'comment']
265
computers = query(computer_filter, max_search, computer_fields)
266
267
computers[:results].each do |comp|
268
computer_rid = get_rid(comp[1][:value]).to_i
269
270
uac_int = comp[8][:value].to_i # Set this because it is used so frequently below
271
sat_int = comp[4][:value].to_i
272
273
# Add the group to the database
274
# Also parse the ADF_ flags from userAccountControl: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680832(v=vs.85).aspx
275
# Note that userAccountControl is basically the same for a computer as a user; this is because a computer account is derived from a user account
276
# (if you look at the objectClass for a computer account, it includes 'user') and, for efficiency, we should really store it all in one
277
# table. However, the reality is that it will get annoying for users to have to remember to use the userAccountControl flags to work out whether
278
# its a user or a computer and so, for convenience and ease of use, I have put them in completely separate tables.
279
# Also add the sAMAccount type flags from https://msdn.microsoft.com/en-us/library/windows/desktop/ms679637(v=vs.85).aspx
280
sql_param_computer = {
281
c_rid: computer_rid,
282
c_distinguishedName: comp[0][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
283
c_cn: comp[2][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
284
c_dNSHostName: comp[3][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
285
c_sAMAccountType: sat_int,
286
c_sAMAccountName: comp[5][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
287
c_displayName: comp[6][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
288
c_logonCount: comp[7][:value].to_i,
289
c_userAccountControl: uac_int,
290
c_whenChanged: comp[9][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
291
c_whenCreated: comp[10][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
292
c_primaryGroupID: comp[11][:value].to_i,
293
c_badPwdCount: comp[12][:value].to_i,
294
c_operatingSystem: comp[13][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
295
c_operatingSystemServicePack: comp[14][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
296
c_operatingSystemVersion: comp[15][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
297
c_description: comp[16][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
298
c_comment: comp[17][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
299
# The login script is executed
300
c_ADS_UF_SCRIPT: (uac_int & 0x00000001).zero? ? 0 : 1,
301
# The user account is disabled.
302
c_ADS_UF_ACCOUNTDISABLE: (uac_int & 0x00000002).zero? ? 0 : 1,
303
# The home directory is required.
304
c_ADS_UF_HOMEDIR_REQUIRED: (uac_int & 0x00000008).zero? ? 0 : 1,
305
# The account is currently locked out.
306
c_ADS_UF_LOCKOUT: (uac_int & 0x00000010).zero? ? 0 : 1,
307
# No password is required.
308
c_ADS_UF_PASSWD_NOTREQD: (uac_int & 0x00000020).zero? ? 0 : 1,
309
# The user cannot change the password.
310
c_ADS_UF_PASSWD_CANT_CHANGE: (uac_int & 0x00000040).zero? ? 0 : 1,
311
# The user can send an encrypted password.
312
c_ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED: (uac_int & 0x00000080).zero? ? 0 : 1,
313
# This is an account for users whose primary account is in another domain. This account
314
# provides user access to this domain, but not to any domain that trusts this domain.
315
# Also known as a local user account.
316
c_ADS_UF_TEMP_DUPLICATE_ACCOUNT: (uac_int & 0x00000100).zero? ? 0 : 1,
317
# This is a default account type that represents a typical user.
318
c_ADS_UF_NORMAL_ACCOUNT: (uac_int & 0x00000200).zero? ? 0 : 1,
319
# This is a permit to trust account for a system domain that trusts other domains.
320
c_ADS_UF_INTERDOMAIN_TRUST_ACCOUNT: (uac_int & 0x00000800).zero? ? 0 : 1,
321
# This is a computer account for a computer that is a member of this domain.
322
c_ADS_UF_WORKSTATION_TRUST_ACCOUNT: (uac_int & 0x00001000).zero? ? 0 : 1,
323
# This is a computer account for a system backup domain controller that is a member of this domain.
324
c_ADS_UF_SERVER_TRUST_ACCOUNT: (uac_int & 0x00002000).zero? ? 0 : 1,
325
# The password for this account will never expire.
326
c_ADS_UF_DONT_EXPIRE_PASSWD: (uac_int & 0x00010000).zero? ? 0 : 1,
327
# This is an MNS logon account.
328
c_ADS_UF_MNS_LOGON_ACCOUNT: (uac_int & 0x00020000).zero? ? 0 : 1,
329
# The user must log on using a smart card.
330
c_ADS_UF_SMARTCARD_REQUIRED: (uac_int & 0x00040000).zero? ? 0 : 1,
331
# The service account (user or computer account), under which a service runs, is trusted for Kerberos delegation.
332
# Any such service can impersonate a client requesting the service.
333
c_ADS_UF_TRUSTED_FOR_DELEGATION: (uac_int & 0x00080000).zero? ? 0 : 1,
334
# The security context of the user will not be delegated to a service even if the service
335
# account is set as trusted for Kerberos delegation.
336
c_ADS_UF_NOT_DELEGATED: (uac_int & 0x00100000).zero? ? 0 : 1,
337
# Restrict this principal to use only Data #Encryption Standard (DES) encryption types for keys.
338
c_ADS_UF_USE_DES_KEY_ONLY: (uac_int & 0x00200000).zero? ? 0 : 1,
339
# This account does not require Kerberos pre-authentication for logon.
340
c_ADS_UF_DONT_REQUIRE_PREAUTH: (uac_int & 0x00400000).zero? ? 0 : 1,
341
# The password has expired
342
c_ADS_UF_PASSWORD_EXPIRED: (uac_int & 0x00800000).zero? ? 0 : 1,
343
# The account is enabled for delegation. This is a security-sensitive setting; accounts with
344
# this option enabled should be strictly controlled. This setting enables a service running
345
# under the account to assume a client identity and authenticate as that user to other remote
346
# servers on the network.
347
c_ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: (uac_int & 0x01000000).zero? ? 0 : 1,
348
# Now add the sAMAccountType objects
349
c_SAM_DOMAIN_OBJECT: (sat_int == 0) ? 1 : 0,
350
c_SAM_GROUP_OBJECT: (sat_int == 0x10000000) ? 1 : 0,
351
c_SAM_NON_SECURITY_GROUP_OBJECT: (sat_int == 0x10000001) ? 1 : 0,
352
c_SAM_ALIAS_OBJECT: (sat_int == 0x20000000) ? 1 : 0,
353
c_SAM_NON_SECURITY_ALIAS_OBJECT: (sat_int == 0x20000001) ? 1 : 0,
354
c_SAM_NORMAL_USER_ACCOUNT: (sat_int == 0x30000000) ? 1 : 0,
355
c_SAM_MACHINE_ACCOUNT: (sat_int == 0x30000001) ? 1 : 0,
356
c_SAM_TRUST_ACCOUNT: (sat_int == 0x30000002) ? 1 : 0,
357
c_SAM_APP_BASIC_GROUP: (sat_int == 0x40000000) ? 1 : 0,
358
c_SAM_APP_QUERY_GROUP: (sat_int == 0x40000001) ? 1 : 0,
359
c_SAM_ACCOUNT_TYPE_MAX: (sat_int == 0x7fffffff) ? 1 : 0
360
}
361
run_sqlite_query(db, 'ad_computers', sql_param_computer)
362
print_line "Computer [#{sql_param_computer[:c_cn]}][#{sql_param_computer[:c_dNSHostName]}][#{sql_param_computer[:c_rid]}]" if datastore['SHOW_COMPUTERS']
363
end
364
rescue ::RuntimeError, ::Rex::Post::Meterpreter::RequestError => e
365
print_error("Error(Computers): #{e.message}")
366
return
367
end
368
369
loot_path = store_loot(
370
'host.ad_to_sqlite',
371
'application/x-sqlite3',
372
session,
373
File.binread(dbfile.path),
374
'ad_to_sqlite.db',
375
'AD Computer, Group and Recursive User Membership'
376
)
377
378
print_good("Sqlite extraction stored in file: #{loot_path}")
379
ensure
380
# Finished enumeration, cleanup resources
381
if db
382
db.close
383
end
384
385
if dbfile
386
dbfile.close
387
dbfile.unlink
388
end
389
end
390
391
# Run the parameterised SQL query
392
def run_sqlite_query(db, table_name, values)
393
sql_param_columns = values.keys
394
sql_param_bind_params = values.keys.map { |k| ":#{k}" }
395
db.execute("replace into #{table_name} (#{sql_param_columns.join(',')}) VALUES (#{sql_param_bind_params.join(',')})", values)
396
end
397
398
# Creat the SQLite Database
399
def create_sqlite_db
400
dbfile = Tempfile.new('ad_to_sqlite')
401
db = SQLite3::Database.new(dbfile.path)
402
db.type_translation = true
403
404
# Create the table for the AD Computers
405
db.execute('DROP TABLE IF EXISTS ad_computers')
406
sql_table_computers = 'CREATE TABLE ad_computers ('\
407
'c_rid INTEGER PRIMARY KEY NOT NULL,'\
408
'c_distinguishedName TEXT UNIQUE NOT NULL,'\
409
'c_cn TEXT,'\
410
'c_sAMAccountType INTEGER,'\
411
'c_sAMAccountName TEXT UNIQUE NOT NULL,'\
412
'c_dNSHostName TEXT,'\
413
'c_displayName TEXT,'\
414
'c_logonCount INTEGER,'\
415
'c_userAccountControl INTEGER,'\
416
'c_primaryGroupID INTEGER,'\
417
'c_badPwdCount INTEGER,'\
418
'c_description TEXT,'\
419
'c_comment TEXT,'\
420
'c_operatingSystem TEXT,'\
421
'c_operatingSystemServicePack TEXT,'\
422
'c_operatingSystemVersion TEXT,'\
423
'c_whenChanged TEXT,'\
424
'c_whenCreated TEXT,'\
425
'c_ADS_UF_SCRIPT INTEGER,'\
426
'c_ADS_UF_ACCOUNTDISABLE INTEGER,'\
427
'c_ADS_UF_HOMEDIR_REQUIRED INTEGER,'\
428
'c_ADS_UF_LOCKOUT INTEGER,'\
429
'c_ADS_UF_PASSWD_NOTREQD INTEGER,'\
430
'c_ADS_UF_PASSWD_CANT_CHANGE INTEGER,'\
431
'c_ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED INTEGER,'\
432
'c_ADS_UF_TEMP_DUPLICATE_ACCOUNT INTEGER,'\
433
'c_ADS_UF_NORMAL_ACCOUNT INTEGER,'\
434
'c_ADS_UF_INTERDOMAIN_TRUST_ACCOUNT INTEGER,'\
435
'c_ADS_UF_WORKSTATION_TRUST_ACCOUNT INTEGER,'\
436
'c_ADS_UF_SERVER_TRUST_ACCOUNT INTEGER,'\
437
'c_ADS_UF_DONT_EXPIRE_PASSWD INTEGER,'\
438
'c_ADS_UF_MNS_LOGON_ACCOUNT INTEGER,'\
439
'c_ADS_UF_SMARTCARD_REQUIRED INTEGER,'\
440
'c_ADS_UF_TRUSTED_FOR_DELEGATION INTEGER,'\
441
'c_ADS_UF_NOT_DELEGATED INTEGER,'\
442
'c_ADS_UF_USE_DES_KEY_ONLY INTEGER,'\
443
'c_ADS_UF_DONT_REQUIRE_PREAUTH INTEGER,'\
444
'c_ADS_UF_PASSWORD_EXPIRED INTEGER,'\
445
'c_ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION INTEGER,'\
446
'c_SAM_DOMAIN_OBJECT INTEGER,'\
447
'c_SAM_GROUP_OBJECT INTEGER,'\
448
'c_SAM_NON_SECURITY_GROUP_OBJECT INTEGER,'\
449
'c_SAM_ALIAS_OBJECT INTEGER,'\
450
'c_SAM_NON_SECURITY_ALIAS_OBJECT INTEGER,'\
451
'c_SAM_NORMAL_USER_ACCOUNT INTEGER,'\
452
'c_SAM_MACHINE_ACCOUNT INTEGER,'\
453
'c_SAM_TRUST_ACCOUNT INTEGER,'\
454
'c_SAM_APP_BASIC_GROUP INTEGER,'\
455
'c_SAM_APP_QUERY_GROUP INTEGER,'\
456
'c_SAM_ACCOUNT_TYPE_MAX INTEGER)'
457
db.execute(sql_table_computers)
458
459
# Create the table for the AD Groups
460
db.execute('DROP TABLE IF EXISTS ad_groups')
461
sql_table_group = 'CREATE TABLE ad_groups ('\
462
'g_rid INTEGER PRIMARY KEY NOT NULL,'\
463
'g_distinguishedName TEXT UNIQUE NOT NULL,'\
464
'g_sAMAccountType INTEGER,'\
465
'g_sAMAccountName TEXT UNIQUE NOT NULL,'\
466
'g_groupType INTEGER,'\
467
'g_adminCount INTEGER,'\
468
'g_description TEXT,'\
469
'g_comment TEXT,'\
470
'g_cn TEXT,'\
471
'g_managedBy TEXT,'\
472
'g_whenChanged TEXT,'\
473
'g_whenCreated TEXT,'\
474
'g_GT_GROUP_CREATED_BY_SYSTEM INTEGER,'\
475
'g_GT_GROUP_SCOPE_GLOBAL INTEGER,'\
476
'g_GT_GROUP_SCOPE_LOCAL INTEGER,'\
477
'g_GT_GROUP_SCOPE_UNIVERSAL INTEGER,'\
478
'g_GT_GROUP_SAM_APP_BASIC INTEGER,'\
479
'g_GT_GROUP_SAM_APP_QUERY INTEGER,'\
480
'g_GT_GROUP_SECURITY INTEGER,'\
481
'g_GT_GROUP_DISTRIBUTION INTEGER,'\
482
'g_SAM_DOMAIN_OBJECT INTEGER,'\
483
'g_SAM_GROUP_OBJECT INTEGER,'\
484
'g_SAM_NON_SECURITY_GROUP_OBJECT INTEGER,'\
485
'g_SAM_ALIAS_OBJECT INTEGER,'\
486
'g_SAM_NON_SECURITY_ALIAS_OBJECT INTEGER,'\
487
'g_SAM_NORMAL_USER_ACCOUNT INTEGER,'\
488
'g_SAM_MACHINE_ACCOUNT INTEGER,'\
489
'g_SAM_TRUST_ACCOUNT INTEGER,'\
490
'g_SAM_APP_BASIC_GROUP INTEGER,'\
491
'g_SAM_APP_QUERY_GROUP INTEGER,'\
492
'g_SAM_ACCOUNT_TYPE_MAX INTEGER)'
493
db.execute(sql_table_group)
494
495
# Create the table for the AD Users
496
db.execute('DROP TABLE IF EXISTS ad_users')
497
sql_table_users = 'CREATE TABLE ad_users ('\
498
'u_rid INTEGER PRIMARY KEY NOT NULL,'\
499
'u_distinguishedName TEXT UNIQUE NOT NULL,'\
500
'u_description TEXT,'\
501
'u_displayName TEXT,'\
502
'u_sAMAccountType INTEGER,'\
503
'u_sAMAccountName TEXT,'\
504
'u_logonCount INTEGER,'\
505
'u_userAccountControl INTEGER,'\
506
'u_primaryGroupID INTEGER,'\
507
'u_cn TEXT,'\
508
'u_adminCount INTEGER,'\
509
'u_badPwdCount INTEGER,'\
510
'u_userPrincipalName TEXT UNIQUE,'\
511
'u_comment TEXT,'\
512
'u_title TEXT,'\
513
'u_manager TEXT,'\
514
'u_whenCreated TEXT,'\
515
'u_whenChanged TEXT,'\
516
'u_ADS_UF_SCRIPT INTEGER,'\
517
'u_ADS_UF_ACCOUNTDISABLE INTEGER,'\
518
'u_ADS_UF_HOMEDIR_REQUIRED INTEGER,'\
519
'u_ADS_UF_LOCKOUT INTEGER,'\
520
'u_ADS_UF_PASSWD_NOTREQD INTEGER,'\
521
'u_ADS_UF_PASSWD_CANT_CHANGE INTEGER,'\
522
'u_ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED INTEGER,'\
523
'u_ADS_UF_TEMP_DUPLICATE_ACCOUNT INTEGER,'\
524
'u_ADS_UF_NORMAL_ACCOUNT INTEGER,'\
525
'u_ADS_UF_INTERDOMAIN_TRUST_ACCOUNT INTEGER,'\
526
'u_ADS_UF_WORKSTATION_TRUST_ACCOUNT INTEGER,'\
527
'u_ADS_UF_SERVER_TRUST_ACCOUNT INTEGER,'\
528
'u_ADS_UF_DONT_EXPIRE_PASSWD INTEGER,'\
529
'u_ADS_UF_MNS_LOGON_ACCOUNT INTEGER,'\
530
'u_ADS_UF_SMARTCARD_REQUIRED INTEGER,'\
531
'u_ADS_UF_TRUSTED_FOR_DELEGATION INTEGER,'\
532
'u_ADS_UF_NOT_DELEGATED INTEGER,'\
533
'u_ADS_UF_USE_DES_KEY_ONLY INTEGER,'\
534
'u_ADS_UF_DONT_REQUIRE_PREAUTH INTEGER,'\
535
'u_ADS_UF_PASSWORD_EXPIRED INTEGER,'\
536
'u_ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION INTEGER,'\
537
'u_SAM_DOMAIN_OBJECT INTEGER,'\
538
'u_SAM_GROUP_OBJECT INTEGER,'\
539
'u_SAM_NON_SECURITY_GROUP_OBJECT INTEGER,'\
540
'u_SAM_ALIAS_OBJECT INTEGER,'\
541
'u_SAM_NON_SECURITY_ALIAS_OBJECT INTEGER,'\
542
'u_SAM_NORMAL_USER_ACCOUNT INTEGER,'\
543
'u_SAM_MACHINE_ACCOUNT INTEGER,'\
544
'u_SAM_TRUST_ACCOUNT INTEGER,'\
545
'u_SAM_APP_BASIC_GROUP INTEGER,'\
546
'u_SAM_APP_QUERY_GROUP INTEGER,'\
547
'u_SAM_ACCOUNT_TYPE_MAX INTEGER)'
548
db.execute(sql_table_users)
549
550
# Create the table for the mapping between the two (membership)
551
db.execute('DROP TABLE IF EXISTS ad_mapping')
552
sql_table_mapping = 'CREATE TABLE ad_mapping ('\
553
'user_rid INTEGER NOT NULL,' \
554
'group_rid INTEGER NOT NULL,'\
555
'PRIMARY KEY (user_rid, group_rid),'\
556
'FOREIGN KEY(user_rid) REFERENCES ad_users(u_rid)'\
557
'FOREIGN KEY(group_rid) REFERENCES ad_groups(g_rid))'
558
db.execute(sql_table_mapping)
559
560
# Create the view for the AD User/Group membership
561
db.execute('DROP VIEW IF EXISTS view_mapping')
562
sql_view_mapping = 'CREATE VIEW view_mapping AS SELECT ad_groups.*,ad_users.* FROM ad_mapping '\
563
'INNER JOIN ad_groups ON ad_groups.g_rid = ad_mapping.group_rid '\
564
'INNER JOIN ad_users ON ad_users.u_rid = ad_mapping.user_rid'
565
db.execute(sql_view_mapping)
566
567
return db, dbfile
568
rescue SQLite3::Exception => e
569
print_error("Error(Database): #{e.message}")
570
return
571
end
572
573
def get_rid(data)
574
sid = data.unpack('bbbbbbbbV*')[8..]
575
sid[-1]
576
end
577
end
578
579