feat: Generate Nginx WAF config with separate map and rule files

This commit modifies the script to output two files:
- waf_maps.conf (for http block)
- waf_rules.conf (for server block)
to avoid conflicts and provide more flexibility.

This update should fix the bugged nginx rules integration on existing setups: https://github.com/fabriziosalmi/patterns/issues/8
This commit is contained in:
fabriziosalmi 2025-01-28 22:40:56 +01:00
parent eaf5714520
commit f1bae07d6c
6 changed files with 534 additions and 105 deletions

View File

@ -15,6 +15,9 @@ logging.basicConfig(
# Input and output paths # Input and output paths
INPUT_FILE = Path(os.getenv("INPUT_FILE", "owasp_rules.json")) INPUT_FILE = Path(os.getenv("INPUT_FILE", "owasp_rules.json"))
OUTPUT_DIR = Path(os.getenv("OUTPUT_DIR", "waf_patterns/nginx")) OUTPUT_DIR = Path(os.getenv("OUTPUT_DIR", "waf_patterns/nginx"))
MAPS_FILE = OUTPUT_DIR / "waf_maps.conf"
RULES_FILE = OUTPUT_DIR / "waf_rules.conf"
# Create output directory if it doesn't exist # Create output directory if it doesn't exist
OUTPUT_DIR.mkdir(parents=True, exist_ok=True) OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
@ -52,10 +55,18 @@ def sanitize_pattern(pattern):
return None return None
if pattern.startswith("@rx "): if pattern.startswith("@rx "):
sanitized_pattern = pattern.replace("@rx ", "").strip() sanitized_pattern = pattern.replace("@rx ", "").strip()
return sanitized_pattern if validate_regex(sanitized_pattern) else None if validate_regex(sanitized_pattern):
return re.escape(sanitized_pattern).replace(r'\@', '@')
return pattern if validate_regex(pattern) else None else:
logging.warning(f"Invalid regex in pattern: {sanitized_pattern}")
return None
if validate_regex(pattern):
return re.escape(pattern).replace(r'\@', '@')
else:
logging.warning(f"Invalid regex in pattern: {pattern}")
return None
def generate_nginx_waf(rules): def generate_nginx_waf(rules):
@ -73,54 +84,72 @@ def generate_nginx_waf(rules):
else: else:
logging.warning(f"Invalid or unsupported pattern skipped: {pattern}") logging.warning(f"Invalid or unsupported pattern skipped: {pattern}")
# Write Nginx rule snippets per category # Write map definitions to a dedicated file
for category, patterns in categorized_rules.items(): try:
output_file = OUTPUT_DIR / f"{category}.conf" with open(MAPS_FILE, "w") as f:
try: f.write("# Nginx WAF Maps Definitions\n")
with open(output_file, "w") as f: f.write("# Automatically generated from OWASP rules.\n\n")
f.write(f"# Nginx WAF rules for {category.upper()}\n") f.write("http {\n")
f.write("# Automatically generated from OWASP rules.\n") for category, patterns in categorized_rules.items():
f.write("# Include this file in your server or location block.\n\n") f.write(f" map $request_uri $waf_block_{category} {{\n")
f.write(" default 0;\n")
for pattern in patterns:
escaped_pattern = pattern.replace('"', '\\"')
f.write(f' "~*{escaped_pattern}" 1;\n')
f.write(" }\n\n")
f.write("}\n")
# Use a map to avoid redundant patterns logging.info(f"Generated {MAPS_FILE} containing map definitions")
f.write("map $request_uri $waf_block_{category} {{\n".format(category=category)) except IOError as e:
f.write(" default 0;\n") logging.error(f"Failed to write {MAPS_FILE}: {e}")
for pattern in patterns:
escaped_pattern = pattern.replace('"', '\\"')
f.write(f' "~*{escaped_pattern}" 1;\n')
f.write("}\n\n")
# Apply the WAF rule
f.write("if ($waf_block_{category}) {{\n".format(category=category))
f.write(" return 403;\n")
f.write(" # Log the blocked request (optional)\n")
f.write(" # access_log /var/log/nginx/waf_blocked.log;\n")
f.write("}\n\n")
logging.info(f"Generated {output_file} ({len(patterns)} patterns)") # Write if blocks to a dedicated file
except IOError as e: try:
logging.error(f"Failed to write {output_file}: {e}") with open(RULES_FILE, "w") as f:
f.write("# Nginx WAF Rules\n")
f.write("# Automatically generated from OWASP rules.\n")
f.write("# Include this file inside server block\n\n")
f.write(" # WAF rules\n")
for category in categorized_rules.keys():
f.write(f" if ($waf_block_{category}) {{\n")
f.write(" return 403;\n")
f.write(" # Log the blocked request (optional)\n")
f.write(" # access_log /var/log/nginx/waf_blocked.log;\n")
f.write(" }\n\n")
logging.info(f"Generated {RULES_FILE} containing rules")
except IOError as e:
logging.error(f"Failed to write {RULES_FILE}: {e}")
# Generate a README file with usage instructions # Generate a README file with usage instructions
readme_file = OUTPUT_DIR / "README.md" readme_file = OUTPUT_DIR / "README.md"
with open(readme_file, "w") as f: with open(readme_file, "w") as f:
f.write("# Nginx WAF Rule Snippets\n\n") f.write("# Nginx WAF Configuration\n\n")
f.write("This directory contains Nginx WAF rule snippets generated from OWASP rules.\n") f.write("This directory contains Nginx WAF configuration files generated from OWASP rules.\n")
f.write("You can include these snippets in your existing Nginx configuration to enhance security.\n\n") f.write("You can include these files in your existing Nginx configuration to enhance security.\n\n")
f.write("## Usage\n") f.write("## Usage\n")
f.write("1. Include the rule snippets in your `server` or `location` block:\n") f.write("1. Include the `waf_maps.conf` file in your `nginx.conf` *inside the `http` block*:\n")
f.write(" ```nginx\n") f.write(" ```nginx\n")
f.write(" server {\n") f.write(" http {\n")
f.write(" # Your existing configuration\n") f.write(" include /path/to/waf_patterns/nginx/waf_maps.conf;\n")
f.write(" include /path/to/waf_patterns/nginx/*.conf;\n") f.write(" # ... other http configurations ...\n")
f.write(" }\n") f.write(" }\n")
f.write(" ```\n") f.write(" ```\n")
f.write("2. Reload Nginx to apply the changes:\n") f.write("2. Include the `waf_rules.conf` file in your `server` block:\n")
f.write(" ```nginx\n")
f.write(" server {\n")
f.write(" # ... other server configurations ...\n")
f.write(" include /path/to/waf_patterns/nginx/waf_rules.conf;\n")
f.write(" }\n")
f.write(" ```\n")
f.write("3. Reload Nginx to apply the changes:\n")
f.write(" ```bash\n") f.write(" ```bash\n")
f.write(" sudo nginx -t && sudo systemctl reload nginx\n") f.write(" sudo nginx -t && sudo systemctl reload nginx\n")
f.write(" ```\n") f.write(" ```\n")
f.write("\n## Notes\n") f.write("\n## Notes\n")
f.write("- The rules use `map` directives for efficient pattern matching.\n") f.write("- The rules use `map` directives for efficient pattern matching. The maps are defined in the `waf_maps.conf` file.\n")
f.write("- The rules (if statements) are defined in the `waf_rules.conf` file.\n")
f.write("- Blocked requests return a `403 Forbidden` response by default.\n") f.write("- Blocked requests return a `403 Forbidden` response by default.\n")
f.write("- You can enable logging for blocked requests by uncommenting the `access_log` line.\n") f.write("- You can enable logging for blocked requests by uncommenting the `access_log` line.\n")

View File

@ -1,22 +1,30 @@
# Nginx WAF Rule Snippets # Nginx WAF Configuration
This directory contains Nginx WAF rule snippets generated from OWASP rules. This directory contains Nginx WAF configuration files generated from OWASP rules.
You can include these snippets in your existing Nginx configuration to enhance security. You can include these files in your existing Nginx configuration to enhance security.
## Usage ## Usage
1. Include the rule snippets in your `server` or `location` block: 1. Include the `waf_maps.conf` file in your `nginx.conf` *inside the `http` block*:
```nginx ```nginx
server { http {
# Your existing configuration include /path/to/waf_patterns/nginx/waf_maps.conf;
include /path/to/waf_patterns/nginx/*.conf; # ... other http configurations ...
} }
``` ```
2. Reload Nginx to apply the changes: 2. Include the `waf_rules.conf` file in your `server` block:
```nginx
server {
# ... other server configurations ...
include /path/to/waf_patterns/nginx/waf_rules.conf;
}
```
3. Reload Nginx to apply the changes:
```bash ```bash
sudo nginx -t && sudo systemctl reload nginx sudo nginx -t && sudo systemctl reload nginx
``` ```
## Notes ## Notes
- The rules use `map` directives for efficient pattern matching. - The rules use `map` directives for efficient pattern matching. The maps are defined in the `waf_maps.conf` file.
- The rules (if statements) are defined in the `waf_rules.conf` file.
- Blocked requests return a `403 Forbidden` response by default. - Blocked requests return a `403 Forbidden` response by default.
- You can enable logging for blocked requests by uncommenting the `access_log` line. - You can enable logging for blocked requests by uncommenting the `access_log` line.

View File

@ -1,44 +0,0 @@
# Nginx WAF rules for DETECTION
location / {
set $attack_detected 0;
if ($request_uri ~* "@lt 1") {
set $attack_detected 1;
}
if ($request_uri ~* "@lt 1") {
set $attack_detected 1;
}
if ($request_uri ~* "@pmFromFile scanners-user-agents.data") {
set $attack_detected 1;
}
if ($request_uri ~* "@lt 2") {
set $attack_detected 1;
}
if ($request_uri ~* "@lt 2") {
set $attack_detected 1;
}
if ($request_uri ~* "@lt 3") {
set $attack_detected 1;
}
if ($request_uri ~* "@lt 3") {
set $attack_detected 1;
}
if ($request_uri ~* "@lt 4") {
set $attack_detected 1;
}
if ($request_uri ~* "@lt 4") {
set $attack_detected 1;
}
if ($attack_detected = 1) {
return 403;
}
}

View File

@ -1,15 +0,0 @@
# Nginx WAF rules for LFI
# Automatically generated from OWASP rules.
# Include this file in your server or location block.
map $request_uri $waf_block_lfi {
default 0;
"~*(?:(?:^|[x5c/;]).{2,3}[x5c/;]|[x5c/;].{2,3}(?:[x5c/;]|$))" 1;
}
if ($waf_block_lfi) {
return 403;
# Log the blocked request (optional)
# access_log /var/log/nginx/waf_blocked.log;
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,119 @@
# Nginx WAF Rules
# Automatically generated from OWASP rules.
# Include this file inside server block
# WAF rules
if ($waf_block_initialization) {
return 403;
# Log the blocked request (optional)
# access_log /var/log/nginx/waf_blocked.log;
}
if ($waf_block_attack) {
return 403;
# Log the blocked request (optional)
# access_log /var/log/nginx/waf_blocked.log;
}
if ($waf_block_exceptions) {
return 403;
# Log the blocked request (optional)
# access_log /var/log/nginx/waf_blocked.log;
}
if ($waf_block_rfi) {
return 403;
# Log the blocked request (optional)
# access_log /var/log/nginx/waf_blocked.log;
}
if ($waf_block_lfi) {
return 403;
# Log the blocked request (optional)
# access_log /var/log/nginx/waf_blocked.log;
}
if ($waf_block_enforcement) {
return 403;
# Log the blocked request (optional)
# access_log /var/log/nginx/waf_blocked.log;
}
if ($waf_block_php) {
return 403;
# Log the blocked request (optional)
# access_log /var/log/nginx/waf_blocked.log;
}
if ($waf_block_fixation) {
return 403;
# Log the blocked request (optional)
# access_log /var/log/nginx/waf_blocked.log;
}
if ($waf_block_evaluation) {
return 403;
# Log the blocked request (optional)
# access_log /var/log/nginx/waf_blocked.log;
}
if ($waf_block_sql) {
return 403;
# Log the blocked request (optional)
# access_log /var/log/nginx/waf_blocked.log;
}
if ($waf_block_generic) {
return 403;
# Log the blocked request (optional)
# access_log /var/log/nginx/waf_blocked.log;
}
if ($waf_block_leakages) {
return 403;
# Log the blocked request (optional)
# access_log /var/log/nginx/waf_blocked.log;
}
if ($waf_block_java) {
return 403;
# Log the blocked request (optional)
# access_log /var/log/nginx/waf_blocked.log;
}
if ($waf_block_xss) {
return 403;
# Log the blocked request (optional)
# access_log /var/log/nginx/waf_blocked.log;
}
if ($waf_block_rce) {
return 403;
# Log the blocked request (optional)
# access_log /var/log/nginx/waf_blocked.log;
}
if ($waf_block_sqli) {
return 403;
# Log the blocked request (optional)
# access_log /var/log/nginx/waf_blocked.log;
}
if ($waf_block_iis) {
return 403;
# Log the blocked request (optional)
# access_log /var/log/nginx/waf_blocked.log;
}
if ($waf_block_shells) {
return 403;
# Log the blocked request (optional)
# access_log /var/log/nginx/waf_blocked.log;
}
if ($waf_block_correlation) {
return 403;
# Log the blocked request (optional)
# access_log /var/log/nginx/waf_blocked.log;
}