From ee9a54b0a5558ecc5792c837e469d7d8555e6ccf Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Mon, 9 Feb 2026 16:47:17 +0100 Subject: [PATCH] fix(waf): Add LuCI whitelist and moderate sensitivity mode - Add TRUSTED_PATH_PREFIXES for LuCI, ubus, and CGI paths - Fix moderate mode to always require threshold (3 attempts in 5 min) instead of immediate ban on critical threats - Add WireGuard endpoint whitelist support to prevent VPN peer bans - New script: mitmproxy-sync-wg-endpoints extracts peer IPs from UCI - Bump version to v2.4 Prevents accidental bans from legitimate external LuCI login attempts. Co-Authored-By: Claude Opus 4.5 --- .../srv/mitmproxy/addons/secubox_analytics.py | 100 +++++++++++++++--- .../root/usr/sbin/mitmproxy-sync-wg-endpoints | 97 +++++++++++++++++ 2 files changed, 184 insertions(+), 13 deletions(-) create mode 100755 package/secubox/secubox-app-mitmproxy/root/usr/sbin/mitmproxy-sync-wg-endpoints diff --git a/package/secubox/secubox-app-mitmproxy/root/srv/mitmproxy/addons/secubox_analytics.py b/package/secubox/secubox-app-mitmproxy/root/srv/mitmproxy/addons/secubox_analytics.py index 21137eea..97db9301 100644 --- a/package/secubox/secubox-app-mitmproxy/root/srv/mitmproxy/addons/secubox_analytics.py +++ b/package/secubox/secubox-app-mitmproxy/root/srv/mitmproxy/addons/secubox_analytics.py @@ -30,6 +30,28 @@ AUTOBAN_FILE = "/data/autoban-requests.log" AUTOBAN_CONFIG = "/data/autoban.json" # Subdomain metrics file SUBDOMAIN_METRICS_FILE = "/tmp/secubox-subdomain-metrics.json" +# WireGuard endpoints file (written by host from UCI wireguard config) +# Contains public IPs of WireGuard peers that should never be banned +WIREGUARD_ENDPOINTS_FILE = "/data/wireguard-endpoints.json" + +# ============================================================================ +# TRUSTED PATH WHITELIST - Skip threat detection for legitimate management +# ============================================================================ + +# Paths that should NOT trigger threat detection (legitimate admin interfaces) +TRUSTED_PATH_PREFIXES = [ + '/cgi-bin/luci', # OpenWrt LuCI web interface + '/luci-static/', # LuCI static assets + '/ubus/', # OpenWrt ubus API + '/cgi-bin/cgi-', # OpenWrt CGI scripts +] + +# Hosts that should have relaxed detection (local management) +TRUSTED_HOSTS = [ + 'localhost', + '127.0.0.1', + '192.168.255.1', # Default SecuBox LAN IP +] # ============================================================================ # THREAT DETECTION PATTERNS @@ -489,6 +511,7 @@ class SecuBoxAnalytics: self.blocked_ips = set() self.autoban_config = {} self.autoban_requested = set() # Track IPs we've already requested to ban + self.wireguard_endpoints = set() # WireGuard peer endpoint IPs (never ban) # Attempt tracking for sensitivity-based auto-ban # Structure: {ip: [(timestamp, severity, reason), ...]} self.threat_attempts = defaultdict(list) @@ -508,7 +531,8 @@ class SecuBoxAnalytics: self._load_geoip() self._load_blocked_ips() self._load_autoban_config() - ctx.log.info("SecuBox Analytics addon v2.3 loaded - Enhanced threat detection with subdomain metrics") + self._load_wireguard_endpoints() + ctx.log.info("SecuBox Analytics addon v2.4 loaded - Enhanced threat detection with WireGuard protection") def _load_geoip(self): """Load GeoIP database if available""" @@ -694,6 +718,26 @@ class SecuBoxAnalytics: ctx.log.warn(f"Could not load auto-ban config: {e}") self.autoban_config = {'enabled': False} + def _load_wireguard_endpoints(self): + """ + Load WireGuard peer endpoint IPs from config file. + These IPs are NEVER banned to ensure VPN tunnel connectivity. + + The host should write this file from UCI wireguard config: + /data/wireguard-endpoints.json = {"endpoints": ["1.2.3.4", "5.6.7.8"]} + """ + try: + if os.path.exists(WIREGUARD_ENDPOINTS_FILE): + with open(WIREGUARD_ENDPOINTS_FILE, 'r') as f: + data = json.load(f) + endpoints = data.get('endpoints', []) + if isinstance(endpoints, list): + self.wireguard_endpoints = set(endpoints) + if self.wireguard_endpoints: + ctx.log.info(f"WireGuard endpoints loaded: {len(self.wireguard_endpoints)} IPs protected from auto-ban") + except Exception as e: + ctx.log.debug(f"Could not load WireGuard endpoints: {e}") + def _clean_old_attempts(self, ip: str, window: int): """Remove attempts older than the window for an IP""" now = time.time() @@ -744,7 +788,12 @@ class SecuBoxAnalytics: if ip in whitelist: return False, '' - # Skip local IPs + # Skip WireGuard peer endpoint IPs (critical for VPN connectivity) + if ip in self.wireguard_endpoints: + return False, '' + + # Skip local/private IPs (includes WireGuard tunnels which typically use 10.x.x.x) + # RFC 1918 private ranges: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 if ip.startswith(('10.', '172.16.', '172.17.', '172.18.', '172.19.', '172.20.', '172.21.', '172.22.', '172.23.', '172.24.', '172.25.', '172.26.', '172.27.', '172.28.', '172.29.', @@ -866,10 +915,9 @@ class SecuBoxAnalytics: # Permissive: always require threshold to be met return self._check_threshold(ip, threshold, window) - else: # moderate - # Moderate: critical threats ban immediately, others follow threshold - if is_critical_threat: - return True, threat_reason + else: # moderate (default) + # Moderate: ALWAYS require threshold to be met, never immediate ban + # This prevents accidental bans from single requests (e.g., legitimate LuCI login) return self._check_threshold(ip, threshold, window) def _request_autoban(self, ip: str, reason: str, severity: str = 'high'): @@ -974,8 +1022,29 @@ class SecuBoxAnalytics: 'device': device } + def _is_trusted_path(self, request: http.Request) -> bool: + """Check if request path is in trusted whitelist (e.g., LuCI management)""" + path = request.path.lower() + host = request.host.lower() if request.host else '' + + # Check trusted hosts + for trusted_host in TRUSTED_HOSTS: + if trusted_host in host: + return True + + # Check trusted path prefixes + for prefix in TRUSTED_PATH_PREFIXES: + if path.startswith(prefix.lower()): + return True + + return False + def _detect_bot_behavior(self, request: http.Request) -> dict: """Detect bot-like behavior based on request patterns""" + # Skip detection for trusted management paths + if self._is_trusted_path(request): + return {'is_bot_behavior': False, 'behavior_type': None, 'pattern': None, 'severity': None} + path = request.path.lower() for pattern in BOT_BEHAVIOR_PATHS: @@ -1021,6 +1090,10 @@ class SecuBoxAnalytics: def _detect_scan(self, request: http.Request) -> dict: """Comprehensive threat detection with categorized patterns""" + # Skip path-based detection for trusted management paths (LuCI, etc.) + # Still check for injection attacks in body/params as those are dangerous everywhere + is_trusted = self._is_trusted_path(request) + path = request.path.lower() full_url = request.pretty_url.lower() query = request.query @@ -1046,13 +1119,14 @@ class SecuBoxAnalytics: combined = ' '.join(search_targets) threats = [] - # Check path-based scans - for pattern in PATH_SCAN_PATTERNS: - if re.search(pattern, path, re.IGNORECASE): - return { - 'is_scan': True, 'pattern': pattern, 'type': 'path_scan', - 'severity': 'medium', 'category': 'reconnaissance' - } + # Check path-based scans (skip for trusted paths like LuCI) + if not is_trusted: + for pattern in PATH_SCAN_PATTERNS: + if re.search(pattern, path, re.IGNORECASE): + return { + 'is_scan': True, 'pattern': pattern, 'type': 'path_scan', + 'severity': 'medium', 'category': 'reconnaissance' + } # Check SQL Injection for pattern in SQL_INJECTION_PATTERNS: diff --git a/package/secubox/secubox-app-mitmproxy/root/usr/sbin/mitmproxy-sync-wg-endpoints b/package/secubox/secubox-app-mitmproxy/root/usr/sbin/mitmproxy-sync-wg-endpoints new file mode 100755 index 00000000..46297604 --- /dev/null +++ b/package/secubox/secubox-app-mitmproxy/root/usr/sbin/mitmproxy-sync-wg-endpoints @@ -0,0 +1,97 @@ +#!/bin/sh +# Sync WireGuard peer endpoints to mitmproxy WAF whitelist +# This ensures VPN peers are never banned by the WAF +# +# Run this: +# - On boot (via init script) +# - When WireGuard config changes (via UCI hook) +# - Periodically (via cron) + +ENDPOINTS_FILE="/srv/mitmproxy/wireguard-endpoints.json" + +# Extract all WireGuard peer endpoints from UCI +get_wg_endpoints() { + local endpoints="" + + # Get all wireguard interfaces + for iface in $(uci show network 2>/dev/null | grep "proto='wireguard'" | cut -d. -f2); do + # Get peers for this interface + for peer in $(uci show network 2>/dev/null | grep "network\.@wireguard_${iface}\[" | grep "endpoint_host" | cut -d= -f1); do + endpoint=$(uci -q get "$peer" 2>/dev/null | cut -d: -f1) + if [ -n "$endpoint" ]; then + # Skip if it's a hostname (contains letters) + case "$endpoint" in + *[a-zA-Z]*) + # Resolve hostname to IP + resolved=$(nslookup "$endpoint" 2>/dev/null | grep "Address" | tail -1 | awk '{print $2}') + if [ -n "$resolved" ] && [ "$resolved" != "#53" ]; then + endpoint="$resolved" + else + continue + fi + ;; + esac + + if [ -n "$endpoints" ]; then + endpoints="$endpoints, \"$endpoint\"" + else + endpoints="\"$endpoint\"" + fi + fi + done + done + + # Also check direct endpoint_host in wireguard peer sections + for peer in $(uci show network 2>/dev/null | grep "\.endpoint_host=" | cut -d= -f1); do + endpoint=$(uci -q get "$peer" 2>/dev/null | cut -d: -f1) + if [ -n "$endpoint" ]; then + case "$endpoint" in + *[a-zA-Z]*) + resolved=$(nslookup "$endpoint" 2>/dev/null | grep "Address" | tail -1 | awk '{print $2}') + if [ -n "$resolved" ] && [ "$resolved" != "#53" ]; then + endpoint="$resolved" + else + continue + fi + ;; + esac + + # Check if already in list + case "$endpoints" in + *"$endpoint"*) ;; + *) + if [ -n "$endpoints" ]; then + endpoints="$endpoints, \"$endpoint\"" + else + endpoints="\"$endpoint\"" + fi + ;; + esac + fi + done + + echo "$endpoints" +} + +# Main +endpoints=$(get_wg_endpoints) + +# Write JSON file +cat > "$ENDPOINTS_FILE" << EOF +{ + "updated": "$(date -Iseconds)", + "endpoints": [$endpoints] +} +EOF + +# Count endpoints +if [ -n "$endpoints" ]; then + count=$(echo "$endpoints" | tr ',' '\n' | wc -l) +else + count=0 +fi + +logger -t mitmproxy-wg "Synced $count WireGuard endpoint(s) to WAF whitelist" + +# If verbose mode +[ "$1" = "-v" ] && cat "$ENDPOINTS_FILE"