coreruleset / coreruleset/coreruleset

SQLi detection bypass via single-pass percent-encoded SQL syntax

Open
#4,722 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

:heavy_minus_sign: False Negative - Evasion
Dominant language
Python
Stars
3.3k
Forks
470
Avg merge
1d 20h
Merged PRs (30d)
11

Description

Description

We identified a repeatable SQL injection detection difference in ModSecurity / OWASP CRS for the following normalization family:

Single-pass percent encoding of SQL syntax

Broad percent encoding of SQL separators and syntax characters is allowed without requiring a recursive decoding layer.

Canonical SQL injection forms are blocked, while transformed forms assigned to this family are allowed. Across the complete corpus, 63 successful records exhibit this family. Of those, 14 were independently checked for semantic equivalence; the remaining 49 demonstrate the WAF decision only.

Expected behavior: canonical and backend-equivalent transformed forms receive the same blocking decision after transport-aware normalization.

Actual behavior: the canonical form receives HTTP 403, while the transformed form receives HTTP 200 from the same test endpoint.

This issue is intentionally limited to Single-pass percent encoding of SQL syntax. Patterns overlap, and any overlap with the other issue folders is not intended to combine separate root causes into this report.

Representative cases:

Case 1 — result-set

  • Canonical: ' OR TRUE #
  • Allowed form: %27 Or 2%2B2 %23-- a1B2c

Case 2 — order-by-probe

  • Canonical: ') ORDER BY 1 --
  • Allowed form: %EF%BB%BF')/* */ORDER BY/* */  1+0--

Case 3 — state-diff

  • Canonical: '; DROP TABLE users --
  • Allowed form: %27%3B%DROP%20TABLE%20users%20--%207kQ2m

The full matching corpus is attached as cases.csv.

How to reproduce the misbehavior (-> curl call)

Set TARGET_URL to an authorized CRS test deployment that accepts the same GET-with-JSON request shape. The observed blocked response was HTTP 403 and the observed allowed response was HTTP 200.

TARGET_URL='https://your-crs-test-instance.example/'

# Canonical form: expected and observed HTTP 403.
curl --silent --show-error --include --get "$TARGET_URL" \
  --header 'Content-Type: application/json' \
  --data-urlencode data='{"payload": "'"'"' OR TRUE #"}'

# Mutated form: expected HTTP 403, but observed HTTP 200.
curl --silent --show-error --include --get "$TARGET_URL" \
  --header 'Content-Type: application/json' \
  --data-urlencode data='{"payload": "%27 Or 2%2B2 %23-- a1B2c"}'

Before submission, test the same payload pair against the CRS Sandbox and adapt the request shape to its documented interface.

Logs

The adjacent cases.csv contains all 63 matching successful records with canonical payload, allowed payload, observed components, and composite-family labels.

The attached CSV contains only payload pairs and classification metadata. A native ModSecurity audit log can be captured from the submission environment and attached if required.

Your Environment
  • CRS version (e.g., v3.3.4): v3.3.2
  • Paranoia level setting (e.g. PL1) : PL1 (CRS default; no explicit local override)
  • ModSecurity version (e.g., 2.9.6): v3.0.15
  • Web Server and version or cloud provider / CDN (e.g., Apache httpd 2.4.54): Standalone libmodsecurity v3 integration; no Apache or Nginx connector
  • Operating System and version: Ubuntu 22.04 LTS, x86_64

Relevant configuration:

  • SecRuleEngine On
  • SecRequestBodyAccess On
  • JSON request-body processor enabled for Content-Type: application/json
  • REQUEST-942-APPLICATION-ATTACK-SQLI.conf loaded
  • REQUEST-949-BLOCKING-EVALUATION.conf loaded
Confirmation

[x] I have removed any personal data (email addresses, IP addresses,
passwords, domain names) from any logs posted.

cases.csv

Contributor guide

Open the contributing guide

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 by adapting the curl requests to the CRS Sandbox and compare the canonical and encoded payloads. Inspect REQUEST-942-APPLICATION-ATTACK-SQLI.conf and REQUEST-949-BLOCKING-EVALUATION.conf, using cases.csv to reproduce the reported family. Done means backend-equivalent single-pass encoded forms receive the same blocking decision.

Written by the indexing model from the issue text.

Assessment

Tech stack
bash, sql
Domain
security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.