How a Microsoft-signed SQL Server stored procedure lets sysadmins bypass disabled xp_cmdshell

Comments 0

Share to social media

SQL injection inside Microsoft-signed system stored procedures is not supposed to happen. Yet, as I’ve been documenting in this series, it happens more often than you may assume.

This article walks through another one I found and reported to the Microsoft Security Response Center (MSRC). It’s a textbook SQL injection sitting inside sys.sp_MSdeletefoldercontents, a system stored procedure used by SQL Server replication.

What makes it interesting is not the injection technique itself (there is no clever Unicode trick this time), but what it enables: a working execution path for xp_cmdshell on a server where xp_cmdshell is explicitly disabled by configuration.

This article is part of Fabiano Amorim’s series on SQL Server vulnerabilities you may not be aware of.

Introducing the vulnerability (and some background)

Disabling xp_cmdshell is one of the most familiar SQL Server security 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.

The underlying assumption is simple: if xp_cmdshell is disabled, operating-system commands cannot be executed through it until somebody explicitly changes the configuration.

The vulnerability in this article challenges that assumption. The internal procedure accepts a folder path, concatenates that value into a dynamically-generated T-SQL batch, and executes the batch with sp_executesql.

Because the folder value is not parameterized or safely escaped, however, a caller can terminate the intended string literal and append arbitrary T-SQL.

While a direct call to xp_cmdshell fails with the expected ‘component is turned off’ error, it’s a different situation entirely when injected into sys.sp_MSdeletefoldercontents.

There, the exact same xp_cmdshell call succeeds without an explicit sp_configure/reconfigure.

Why this is not just a privilege escalation vulnerability

This is not a privilege escalation vulnerability from a normal database user to sysadmin. The procedure itself checks for sysadmin membership, so the prerequisite is already highly privileged.

Instead, the security relevance is different: it creates an alternate execution path around a configuration control that administrators, auditors, and detection systems may treat as authoritative.

Technical scope

The supplied proof of concept requires a login that is already a member of the sysadmin fixed server role. This article does not claim a lower-privileged user can exploit this path. The issue demonstrated here is a SQL injection and security-control bypass: a sysadmin session can reach xp_cmdshell through sys.sp_MSdeletefoldercontents even when a direct xp_cmdshell call is blocked because the feature is disabled.

Disclosure note

I reported this vulnerability to the Microsoft Security Response Center on March 25, 2026. Microsoft investigated it, classified it as a low-severity “defense-in-depth” 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.

What is sys.sp_MSdeletefoldercontents?

sys.sp_MSdeletefoldercontents is an internal system stored procedure used to remove a collection of replication-related files from a supplied folder.

The procedure contains an explicit security check: if the current login is not a member of the sysadmin fixed server role, it raises error 21089 and returns.

And, since the exploitation of the supplied path begins from sysadmin rather than a less-privileged database role, this is important.

Here’s the relevant security check:

If that were the end of the story, the procedure would simply be a privileged housekeeping routine. Instead, a problem appears when @folder is copied directly into executable SQL text.

The vulnerable dynamic SQL

Let’s take a look at the procedure code – the vulnerable construction pattern at @command concatenation:

There is a sysadmin guard at the top, but what comes after that is the interesting part: the procedure builds a dynamic T-SQL command string by concatenating @folder (the user-supplied nvarchar(255) parameter.)

In other words, this is the dangerous pattern:

It’s the textbook definition of SQL injection, whereby the procedure trusts @folder 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 – not data.

The injected code then runs through sp_executesql – inheriting the procedure’s own security context rather than the caller’s.

The sysadmin catch (and why it still matters)

Before we jump to the proof of concept, let’s confront the objection head-on, because Microsoft did.

The procedure explicitly checks is_srvrolemember('sysadmin') 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.

From MSRC (Case 111384):

After careful investigation, this case has been assessed as a low severity, defense-in-depth item, and does not meet MSRC’s bar for immediate servicing due to sys.sp_MSdeletefoldercontents requiring sysadmin privileges.

The obvious counterargument is fair: a member of sysadmin already controls SQL Server. A sysadmin can usually enable xp_cmdshell, execute a command, and disable it again.

But that just prompts the question: why should we care if the attacker is already sysadmin?

What an attacker can (and will) do once they’ve obtained sysadmin

Well, privilege is only one part of the security equation. Visibility is another. Obtaining sysadmin is not often the end of the intrusion during a real attack – rather, it’s when the attacker begins their ‘post-exploitation activities.’

These activities include collecting information, accessing credentials and sensitive data, moving laterally, establishing persistence, and/or executing operating-system commands.

This is where techniques such as this one become valuable. From the attacker’s perspective, they are gaining from the ‘evasion’ element, not the privilege escalation itself.

In my own SQL Server security research, once I obtain sysadmin, one of the next steps is frequently to test operating-system command execution. I’ve used this technique extensively to reach xp_cmdshell without having to perform the configuration change that defenders commonly expect to see.

In fact, I’ve never seen anyone monitoring for sp_MSdeletefoldercontents in my time. After all, it’s a Microsoft-owned procedure, so it must be safe! Why should they monitor for it?

Instead, focus goes toward controlling xp_cmdshell activity using common security best practices like monitoring, audit, alert setup, etc.

Future-proof database monitoring with Redgate Monitor

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.
Learn more & try for free

The vulnerability in action (and how to recreate it)

Here’s the full vulnerability in action, and a step-by-step guide on how to recreate it.

Step 1: confirm direct xp_cmdshell execution is blocked

Start with xp_cmdshell disabled, then invoke it directly:

