Transform OWASP CRS rules into a modern, abstract representation π
The code in this repository aims to generate a new representation for the OWASP CRS rules, making them more accessible and maintainable.
The goal is to abstract OWASP CRS from the specifics of the Seclang language in which it is currently written. This abstraction provides:
- π Better maintainability - Easier to understand and modify rules
- π― Language independence - Not tied to Seclang syntax
- π Enhanced tooling - Better support for analysis and transformation
- π Improved readability - Cleaner representation of security rules
- π§ Extended ANTLR Listener - Extends the listener generated by the ANTLR Seclang parser
- π Complete Rule Loading - Loads Seclang rule information stored in the CRS files, including comments
- π¨ New Representation - Defines a new representation for the rules and translates them into it
- π Bidirectional Conversion - Convert between Seclang and CRSLang formats
Initialize the repo:
git clone https://github.com/coreruleset/crslang.git
cd crslangGenerate the ANTLR parser code and build the project:
go buildLoad and translate the Seclang OWASP CRS files to the new representation:
./crslang -o crs seclang_parser/testdata/crsLoad and translate the CRSLang back to Seclang:
./crslang -s crs.yamlRun the tests to ensure everything works correctly:
go test -vCRSLang can be compiled to WebAssembly so the translation logic runs directly in the browser without any server-side component.
Running make wasm produces two files inside the wasm/ directory:
| File | Description |
|---|---|
wasm/crslang.wasm |
Compiled WebAssembly module |
wasm/wasm_exec.js |
Go-provided JS glue required to load the module |
make wasmThe generated files must be served over HTTP (browsers block file:// WASM
loads). Any static file server works, for example:
cd wasm
python3 -m http.server 8080
# Open http://localhost:8080/demo.htmlAfter loading wasm_exec.js and instantiating crslang.wasm, two functions
are available on the global window object:
Translates a SecLang configuration string into a CRSLang YAML string.
const result = seclangToCRSLang(
'SecRule REQUEST_HEADERS:Host "@rx ^$" "id:920280,phase:1,block,msg:\'Request Missing a Host Header\'"'
);
if (result.error) {
console.error("Translation failed:", result.error);
} else {
console.log(result.yaml); // CRSLang YAML string
}Translates a CRSLang YAML string back into SecLang format.
const result = crslangToSeclang(crslangYaml);
if (result.error) {
console.error("Translation failed:", result.error);
} else {
console.log(result.seclang); // SecLang string
}<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"></head>
<body>
<script src="wasm_exec.js"></script>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(fetch("crslang.wasm"), go.importObject)
.then(result => {
go.run(result.instance);
const out = seclangToCRSLang(
'SecRule REQUEST_HEADERS:Host "@rx ^$" ' +
'"id:920280,phase:1,block,msg:\'Request Missing a Host Header\'"'
);
console.log(out.yaml);
});
</script>
</body>
</html>A ready-to-use playground is available at wasm/index.html.
For detailed information about the project structure and API, check out the source code and test files.
- kind: rule
metadata:
comment: ...
phase: "1"
id: 920300
message: Request Missing an Accept Header
severity: NOTICE
tags:
- application-multi
- language-multi
- platform-multi
- attack-protocol
- paranoia-level/3
- OWASP_CRS
- capec/1000/210/272
- PCI/6.5.10
version: OWASP_CRS/4.0.1-dev
conditions:
- collections:
- name: REQUEST_HEADERS
arguments:
- Accept
count: true
operator:
eq: "0"
transformations:
- none
- variables:
- REQUEST_METHOD
operator:
negate: true
rx: ^(?:OPTIONS|CONNECT)$
- collections:
- name: REQUEST_HEADERS
arguments:
- User-Agent
operator:
negate: true
pm: AppleWebKit Android
transformations:
- none
actions:
disruptive: pass
non-disruptive:
- setvar:
collection: TX
operation: =+
assignments:
- inbound_anomaly_score_pl3: "%{tx.notice_anomaly_score}"
flow:
- chain- kind: rule
metadata:
comment: ...
phase: "1"
id: 920170
message: GET or HEAD Request with Body Content
severity: CRITICAL
tags:
- application-multi
- language-multi
- platform-multi
- attack-protocol
- paranoia-level/1
- OWASP_CRS
- capec/1000/210/272
version: OWASP_CRS/4.0.1-dev
conditions:
- variables:
- REQUEST_METHOD
operator:
rx: ^(?:GET|HEAD)$
transformations:
- none
actions:
disruptive: block
non-disruptive:
- logdata: "%{MATCHED_VAR}"
flow:
- chain
chained-rule:
kind: rule
conditions:
- collections:
- name: REQUEST_HEADERS
arguments:
- Content-Length
operator:
negate: true
rx: ^0?$
transformations:
- none
actions:
non-disruptive:
- setvar:
collection: TX
operation: =+
assignments:
- inbound_anomaly_score_pl1: "%{tx.critical_anomaly_score}"- kind: rule
metadata:
comment: ...
phase: "1"
id: 901200
tags:
- OWASP_CRS
version: OWASP_CRS/4.0.1-dev
conditions:
- alwaysMatch: true
transformations:
- none
actions:
disruptive: pass
non-disruptive:
- nolog
- setvar:
- blocking_inbound_anomaly_score: "0"
- detection_inbound_anomaly_score: "0"
- inbound_anomaly_score_pl1: "0"
- inbound_anomaly_score_pl2: "0"
- inbound_anomaly_score_pl3: "0"
- inbound_anomaly_score_pl4: "0"
- sql_injection_score: "0"
- xss_score: "0"
- rfi_score: "0"
- lfi_score: "0"
- rce_score: "0"
- php_injection_score: "0"
- http_violation_score: "0"
- session_fixation_score: "0"
- blocking_outbound_anomaly_score: "0"
- detection_outbound_anomaly_score: "0"
- outbound_anomaly_score_pl1: "0"
- outbound_anomaly_score_pl2: "0"
- outbound_anomaly_score_pl3: "0"
- outbound_anomaly_score_pl4: "0"
- anomaly_score: "0"We welcome contributions! Please feel free to submit issues and pull requests.
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.