diff --git a/package/secubox/secubox-app-mitmproxy/files/srv/mitmproxy/addons/waf_loader.py b/package/secubox/secubox-app-mitmproxy/files/srv/mitmproxy/addons/waf_loader.py new file mode 100644 index 00000000..ef58adb6 --- /dev/null +++ b/package/secubox/secubox-app-mitmproxy/files/srv/mitmproxy/addons/waf_loader.py @@ -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() diff --git a/package/secubox/secubox-app-mitmproxy/files/srv/mitmproxy/waf-rules.json b/package/secubox/secubox-app-mitmproxy/files/srv/mitmproxy/waf-rules.json new file mode 100644 index 00000000..013825a6 --- /dev/null +++ b/package/secubox/secubox-app-mitmproxy/files/srv/mitmproxy/waf-rules.json @@ -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": "]*>", "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": "]*>", "desc": "Iframe injection"}, + {"id": "xss-005", "pattern": "]*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"} + ] + } + } +} diff --git a/package/secubox/secubox-app-mitmproxy/files/usr/sbin/mitmproxy-waf-sync b/package/secubox/secubox-app-mitmproxy/files/usr/sbin/mitmproxy-waf-sync new file mode 100755 index 00000000..246f91e3 --- /dev/null +++ b/package/secubox/secubox-app-mitmproxy/files/usr/sbin/mitmproxy-waf-sync @@ -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"