This is exactly the message any hardened server should return, and exactly what an auditor expects to see.

Step 2: invoke xp_cmdshell through the injection

Next, call sys.sp_MSdeletefoldercontents and place the xp_cmdshell statement inside @folder:

There it is. xp_cmdshell executed, and whoami returned the SQL Server service account.

Step 3: prove the primitive is general T-SQL injection

The report also uses a harmless SELECT payload to show that the primitive is not specific to xp_cmdshell:

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. xp_cmdshell is simply the most security-relevant demonstration of the resulting control bypass.

Why, and how, does this bypass the xp_cmdshell disable?

When sys.sp_MSdeletefoldercontents calls sp_executesql, the batch runs inside the execution context of the system procedure, which is a Microsoft-signed object living in the sys schema. Along that path, the feature-switch check for xp_cmdshell is not enforced the same way it is for a direct call from a user session.

Overall, sysadmin being able to run arbitrary T-SQL is not the problem. However, sysadmin being able to run xp_cmdshell through a Microsoft-signed system procedure, without ever asking sp_configure for permission or leaving a “feature enabled” event behind, is most definitely a problem.

What can you do to prevent the vulnerability?

Since the supplied exploit requires sysadmin, the most important defensive control is still strict control of sysadmin membership.

This finding shouldn’t be interpreted as a reason to re-enable xp_cmdshell – rather, it’s reason to avoid treating the disabled flag as the only relevant control.

Here’s everything you can do to prevent the vulnerability:

  1. Keep sysadmin membership extremely small. Review permanent human accounts, service accounts, and automation identities that hold the role.

  2. Continue to keep xp_cmdshell disabled unless there’s a documented operational need. Microsoft still recommends this.

  3. Monitor for calls to sys.sp_MSdeletefoldercontents outside expected replication workflows – especially those containing quote characters, semicolons, comment markers, or other values that don’t resemble normal filesystem paths.

  4. Do not exclusively depend on alerts for sp_configure 'xp_cmdshell'. 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.

  5. Alert on actual child process creation by the SQL Server service and on suspicious use of extended procedures. The most valuable detection strategy is to correlate SQL activity with host process creation.

  6. If you don’t use replication, deny explicit execute on the procedure. sp_MSdeletefoldercontents is a replication-facing procedure. On instances where replication is not configured and never will be, an explicit DENY EXECUTE ON sys.sp_MSdeletefoldercontents TO public; gives you one more small barrier. It won’t stop a determined sysadmin (they can GRANT it back), but it’ll show up in an audit trail if someone tries!

In summary: the sys.sp_MSdeletefoldercontents vulnerability in SQL Server

The vulnerability in SQL Server sys.sp_MSdeletefoldercontents is easy to understand once the generated SQL is visible. Essentially, @folder is inserted directly into a dynamic batch, so a crafted folder string can escape the intended pathname literal and append arbitrary T-SQL.

What makes the finding interesting is the behavior demonstrated with xp_cmdshell. The direct call is rejected because the feature is disabled, yet the injected call through the internal procedure succeeds.

The fix for SQL Server developers: never concatenate caller-controlled text into executable SQL when it can be passed as data.

For SQL Server administrators, it’s similar but not the same. Keep xp_cmdshell disabled if possible, and do not treat that single setting as proof that operating-system command execution is impossible.

After all, if an attacker wants to run a shell command, it has several options, including: extended procedures, OLE Automation, SQL Agent jobs, common language runtime (CLR), and external scripts.

Finally, enforcing auditing, monitoring, and alerting, is always important.

References

Move fast. Govern at scale.

Redgate Flyway Enterprise embeds guardrails in the database layer, so every change is policy-checked, deterministic, and traceable.
Try for free

FAQs: The sys.sp_MSdeletefoldercontents vulnerability in SQL Server

1. What is the vulnerability in sys.sp_MSdeletefoldercontents?

It’s a SQL injection flaw where the @folder 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.

2. Does this let a non-admin user gain sysadmin access?

No. The procedure requires the caller to already be a sysadmin, so it’s not a privilege escalation bug. Its significance is that it lets an already-privileged user bypass a security control (the xp_cmdshell disable flag) rather than gain new privileges.

3. Why does xp_cmdshell run here if it's disabled server-wide?

Because the injected command executes inside the security context of the Microsoft-signed system procedure via sp_executesql, the feature-switch check that blocks direct xp_cmdshell calls from a user session isn’t enforced the same way along that internal execution path.

4. How did Microsoft respond to this report?

MSRC investigated and classified it as a low-severity “defense-in-depth” issue (Case 111384), stating it didn’t meet the bar for immediate servicing since sysadmin privileges are a prerequisite.

5. If sysadmin can already do anything, why does this matter?

Because privilege and visibility are separate concerns. This path lets a sysadmin reach OS command execution without ever triggering an sp_configure change or a “feature enabled” event — the exact signal most detection setups rely on to catch xp_cmdshell abuse.

6. How can organizations detect or mitigate this?

Keep sysadmin membership minimal, monitor calls to sys.sp_MSdeletefoldercontents 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’t in use.

This document contains proprietary information and is protected by copyright law.

Copyright © 2026 Red Gate Software Limited. All rights reserved

Article tags

About the author

Fabiano Amorim

See Profile

Fabiano Amorim is a Data Platform MVP since 2011 who loves to conquer complex, challenging problems - especially ones that others aren’t able to solve. He first became interested in technology when his older brother would bring him to his work meetings at the age of 14. With over a decade of experience, Fabiano is well known in the database community for his performance tuning abilities. Elsewhere, he loves to read and spend time with his family.