This year's SQL Server CVEs have a common shape. CVE-2026-21262 (March Patch Tuesday, CVSS 8.8) lets an authenticated, low-privileged login climb to sysadmin over the network. That sounds bad on its own, but sysadmin is not the finish line for an attacker. It's just the on-ramp. The exit ramp is xp_cmdshell, and that is the piece that turns 'they got into the database' into 'what they ran under your service account'.
If you run managed services, you are not patching one instance, you are patching a fleet, and somewhere in that fleet is an instance where this gate is open and nobody noticed. Let's look at the gate and how to verify it is shut across the servers you manage.
The gate is closed by default
Since SQL Server 2005, xp_cmdshell ships disabled. Call it on a clean instance and you get the classic refusal:
EXEC xp_cmdshell 'whoami';
Msg 15281, Level 16, State 1, Procedure sys.xp_cmdshell, Line 1 [Batch Start Line 0] SQL Server blocked access to procedure 'sys.xp_cmdshell' of component 'xp_cmdshell' because this component is turned off as part of the security configuration for this server. A system administrator can enable the use of 'xp_cmdshell' by using sp_configure. For more information about enabling 'xp_cmdshell', search for 'xp_cmdshell' in SQL Server Books Online.
Good. The door is shut as it should be. The problem is how easy the door is to open when you're sysadmin, and how many times it's probably already been opened by some long-departed vendor install script.
Check the current state
Before anything else, find out where you actually stand. sys.configurations tells you both the saved value and the running value:
SELECT name,
value,
value_in_use,
description
FROM sys.configurations
WHERE name = 'xp_cmdshell';
Of course, this is my server, so it's 1. That means xp_cmdshell is enabled. 0 would mean disabled. On your servers, if this comes back 1 and you did not put it there, that is an immediate concern.
Why this is a risk
Here is a demo so you can see exactly why this matters. Since my server already shows 1, I'll walk through how it gets opened in the first place. Enabling it takes four lines:
EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;
And now the door is open.
EXEC xp_cmdshell 'whoami';
Look at that closely. The command did not run as the login that called it. It ran as the SQL Server service account. Whatever that account can touch on the host and the network, the attacker can now touch, from a T-SQL prompt, no shell access required. Swap whoami for anything the service account can reach and you understand the rest of the story. The exposure. The risk.
Who runs as what
The execution context depends on who is calling, which is worth keeping straight:
| Caller | Command runs as |
|---|---|
| sysadmin member | SQL Server service account |
| non-sysadmin (proxy configured) | xp_cmdshell proxy credential |
| non-sysadmin (no proxy) | Cannot execute |
This is why CVE-2026-21262 is more than 'just' a privilege escalation. The climb to sysadmin hands an attacker the first row of that table, and the first row is the host.
Catch it being flipped on
A point-in-time check is fine, but you want to know when the value changed, not just that it is wrong today. Every sp_configure change gets written to the error log. Pull it straight out:
EXEC xp_readerrorlog 0, 1, N'xp_cmdshell';
We're looking for any lines recorded in the log including 'xp_cmdshell'. Mine was already at 1, so I flipped it to 0 to gen the image for this post. The point is that you can see the timestamp when the change was made. For something more durable than the rolling error log, a Server Audit capturing server configuration changes will keep the record where log cycling cannot quietly erase it.
Sweep the whole inventory at once
One instance at a time does not scale when you manage dozens. dbatools turns the same check into a single pass across every registered server:
Get-DbaSpConfigure -SqlInstance $instances |
Where-Object Name -eq 'XpCmdShellEnabled' |
Select-Object SqlInstance, ConfiguredValue, RunningValue
Any server where the running value is 1 goes to the top of your review list.
Shut the door and keep it shut
If nothing legitimately needs xp_cmdshell, the cleanup is the same shape as the setup, in reverse:
EXEC sp_configure 'xp_cmdshell', 0; RECONFIGURE; EXEC sp_configure 'show advanced options', 0; RECONFIGURE;
Beyond that toggle, the boring controls are the ones that hold. Keep the SQL Server service account on a least-privilege footing so that even if the door is opened, the service account is not a domain admin waiting to be borrowed. Do not expose the instance to the internet, because the scanners already know your port is there. And patch the escalation paths, because the whole reason xp_cmdshell is a back half of an attack is that something else got the attacker to sysadmin first.
The door is closed by default purposely. Your job is to keep it closed, or IF it needs to be opened, be sure to close it properly afterward.
Applies to SQL Server 2005 through 2025. xp_cmdshell has been disabled by default the entire time, which means anywhere you find it on was a deliberate act, by someone.
More to Read
xp_cmdshell Server Configuration Option (Microsoft Learn)
xp_cmdshell (Transact-SQL) (Microsoft Learn)
Create a Server Audit and Server Audit Specification (Microsoft Learn)



No comments:
Post a Comment