Web Application Firewall built with Kemal framework
Rules are YAML files. You can use simple patterns or full OWASP-style rules with multiple operators and transforms.
---
id: 942100
msg: "SQL Injection Attack Detected"
variables:
- ARGS
- BODY
- REQUEST_LINE
pattern: "(?i)(union.*select|select.*from|insert.*into)"
action: deny
transforms:
- url_decode
- lowercase
---
id: 942100
name: "SQL Injection - LibInjection Detection"
msg: "SQL Injection Attack Detected via libinjection"
category: "sqli"
severity: "CRITICAL"
paranoia_level: 1
operator: "libinjection_sqli" # or "regex", "libinjection_xss", "contains", "starts_with"
pattern: null # null for LibInjection, pattern for regex
variables:
- type: COOKIE
- type: ARGS
- type: ARGS_NAMES
- type: HEADERS
names: ["User-Agent", "Referer"] # Filter for specific headers
- type: BODY
transforms:
- none
- utf8_to_unicode
- url_decode_uni
- remove_nulls
action: "deny"
tags:
- "OWASP_CRS"
- "attack-sqli"
- "paranoia-level/1"
deny (block) or log (log only) (string, required)sqli, xss, lfi, rce, etc. (string, optional)CRITICAL, HIGH, MEDIUM, LOW (string, optional)Standard regex pattern matching.
operator: "regex"
pattern: "(?i)(union.*select|select.*from)"
LibInjection SQL injection detection (no pattern needed).
operator: "libinjection_sqli"
pattern: null
LibInjection XSS detection (no pattern needed).
operator: "libinjection_xss"
pattern: null
Simple string contains check.
operator: "contains"
pattern: "<script>"
String starts with check.
operator: "starts_with"
pattern: "javascript:"
variables:
- ARGS
- BODY
- REQUEST_LINE
variables:
- type: HEADERS
names: ["User-Agent", "Referer"] # Only check these headers
- type: ARGS
- type: BODY
Transformations are applied in order before pattern matching:
transforms:
- url_decode # %27 -> '
- url_decode_uni # %u0027 -> '
- lowercase # SELECT -> select
- remove_nulls # Remove \0 bytes
.yaml file in the rules/ directory (or subdirectories)Create rules/custom/sqli-detection.yaml:
---
id: 942100
name: "SQL Injection - LibInjection Detection"
msg: "SQL Injection Attack Detected via libinjection"
category: "sqli"
severity: "CRITICAL"
operator: "libinjection_sqli"
pattern: null
variables:
- type: ARGS
- type: ARGS_NAMES
- type: BODY
- type: COOKIE
transforms:
- none
- url_decode_uni
- remove_nulls
action: "deny"
tags:
- "OWASP_CRS"
- "attack-sqli"
Create rules/custom/xss-detection.yaml:
---
id: 941100
name: "XSS Attack Detection"
msg: "XSS Attack Detected"
category: "xss"
severity: "HIGH"
operator: "libinjection_xss"
pattern: null
variables:
- type: ARGS
- type: BODY
- type: HEADERS
names: ["User-Agent", "Referer"]
transforms:
- url_decode_uni
- lowercase
action: "deny"
tags:
- "OWASP_CRS"
- "attack-xss"
Create rules/custom/path-traversal.yaml:
---
id: 930100
name: "Path Traversal Attack"
msg: "Path Traversal Attack Detected"
category: "lfi"
severity: "HIGH"
operator: "regex"
pattern: "(?i)(\.\./|\.\.\\\|%2e%2e%2f|%2e%2e%5c)"
variables:
- type: REQUEST_FILENAME
- type: ARGS
transforms:
- url_decode
- url_decode_uni
action: "deny"
tags:
- "attack-lfi"
The project includes OWASP CRS SQL Injection rules (in the rules/owasp-crs/ folder):
These rules have been manually converted from OWASP CRS to YAML format. To add new rules:
The LibInjection C library must be installed on the system or built from source:
# To build LibInjection from source
git clone https://github.com/libinjection/libinjection.git
cd libinjection
make
sudo make install
It is linked during Crystal build with the -linjection flag.
Before deploying rules, test them in observe mode:
# config/waf.yml
waf:
mode: observe # Logs but doesn't block
# SQL Injection test
curl "http://localhost:3030/api/users?id=1' OR '1'='1"
# XSS test
curl "http://localhost:3030/search?q=<script>alert('xss')</script>"
# Check logs
docker logs kemal-waf | grep "OBSERVE MODE"
Organize rules in subdirectories:
rules/
basic-rules.yaml
sqli-rules.yaml
xss-rules.yaml
custom/
domain-specific.yaml
owasp-crs/
sqli-rules.yaml
Rules in subdirectories are automatically loaded.