added mssql_enum_windows_domain_accounts.rb
parent
50a2f4c2a7
commit
8c34f35ca9
|
@ -0,0 +1,267 @@
|
||||||
|
##
|
||||||
|
# This module requires Metasploit: http://metasploit.com/download
|
||||||
|
# Current source: https://github.com/rapid7/metasploit-framework
|
||||||
|
##
|
||||||
|
|
||||||
|
require 'msf/core'
|
||||||
|
require 'msf/core/exploit/mssql_commands'
|
||||||
|
|
||||||
|
class Metasploit3 < Msf::Auxiliary
|
||||||
|
|
||||||
|
include Msf::Exploit::Remote::MSSQL
|
||||||
|
include Msf::Auxiliary::Report
|
||||||
|
|
||||||
|
def initialize(info = {})
|
||||||
|
super(update_info(info,
|
||||||
|
'Name' => 'Microsoft SQL Server SUSER_SNAME Windows Domain Account Enumeration',
|
||||||
|
'Description' => %q{
|
||||||
|
This module can be used to brute force RIDs associated with the domain of
|
||||||
|
the SQL Server using the SUSER_SNAME function. This is similar to the
|
||||||
|
smb_lookupsid module, but executed through SQL Server queries as any user
|
||||||
|
with the PUBLIC role (everyone). Information that can be enumerated includes
|
||||||
|
Windows domain users, groups, and computer accounts. Enumerated accounts can
|
||||||
|
then be used in online dictionary attacks.
|
||||||
|
},
|
||||||
|
'Author' => [ 'nullbind <scott.sutherland[at]netspi.com>','antti <antti.rantasaari[at]netspi.com>'],
|
||||||
|
'License' => MSF_LICENSE,
|
||||||
|
'References' => [[ 'URL','http://msdn.microsoft.com/en-us/library/ms174427.aspx']]
|
||||||
|
))
|
||||||
|
|
||||||
|
register_options(
|
||||||
|
[
|
||||||
|
OptInt.new('FuzzNum', [true, 'Number of principal_ids to fuzz.', 10000]),
|
||||||
|
], self.class)
|
||||||
|
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 the server name
|
||||||
|
sql_server_name = get_sql_server_name
|
||||||
|
print_status("SQL Server Name: #{sql_server_name}")
|
||||||
|
|
||||||
|
# Get the domain name
|
||||||
|
sql_server_domain = get_windows_domain
|
||||||
|
if sql_server_domain.nil?
|
||||||
|
print_error("Could not recover the SQL Server's domain.")
|
||||||
|
disconnect
|
||||||
|
return
|
||||||
|
else
|
||||||
|
print_status("Domain Name: #{sql_server_domain}")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Check if the domain and hostname are the same
|
||||||
|
if sql_server_name == sql_server_domain
|
||||||
|
print_error("The SQL Server does not appear to be part of a Windows domain.")
|
||||||
|
disconnect
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get the base sid for the domain
|
||||||
|
windows_domain_sid = get_windows_domain_sid(sql_server_domain)
|
||||||
|
if windows_domain_sid.nil?
|
||||||
|
print_error("Could not recover the SQL Server's domain sid.")
|
||||||
|
disconnect
|
||||||
|
return
|
||||||
|
else
|
||||||
|
print_good("Found the domain sid: #{windows_domain_sid}")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get a list of windows users, groups, and computer accounts using SUSER_NAME()
|
||||||
|
print_status("Brute forcing #{datastore['FuzzNum']} RIDs through the SQL Server, be patient...")
|
||||||
|
win_domain_user_list = get_win_domain_users(windows_domain_sid)
|
||||||
|
if win_domain_user_list.nil? || win_domain_user_list.empty?
|
||||||
|
print_error('Sorry, no Windows domain accounts were found, or DC could not be contacted.')
|
||||||
|
disconnect
|
||||||
|
return
|
||||||
|
else
|
||||||
|
|
||||||
|
# Print number of objects found and write to a file
|
||||||
|
print_good("#{win_domain_user_list.length} user accounts, groups, and computer accounts were found.")
|
||||||
|
|
||||||
|
win_domain_user_list.sort.each do |windows_login|
|
||||||
|
if datastore['VERBOSE']
|
||||||
|
print_status(" - #{windows_login}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Create table for report
|
||||||
|
windows_domain_login_table = Rex::Ui::Text::Table.new(
|
||||||
|
'Header' => 'Windows Domain Accounts',
|
||||||
|
'Ident' => 1,
|
||||||
|
'Columns' => ['name']
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add brute forced names to table
|
||||||
|
win_domain_user_list.each do |object_name|
|
||||||
|
windows_domain_login_table << [object_name]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Create output file
|
||||||
|
this_service = nil
|
||||||
|
if framework.db and framework.db.active
|
||||||
|
this_service = report_service(
|
||||||
|
:host => rhost,
|
||||||
|
:port => rport,
|
||||||
|
:name => 'mssql',
|
||||||
|
:proto => 'tcp'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
filename= "#{datastore['RHOST']}-#{datastore['RPORT']}_windows_domain_accounts.csv"
|
||||||
|
path = store_loot("windows_domain_accounts", "text/plain", datastore['RHOST'], windows_domain_login_table.to_csv, filename, "SQL Server query results",this_service)
|
||||||
|
print_status("Query results have been saved to: #{path}")
|
||||||
|
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
|
||||||
|
|
||||||
|
# Get list of windows accounts,groups,and computer accounts
|
||||||
|
def get_win_domain_users(windows_domain_sid)
|
||||||
|
|
||||||
|
# Create array to store the windws accounts etc
|
||||||
|
windows_logins = []
|
||||||
|
|
||||||
|
# Fuzz the principal_id parameter passed to the SUSER_NAME function
|
||||||
|
(500..datastore['FuzzNum']).each do |principal_id|
|
||||||
|
|
||||||
|
# Convert number to hex and fix order
|
||||||
|
principal_id_hex = "%02X" % principal_id
|
||||||
|
principal_id_hex_pad = (principal_id_hex.size.even? ? principal_id_hex : ("0"+ principal_id_hex))
|
||||||
|
principal_id_clean = principal_id_hex_pad.scan(/(..)/).reverse.flatten.join
|
||||||
|
|
||||||
|
# Add padding
|
||||||
|
principal_id_hex_padded2 = principal_id_clean.ljust(8, '0')
|
||||||
|
|
||||||
|
# Create full sid
|
||||||
|
win_sid = "0x#{windows_domain_sid}#{principal_id_hex_padded2}"
|
||||||
|
|
||||||
|
# Return if sid does not resolve correctly for a domain
|
||||||
|
if win_sid.length < 48
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
# Setup query
|
||||||
|
sql = "SELECT SUSER_SNAME(#{win_sid}) as name"
|
||||||
|
|
||||||
|
# Execute query
|
||||||
|
result = mssql_query(sql)
|
||||||
|
|
||||||
|
# Parse results
|
||||||
|
parse_results = result[:rows]
|
||||||
|
windows_login = parse_results[0][0]
|
||||||
|
|
||||||
|
# Print account,group,or computer account etc
|
||||||
|
if windows_login.length != 0
|
||||||
|
print_status(" - #{windows_login}")
|
||||||
|
|
||||||
|
# Verbose output
|
||||||
|
if datastore['VERBOSE']
|
||||||
|
print_status("Test sid: #{win_sid}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Add to windows domain object list
|
||||||
|
windows_logins.push(windows_login) unless windows_logins.include?(windows_login)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return list of logins
|
||||||
|
windows_logins
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get windows domain
|
||||||
|
def get_windows_domain
|
||||||
|
|
||||||
|
# Setup query to check for sysadmin
|
||||||
|
sql = "SELECT DEFAULT_DOMAIN() as mydomain"
|
||||||
|
|
||||||
|
# Run query
|
||||||
|
result = mssql_query(sql)
|
||||||
|
|
||||||
|
# Parse query results
|
||||||
|
parse_results = result[:rows]
|
||||||
|
sql_server_domain = parse_results[0][0]
|
||||||
|
|
||||||
|
# Return domain
|
||||||
|
sql_server_domain
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get the sql server's hostname
|
||||||
|
def get_sql_server_name
|
||||||
|
|
||||||
|
# Setup query to check for sysadmin
|
||||||
|
sql = "SELECT @@servername"
|
||||||
|
|
||||||
|
# Run query
|
||||||
|
result = mssql_query(sql)
|
||||||
|
|
||||||
|
# Parse query results
|
||||||
|
parse_results = result[:rows]
|
||||||
|
sql_instance_name = parse_results[0][0]
|
||||||
|
sql_server_name = sql_instance_name.split('\\')[0]
|
||||||
|
|
||||||
|
# Return servername
|
||||||
|
sql_server_name
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get windows domain
|
||||||
|
def get_windows_domain_sid(sql_server_domain)
|
||||||
|
|
||||||
|
# Set group
|
||||||
|
domain_group = "#{sql_server_domain}\\Domain Admins"
|
||||||
|
|
||||||
|
# Setup query to check for sysadmin
|
||||||
|
sql = "select SUSER_SID('#{domain_group}') as dasid"
|
||||||
|
|
||||||
|
# Run query
|
||||||
|
result = mssql_query(sql)
|
||||||
|
|
||||||
|
# Parse query results
|
||||||
|
parse_results = result[:rows]
|
||||||
|
object_sid = parse_results[0][0]
|
||||||
|
domain_sid = object_sid[0..47]
|
||||||
|
|
||||||
|
# Return if sid does not resolve for a domain
|
||||||
|
if domain_sid.length == 0
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return domain sid
|
||||||
|
domain_sid
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue