coreruleset / coreruleset/coreruleset
SQLi detection bypass via single-pass percent-encoded SQL syntax
Nobody has claimed this yet.
- 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 OnSecRequestBodyAccess On- JSON request-body processor enabled for
Content-Type: application/json REQUEST-942-APPLICATION-ATTACK-SQLI.confloadedREQUEST-949-BLOCKING-EVALUATION.confloaded
Confirmation
[x] I have removed any personal data (email addresses, IP addresses,
passwords, domain names) from any logs posted.
Contributor guide
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 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