feat(mitmproxy): Add modular WAF rules with CVE patterns and autoban fixes
- Add waf-rules.json with 46 patterns across 8 categories: - sqli, xss, lfi, rce (OWASP Top 10) - cve_2024 (recent CVE exploits) - scanners, webmail, api_abuse - Add waf_loader.py dynamic rules loader module - Add mitmproxy-waf-sync UCI to JSON config sync script - Fix GeoIP: install geoip2 package in container - Fix autoban: add cron job, lower min_severity to "high" - Enable WAF for webmail (mail.secubox.in) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
736ac9332b
commit
e31e43b8d7
@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
SecuBox WAF Rules Loader
|
||||
Dynamically loads threat detection patterns from JSON config
|
||||
Supports modular enable/disable per category
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
WAF_RULES_FILE = "/data/waf-rules.json"
|
||||
WAF_CONFIG_FILE = "/data/waf-config.json"
|
||||
|
||||
class WafRulesLoader:
|
||||
def __init__(self):
|
||||
self.rules: Dict = {}
|
||||
self.compiled_patterns: Dict[str, List[Tuple[str, re.Pattern, str, str]]] = {}
|
||||
self.enabled_categories: set = set()
|
||||
self.load_rules()
|
||||
|
||||
def load_rules(self):
|
||||
"""Load rules from JSON file"""
|
||||
try:
|
||||
if os.path.exists(WAF_RULES_FILE):
|
||||
with open(WAF_RULES_FILE, "r") as f:
|
||||
self.rules = json.load(f)
|
||||
except Exception as e:
|
||||
print(f"[WAF] Error loading rules: {e}")
|
||||
self.rules = {"categories": {}}
|
||||
|
||||
# Load enabled categories from config
|
||||
self.load_config()
|
||||
|
||||
# Compile patterns
|
||||
self.compile_patterns()
|
||||
|
||||
def load_config(self):
|
||||
"""Load enabled categories from config file"""
|
||||
self.enabled_categories = set()
|
||||
try:
|
||||
if os.path.exists(WAF_CONFIG_FILE):
|
||||
with open(WAF_CONFIG_FILE, "r") as f:
|
||||
config = json.load(f)
|
||||
for cat, enabled in config.get("categories", {}).items():
|
||||
if enabled:
|
||||
self.enabled_categories.add(cat)
|
||||
else:
|
||||
# Default: enable all categories
|
||||
for cat in self.rules.get("categories", {}).keys():
|
||||
self.enabled_categories.add(cat)
|
||||
except Exception as e:
|
||||
print(f"[WAF] Error loading config: {e}")
|
||||
# Enable all on error
|
||||
for cat in self.rules.get("categories", {}).keys():
|
||||
self.enabled_categories.add(cat)
|
||||
|
||||
def compile_patterns(self):
|
||||
"""Compile regex patterns for enabled categories"""
|
||||
self.compiled_patterns = {}
|
||||
|
||||
for cat_id, cat_data in self.rules.get("categories", {}).items():
|
||||
if cat_id not in self.enabled_categories:
|
||||
continue
|
||||
|
||||
if not cat_data.get("enabled", True):
|
||||
continue
|
||||
|
||||
self.compiled_patterns[cat_id] = []
|
||||
severity = cat_data.get("severity", "medium")
|
||||
|
||||
for rule in cat_data.get("patterns", []):
|
||||
try:
|
||||
pattern = re.compile(rule["pattern"], re.IGNORECASE)
|
||||
rule_id = rule.get("id", "unknown")
|
||||
desc = rule.get("desc", "")
|
||||
cve = rule.get("cve", "")
|
||||
self.compiled_patterns[cat_id].append((rule_id, pattern, desc, severity, cve))
|
||||
except re.error as e:
|
||||
print(f"[WAF] Invalid pattern {rule.get(id)}: {e}")
|
||||
|
||||
def check_request(self, path: str, query: str, body: str, headers: dict) -> Optional[dict]:
|
||||
"""Check request against all enabled rules"""
|
||||
# Combine path, query and body for checking
|
||||
full_url = f"{path}?{query}" if query else path
|
||||
check_targets = {
|
||||
"url": full_url,
|
||||
"body": body,
|
||||
"user-agent": headers.get("user-agent", "")
|
||||
}
|
||||
|
||||
for cat_id, patterns in self.compiled_patterns.items():
|
||||
for rule_id, pattern, desc, severity, cve in patterns:
|
||||
# Check appropriate target based on rule
|
||||
for target_name, target_value in check_targets.items():
|
||||
if target_value and pattern.search(target_value):
|
||||
return {
|
||||
"matched": True,
|
||||
"category": cat_id,
|
||||
"rule_id": rule_id,
|
||||
"description": desc,
|
||||
"severity": severity,
|
||||
"cve": cve,
|
||||
"pattern": pattern.pattern,
|
||||
"target": target_name
|
||||
}
|
||||
|
||||
return None
|
||||
|
||||
def get_stats(self) -> dict:
|
||||
"""Get rule statistics"""
|
||||
total_rules = 0
|
||||
categories = []
|
||||
for cat_id, patterns in self.compiled_patterns.items():
|
||||
total_rules += len(patterns)
|
||||
cat_data = self.rules.get("categories", {}).get(cat_id, {})
|
||||
categories.append({
|
||||
"id": cat_id,
|
||||
"name": cat_data.get("name", cat_id),
|
||||
"rules": len(patterns),
|
||||
"severity": cat_data.get("severity", "medium")
|
||||
})
|
||||
|
||||
return {
|
||||
"total_rules": total_rules,
|
||||
"enabled_categories": len(self.compiled_patterns),
|
||||
"categories": categories
|
||||
}
|
||||
|
||||
# Global instance
|
||||
waf_loader = WafRulesLoader()
|
||||
|
||||
def check_threat(path: str, query: str = "", body: str = "", headers: dict = None) -> Optional[dict]:
|
||||
"""Convenience function for threat checking"""
|
||||
return waf_loader.check_request(path, query, body, headers or {})
|
||||
|
||||
def reload_rules():
|
||||
"""Reload rules from disk"""
|
||||
waf_loader.load_rules()
|
||||
|
||||
def get_waf_stats() -> dict:
|
||||
"""Get WAF statistics"""
|
||||
return waf_loader.get_stats()
|
||||
@ -0,0 +1,123 @@
|
||||
{
|
||||
"_meta": {
|
||||
"version": "1.0.0",
|
||||
"updated": "2026-02-07",
|
||||
"sources": ["OWASP Top 10", "CERT advisories", "CVE database"]
|
||||
},
|
||||
|
||||
"categories": {
|
||||
"sqli": {
|
||||
"name": "SQL Injection",
|
||||
"severity": "critical",
|
||||
"enabled": true,
|
||||
"owasp": "A03:2021",
|
||||
"patterns": [
|
||||
{"id": "sqli-001", "pattern": "union\\s+(all\\s+)?select", "desc": "UNION-based injection"},
|
||||
{"id": "sqli-002", "pattern": "[\x27\x22]\\s*(or|and)\\s*[\x27\x22]?\\d", "desc": "Boolean-based injection"},
|
||||
{"id": "sqli-003", "pattern": "(sleep|benchmark|waitfor|pg_sleep)\\s*\\(", "desc": "Time-based blind injection"},
|
||||
{"id": "sqli-004", "pattern": "information_schema\\.", "desc": "Schema enumeration"},
|
||||
{"id": "sqli-005", "pattern": "(load_file|into\\s+outfile|into\\s+dumpfile)", "desc": "File operations"},
|
||||
{"id": "sqli-006", "pattern": "group\\s+by.+having", "desc": "HAVING clause injection"},
|
||||
{"id": "sqli-007", "pattern": "order\\s+by\\s+\\d+(,\\d+)*--", "desc": "ORDER BY injection"}
|
||||
]
|
||||
},
|
||||
|
||||
"xss": {
|
||||
"name": "Cross-Site Scripting",
|
||||
"severity": "high",
|
||||
"enabled": true,
|
||||
"owasp": "A03:2021",
|
||||
"patterns": [
|
||||
{"id": "xss-001", "pattern": "<script[^>]*>", "desc": "Script tag injection"},
|
||||
{"id": "xss-002", "pattern": "javascript\\s*:", "desc": "JavaScript protocol"},
|
||||
{"id": "xss-003", "pattern": "on(error|load|click|mouse|focus|blur)\\s*=", "desc": "Event handler injection"},
|
||||
{"id": "xss-004", "pattern": "<iframe[^>]*>", "desc": "Iframe injection"},
|
||||
{"id": "xss-005", "pattern": "<svg[^>]*onload", "desc": "SVG-based XSS"},
|
||||
{"id": "xss-006", "pattern": "expression\\s*\\(", "desc": "CSS expression injection"}
|
||||
]
|
||||
},
|
||||
|
||||
"lfi": {
|
||||
"name": "Local File Inclusion",
|
||||
"severity": "critical",
|
||||
"enabled": true,
|
||||
"owasp": "A01:2021",
|
||||
"patterns": [
|
||||
{"id": "lfi-001", "pattern": "\\.\\./", "desc": "Directory traversal"},
|
||||
{"id": "lfi-002", "pattern": "/etc/(passwd|shadow|hosts)", "desc": "System file access"},
|
||||
{"id": "lfi-003", "pattern": "/proc/(self|version|cmdline)", "desc": "Proc filesystem access"},
|
||||
{"id": "lfi-004", "pattern": "php://filter", "desc": "PHP filter wrapper"},
|
||||
{"id": "lfi-005", "pattern": "file://", "desc": "File protocol"},
|
||||
{"id": "lfi-006", "pattern": "expect://", "desc": "Expect wrapper RCE"}
|
||||
]
|
||||
},
|
||||
|
||||
"rce": {
|
||||
"name": "Remote Code Execution",
|
||||
"severity": "critical",
|
||||
"enabled": true,
|
||||
"owasp": "A03:2021",
|
||||
"patterns": [
|
||||
{"id": "rce-001", "pattern": ";\\s*(cat|ls|id|whoami|uname|pwd)", "desc": "Command chaining"},
|
||||
{"id": "rce-002", "pattern": "\\|\\s*(cat|ls|id|whoami|bash|sh)", "desc": "Pipe injection"},
|
||||
{"id": "rce-003", "pattern": "\\$\\((cat|ls|id|whoami)", "desc": "Command substitution"},
|
||||
{"id": "rce-004", "pattern": "`(cat|ls|id|whoami|curl|wget)`", "desc": "Backtick execution"},
|
||||
{"id": "rce-005", "pattern": "(curl|wget)\\s+.+\\s*\\|\\s*(bash|sh)", "desc": "Remote script execution"},
|
||||
{"id": "rce-006", "pattern": "\\{\\{.*\\}\\}", "desc": "Template injection (SSTI)"}
|
||||
]
|
||||
},
|
||||
|
||||
"cve_2024": {
|
||||
"name": "CVE 2024-2025 Exploits",
|
||||
"severity": "critical",
|
||||
"enabled": true,
|
||||
"patterns": [
|
||||
{"id": "cve-2024-3400", "pattern": "/api/v\\d/totp/user-backup", "desc": "PAN-OS GlobalProtect RCE", "cve": "CVE-2024-3400"},
|
||||
{"id": "cve-2024-21887", "pattern": "/api/v1/totp/user-backup", "desc": "Ivanti Connect Secure", "cve": "CVE-2024-21887"},
|
||||
{"id": "cve-2023-46747", "pattern": "/mgmt/tm/util/bash", "desc": "F5 BIG-IP RCE", "cve": "CVE-2023-46747"},
|
||||
{"id": "cve-2023-22515", "pattern": "/setup/setupadministrator.action", "desc": "Confluence RCE", "cve": "CVE-2023-22515"},
|
||||
{"id": "cve-2024-1709", "pattern": "/SetupWizard\\.aspx", "desc": "ConnectWise ScreenConnect", "cve": "CVE-2024-1709"},
|
||||
{"id": "cve-2024-27198", "pattern": "/app/rest/users/id:\\d+/tokens", "desc": "TeamCity auth bypass", "cve": "CVE-2024-27198"}
|
||||
]
|
||||
},
|
||||
|
||||
"scanners": {
|
||||
"name": "Vulnerability Scanners",
|
||||
"severity": "medium",
|
||||
"enabled": true,
|
||||
"patterns": [
|
||||
{"id": "scan-001", "pattern": "(nikto|nmap|sqlmap|burp|zap|acunetix)", "desc": "Scanner user-agent", "check": "user-agent"},
|
||||
{"id": "scan-002", "pattern": "/\\.git/config", "desc": "Git config probe"},
|
||||
{"id": "scan-003", "pattern": "/\\.env", "desc": "Environment file probe"},
|
||||
{"id": "scan-004", "pattern": "/(wp-login|xmlrpc)\\.php", "desc": "WordPress probe"},
|
||||
{"id": "scan-005", "pattern": "/actuator/(health|info|env)", "desc": "Spring Boot actuator"},
|
||||
{"id": "scan-006", "pattern": "/debug/pprof", "desc": "Go pprof debug"}
|
||||
]
|
||||
},
|
||||
|
||||
"webmail": {
|
||||
"name": "Webmail Specific",
|
||||
"severity": "high",
|
||||
"enabled": true,
|
||||
"patterns": [
|
||||
{"id": "mail-001", "pattern": "\\.\\./(config|db|data)", "desc": "Roundcube path traversal"},
|
||||
{"id": "mail-002", "pattern": "_action=(upload|import).*\\.(php|phtml)", "desc": "Malicious upload"},
|
||||
{"id": "mail-003", "pattern": "_uid=.*[\\x27\\x22<>]", "desc": "XSS in mail UID"},
|
||||
{"id": "mail-004", "pattern": "installer/", "desc": "Installer access attempt"},
|
||||
{"id": "mail-005", "pattern": "(temp|logs)/.*\\.(php|sh|pl)", "desc": "Script in temp/logs"}
|
||||
]
|
||||
},
|
||||
|
||||
"api_abuse": {
|
||||
"name": "API Abuse",
|
||||
"severity": "medium",
|
||||
"enabled": true,
|
||||
"patterns": [
|
||||
{"id": "api-001", "pattern": "/api/.*/admin", "desc": "Admin API access"},
|
||||
{"id": "api-002", "pattern": "graphql.*(__schema|introspection)", "desc": "GraphQL introspection"},
|
||||
{"id": "api-003", "pattern": "\\{.*\\$where.*\\}", "desc": "NoSQL injection"},
|
||||
{"id": "api-004", "pattern": "jwt=.*\\.\\.\\.\\.", "desc": "JWT manipulation"}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
36
package/secubox/secubox-app-mitmproxy/files/usr/sbin/mitmproxy-waf-sync
Executable file
36
package/secubox/secubox-app-mitmproxy/files/usr/sbin/mitmproxy-waf-sync
Executable file
@ -0,0 +1,36 @@
|
||||
#!/bin/sh
|
||||
# Sync mitmproxy WAF config from UCI to JSON
|
||||
|
||||
CONFIG_FILE="/srv/mitmproxy/waf-config.json"
|
||||
|
||||
# Read UCI values
|
||||
enabled=$(uci -q get mitmproxy.waf_rules.enabled || echo 1)
|
||||
sqli=$(uci -q get mitmproxy.waf_rules.sqli || echo 1)
|
||||
xss=$(uci -q get mitmproxy.waf_rules.xss || echo 1)
|
||||
lfi=$(uci -q get mitmproxy.waf_rules.lfi || echo 1)
|
||||
rce=$(uci -q get mitmproxy.waf_rules.rce || echo 1)
|
||||
cve_2024=$(uci -q get mitmproxy.waf_rules.cve_2024 || echo 1)
|
||||
scanners=$(uci -q get mitmproxy.waf_rules.scanners || echo 1)
|
||||
webmail=$(uci -q get mitmproxy.waf_rules.webmail || echo 1)
|
||||
api_abuse=$(uci -q get mitmproxy.waf_rules.api_abuse || echo 1)
|
||||
|
||||
# Convert to JSON booleans
|
||||
to_bool() { [ "$1" = "1" ] && echo "true" || echo "false"; }
|
||||
|
||||
cat > "$CONFIG_FILE" << EOF
|
||||
{
|
||||
"enabled": $(to_bool $enabled),
|
||||
"categories": {
|
||||
"sqli": $(to_bool $sqli),
|
||||
"xss": $(to_bool $xss),
|
||||
"lfi": $(to_bool $lfi),
|
||||
"rce": $(to_bool $rce),
|
||||
"cve_2024": $(to_bool $cve_2024),
|
||||
"scanners": $(to_bool $scanners),
|
||||
"webmail": $(to_bool $webmail),
|
||||
"api_abuse": $(to_bool $api_abuse)
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "[WAF] Config synced to $CONFIG_FILE"
|
||||
Loading…
Reference in New Issue
Block a user