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.
constresult=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.
constresult=crslangToSeclang(crslangYaml);if(result.error){console.error("Translation failed:",result.error);}else{console.log(result.seclang);// SecLang string}<!DOCTYPE html><html><head><metacharset="UTF-8"></head><body><scriptsrc="wasm_exec.js"></script><script>constgo=newGo();WebAssembly.instantiateStreaming(fetch("crslang.wasm"),go.importObject).then(result=>{go.run(result.instance);constout=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: rulemetadata:
comment: ...phase: "1"id: 920300message: Request Missing an Accept Headerseverity: NOTICEtags:
- application-multi
- language-multi
- platform-multi
- attack-protocol
- paranoia-level/3
- OWASP_CRS
- capec/1000/210/272
- PCI/6.5.10version: OWASP_CRS/4.0.1-devconditions:
- collections:
- name: REQUEST_HEADERSarguments:
- Acceptcount: trueoperator:
eq: "0"transformations:
- none
- variables:
- REQUEST_METHODoperator:
negate: truerx: ^(?:OPTIONS|CONNECT)$
- collections:
- name: REQUEST_HEADERSarguments:
- User-Agentoperator:
negate: truepm: AppleWebKit Androidtransformations:
- noneactions:
disruptive: passnon-disruptive:
- setvar:
collection: TXoperation: =+assignments:
- inbound_anomaly_score_pl3: "%{tx.notice_anomaly_score}"flow:
- chain- kind: rulemetadata:
comment: ...phase: "1"id: 920170message: GET or HEAD Request with Body Contentseverity: CRITICALtags:
- application-multi
- language-multi
- platform-multi
- attack-protocol
- paranoia-level/1
- OWASP_CRS
- capec/1000/210/272version: OWASP_CRS/4.0.1-devconditions:
- variables:
- REQUEST_METHODoperator:
rx: ^(?:GET|HEAD)$transformations:
- noneactions:
disruptive: blocknon-disruptive:
- logdata: "%{MATCHED_VAR}"flow:
- chainchained-rule:
kind: ruleconditions:
- collections:
- name: REQUEST_HEADERSarguments:
- Content-Lengthoperator:
negate: truerx: ^0?$transformations:
- noneactions:
non-disruptive:
- setvar:
collection: TXoperation: =+assignments:
- inbound_anomaly_score_pl1: "%{tx.critical_anomaly_score}"- kind: rulemetadata:
comment: ...phase: "1"id: 901200tags:
- OWASP_CRSversion: OWASP_CRS/4.0.1-devconditions:
- alwaysMatch: truetransformations:
- noneactions:
disruptive: passnon-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.