CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

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