From 75b85080faca54bc0c016659da598a713bca7d93 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Tue, 17 Feb 2026 16:02:58 +0100 Subject: [PATCH] feat(mitmproxy): Add WAF Filters UI to LuCI dashboard - Add new WAF Filters tab displaying 10 detection categories - Categories: sqli, xss, lfi, rce, cve_2024, scanners, webmail, api_abuse, nextcloud, roundcube - Toggle enable/disable per category with live updates - Expandable rules tables with patterns, descriptions, CVE links - Summary stats: total categories, active filters, rule count - RPCD methods: get_waf_rules, toggle_waf_category - Update menu entry and ACL permissions Co-Authored-By: Claude Opus 4.5 --- .claude/HISTORY.md | 19 ++ .claude/WIP.md | 8 + .../resources/view/mitmproxy/waf-filters.js | 215 ++++++++++++++++++ .../root/usr/libexec/rpcd/luci.mitmproxy | 30 ++- .../share/luci/menu.d/luci-app-mitmproxy.json | 7 +- .../share/rpcd/acl.d/luci-app-mitmproxy.json | 5 +- 6 files changed, 280 insertions(+), 4 deletions(-) create mode 100644 package/secubox/luci-app-mitmproxy/htdocs/luci-static/resources/view/mitmproxy/waf-filters.js diff --git a/.claude/HISTORY.md b/.claude/HISTORY.md index dce5a417..ffc93200 100644 --- a/.claude/HISTORY.md +++ b/.claude/HISTORY.md @@ -2271,3 +2271,22 @@ git checkout HEAD -- index.html - Ports: 25, 143, 587, 993 - Mail storage: `/var/mail/` with vmail user (uid 5000) - Old Alpine backup: `/srv/lxc/mailserver-alpine-backup/` + +### 2026-02-17: mitmproxy WAF Filters UI + +**New LuCI View:** +- Added "WAF Filters" tab to mitmproxy security interface +- Displays all 10 WAF detection categories with enable/disable toggles +- Categories: sqli, xss, lfi, rce, cve_2024, scanners, webmail, api_abuse, nextcloud, roundcube +- Summary stats: total categories, active filters, rule count +- Expandable rules tables showing patterns, descriptions, CVE links + +**RPCD Methods:** +- `get_waf_rules` - Returns WAF rules JSON from `/srv/mitmproxy/waf-rules.json` +- `toggle_waf_category` - Enable/disable category in rules file + +**Files Created/Modified:** +- `luci-app-mitmproxy/htdocs/.../view/mitmproxy/waf-filters.js` (new) +- `luci-app-mitmproxy/root/usr/libexec/rpcd/luci.mitmproxy` (added methods) +- `luci-app-mitmproxy/root/usr/share/luci/menu.d/luci-app-mitmproxy.json` (menu entry) +- `luci-app-mitmproxy/root/usr/share/rpcd/acl.d/luci-app-mitmproxy.json` (ACL permissions) diff --git a/.claude/WIP.md b/.claude/WIP.md index 4f3b7e32..f28adf87 100644 --- a/.claude/WIP.md +++ b/.claude/WIP.md @@ -64,6 +64,14 @@ _Last updated: 2026-02-17 (v0.21.0 - Nextcloud LXC + WebRadio)_ ### Just Completed (2026-02-17) +- **mitmproxy WAF Filters UI** — DONE (2026-02-17) + - Added new "WAF Filters" tab to mitmproxy LuCI interface + - Displays 10 filter categories: sqli, xss, lfi, rce, cve_2024, scanners, webmail, api_abuse, nextcloud, roundcube + - Toggle enable/disable per category with live updates + - Expandable rules tables showing patterns, descriptions, CVE links + - Summary stats: total categories, active filters, rule count + - RPCD methods: get_waf_rules, toggle_waf_category + - **Security KISS Dashboard Enhancements** — DONE (2026-02-17) - Added ndpid (nDPI daemon) to service status monitoring - Added Wazuh SIEM to security services list (earlier today) diff --git a/package/secubox/luci-app-mitmproxy/htdocs/luci-static/resources/view/mitmproxy/waf-filters.js b/package/secubox/luci-app-mitmproxy/htdocs/luci-static/resources/view/mitmproxy/waf-filters.js new file mode 100644 index 00000000..75d0cb35 --- /dev/null +++ b/package/secubox/luci-app-mitmproxy/htdocs/luci-static/resources/view/mitmproxy/waf-filters.js @@ -0,0 +1,215 @@ +'use strict'; +'require view'; +'require rpc'; +'require ui'; +'require secubox/kiss-theme'; + +var callGetWafRules = rpc.declare({ + object: 'luci.mitmproxy', + method: 'get_waf_rules', + expect: {} +}); + +var callToggleWafCategory = rpc.declare({ + object: 'luci.mitmproxy', + method: 'toggle_waf_category', + params: ['category', 'enabled'] +}); + +function severityColor(sev) { + return { + critical: '#e74c3c', + high: '#e67e22', + medium: '#f39c12', + low: '#3498db' + }[sev] || '#666'; +} + +function severityBadge(sev) { + return E('span', { + 'style': 'background: ' + severityColor(sev) + '; color: white; padding: 2px 8px; border-radius: 4px; font-size: 11px; text-transform: uppercase;' + }, sev); +} + +return view.extend({ + load: function() { + return callGetWafRules().catch(function() { return {}; }); + }, + + render: function(data) { + var self = this; + var rulesData = data || {}; + // Handle both top-level categories and wrapped in "categories" object + var rules = rulesData.categories || rulesData; + var categories = Object.keys(rules).filter(function(k) { + return k !== '_meta' && typeof rules[k] === 'object' && rules[k].patterns; + }); + + // Count totals + var totalRules = 0; + var enabledCategories = 0; + categories.forEach(function(cat) { + totalRules += (rules[cat].patterns || []).length; + if (rules[cat].enabled !== false) enabledCategories++; + }); + + var content = [ + // Header + E('div', { 'style': 'margin-bottom: 24px;' }, [ + E('h2', { 'style': 'font-size: 24px; font-weight: 700; margin: 0 0 8px 0;' }, '🛡️ WAF Filters'), + E('p', { 'style': 'color: var(--kiss-muted); margin: 0;' }, 'Web Application Firewall detection rules') + ]), + + // Summary Stats + E('div', { 'class': 'kiss-grid kiss-grid-auto', 'style': 'margin-bottom: 24px;' }, [ + E('div', { 'class': 'kiss-stat' }, [ + E('div', { 'class': 'kiss-stat-value', 'style': 'color: #3498db;' }, String(categories.length)), + E('div', { 'class': 'kiss-stat-label' }, 'Categories') + ]), + E('div', { 'class': 'kiss-stat' }, [ + E('div', { 'class': 'kiss-stat-value', 'style': 'color: #27ae60;' }, String(enabledCategories)), + E('div', { 'class': 'kiss-stat-label' }, 'Active') + ]), + E('div', { 'class': 'kiss-stat' }, [ + E('div', { 'class': 'kiss-stat-value', 'style': 'color: #e67e22;' }, String(totalRules)), + E('div', { 'class': 'kiss-stat-label' }, 'Rules') + ]) + ]), + + // Categories + E('div', { 'class': 'cbi-section' }, [ + E('h3', {}, _('Filter Categories')), + E('div', { 'class': 'cbi-section-node' }, + categories.length > 0 ? categories.map(function(catName) { + var cat = rules[catName]; + var catRules = cat.patterns || []; + var isEnabled = cat.enabled !== false; + var severity = cat.severity || 'medium'; + + return E('div', { + 'class': 'kiss-card', + 'style': 'margin-bottom: 16px; border-left: 4px solid ' + severityColor(severity) + ';' + }, [ + // Category Header + E('div', { + 'style': 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;' + }, [ + E('div', {}, [ + E('strong', { 'style': 'font-size: 16px; text-transform: uppercase;' }, catName.replace(/_/g, ' ')), + ' ', + severityBadge(severity), + E('span', { + 'style': 'margin-left: 8px; font-size: 12px; color: var(--kiss-muted);' + }, '(' + catRules.length + ' rules)') + ]), + E('label', { 'style': 'display: flex; align-items: center; cursor: pointer;' }, [ + E('input', { + 'type': 'checkbox', + 'checked': isEnabled, + 'style': 'width: 18px; height: 18px; margin-right: 8px;', + 'data-category': catName, + 'change': function(ev) { + var category = ev.target.dataset.category; + var enabled = ev.target.checked; + ui.showModal(_('Updating...'), [E('p', { 'class': 'spinning' }, _('Updating category...'))]); + callToggleWafCategory(category, enabled).then(function(res) { + ui.hideModal(); + if (res && res.success) { + ui.addNotification(null, E('p', {}, + category + ' ' + (enabled ? _('enabled') : _('disabled'))), 'success'); + } else { + ui.addNotification(null, E('p', {}, + _('Failed: ') + (res.error || 'Unknown error')), 'error'); + ev.target.checked = !enabled; // Revert + } + }).catch(function(err) { + ui.hideModal(); + ui.addNotification(null, E('p', {}, _('Error: ') + err), 'error'); + ev.target.checked = !enabled; + }); + } + }), + E('span', { 'style': 'font-size: 13px; color: ' + (isEnabled ? '#27ae60' : '#95a5a6') + ';' }, + isEnabled ? _('Enabled') : _('Disabled')) + ]) + ]), + + // Rules Table + catRules.length > 0 ? E('details', { 'style': 'margin-top: 8px;' }, [ + E('summary', { + 'style': 'cursor: pointer; color: var(--kiss-muted); font-size: 13px; padding: 8px 0;' + }, _('Show rules')), + E('table', { 'class': 'table', 'style': 'font-size: 12px; margin-top: 8px;' }, [ + E('tr', { 'class': 'tr cbi-section-table-titles' }, [ + E('th', { 'class': 'th', 'style': 'width: 60px;' }, _('ID')), + E('th', { 'class': 'th' }, _('Pattern')), + E('th', { 'class': 'th', 'style': 'width: 200px;' }, _('Description')), + E('th', { 'class': 'th', 'style': 'width: 100px;' }, _('CVE')) + ]) + ].concat(catRules.map(function(rule, idx) { + // Decode placeholder strings back to original + var pattern = (rule.pattern || '') + .replace(/QUOTE/g, "'") + .replace(/DQUOTE/g, '"') + .replace(/x27/g, "'"); + var desc = rule.desc || rule.description || '-'; + var cve = rule.cve || '-'; + + return E('tr', { 'class': 'tr' }, [ + E('td', { 'class': 'td', 'style': 'font-family: monospace; color: #666;' }, + rule.id || String(idx + 1)), + E('td', { + 'class': 'td', + 'style': 'font-family: monospace; font-size: 11px; max-width: 400px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;', + 'title': pattern + }, pattern), + E('td', { 'class': 'td', 'style': 'color: var(--kiss-muted);' }, desc), + E('td', { 'class': 'td' }, + cve !== '-' ? E('a', { + 'href': 'https://nvd.nist.gov/vuln/detail/' + cve, + 'target': '_blank', + 'style': 'color: #e74c3c; font-size: 11px;' + }, cve) : E('span', { 'style': 'color: #999;' }, '-')) + ]); + }))) + ]) : E('p', { 'style': 'color: var(--kiss-muted); font-size: 12px; margin: 8px 0 0 0;' }, + _('No rules defined')) + ]); + }) : E('div', { 'style': 'text-align: center; padding: 40px; color: var(--kiss-muted);' }, [ + E('div', { 'style': 'font-size: 48px; margin-bottom: 16px;' }, '🛡️'), + E('p', {}, _('No WAF rules loaded')), + E('p', { 'style': 'font-size: 12px;' }, _('WAF rules file not found at /srv/mitmproxy/waf-rules.json')) + ]) + ) + ]), + + // Info Card + E('div', { 'class': 'kiss-card' }, [ + E('div', { 'class': 'kiss-card-title' }, '📖 ' + _('Information')), + E('div', {}, [ + E('p', { 'style': 'color: var(--kiss-muted); margin-bottom: 12px;' }, + _('WAF filters detect and block common web attacks. Each category targets a specific type of threat.')), + E('ul', { 'style': 'color: var(--kiss-muted); margin: 0; padding-left: 20px;' }, [ + E('li', {}, E('strong', {}, 'SQLi'), ' - SQL injection attempts'), + E('li', {}, E('strong', {}, 'XSS'), ' - Cross-site scripting attacks'), + E('li', {}, E('strong', {}, 'LFI'), ' - Local file inclusion'), + E('li', {}, E('strong', {}, 'RCE'), ' - Remote code execution'), + E('li', {}, E('strong', {}, 'CVE'), ' - Known vulnerability exploits'), + E('li', {}, E('strong', {}, 'Scanners'), ' - Automated vulnerability scanners') + ]), + E('p', { 'style': 'margin-top: 12px; color: var(--kiss-muted);' }, [ + _('Rules file: '), + E('code', { 'style': 'font-size: 11px; background: #f0f0f0; padding: 2px 6px; border-radius: 3px;' }, + '/srv/mitmproxy/waf-rules.json') + ]) + ]) + ]) + ]; + + return KissTheme.wrap(content, 'admin/secubox/security/mitmproxy/waf-filters'); + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null +}); diff --git a/package/secubox/luci-app-mitmproxy/root/usr/libexec/rpcd/luci.mitmproxy b/package/secubox/luci-app-mitmproxy/root/usr/libexec/rpcd/luci.mitmproxy index 0420b3e7..fb03ef8d 100755 --- a/package/secubox/luci-app-mitmproxy/root/usr/libexec/rpcd/luci.mitmproxy +++ b/package/secubox/luci-app-mitmproxy/root/usr/libexec/rpcd/luci.mitmproxy @@ -756,8 +756,34 @@ wan_clear() { json_dump } +get_waf_rules() { + local rules_file="/srv/mitmproxy/waf-rules.json" + if [ -f "$rules_file" ]; then + cat "$rules_file" + else + echo "{"error":"WAF rules file not found"}" + fi +} + +toggle_waf_category() { + read -r input + local category=$(jsonfilter -s "$input" -e '@.category' 2>/dev/null) + local enabled=$(jsonfilter -s "$input" -e '@.enabled' 2>/dev/null) + local rules_file="/srv/mitmproxy/waf-rules.json" + if [ -z "$category" ]; then + echo '{"error":"Missing category"}' + return + fi + if [ -f "$rules_file" ] && command -v jq >/dev/null 2>&1; then + cp "$rules_file" "${rules_file}.bak" + jq ".categories.\"$category\".enabled = $enabled" "$rules_file" > "${rules_file}.tmp" && mv "${rules_file}.tmp" "$rules_file" + echo '{"success":true}' + else + echo '{"error":"Cannot update rules"}' + fi +} list_methods() { cat <<'EOFM' -{"status":{},"status_cached":{},"settings":{},"save_settings":{"mode":"str","enabled":"bool","proxy_port":"int","web_port":"int","apply_now":"bool","wan_protection_enabled":"bool","wan_interface":"str"},"set_mode":{"mode":"str","apply_now":"bool"},"setup_firewall":{},"clear_firewall":{},"wan_setup":{},"wan_clear":{},"install":{},"start":{},"stop":{},"restart":{},"alerts":{},"threat_stats":{},"subdomain_metrics":{},"clear_alerts":{},"haproxy_enable":{},"haproxy_disable":{},"sync_routes":{},"bans":{},"unban":{"ip":"str"}} +{"status":{},"status_cached":{},"settings":{},"save_settings":{"mode":"str","enabled":"bool","proxy_port":"int","web_port":"int","apply_now":"bool","wan_protection_enabled":"bool","wan_interface":"str"},"set_mode":{"mode":"str","apply_now":"bool"},"setup_firewall":{},"clear_firewall":{},"wan_setup":{},"wan_clear":{},"install":{},"start":{},"stop":{},"restart":{},"alerts":{},"threat_stats":{},"subdomain_metrics":{},"clear_alerts":{},"haproxy_enable":{},"haproxy_disable":{},"sync_routes":{},"bans":{},"unban":{"ip":"str"},"get_waf_rules":{},"toggle_waf_category":{"category":"str","enabled":"bool"}} EOFM } @@ -787,6 +813,8 @@ case "$1" in sync_routes) sync_routes ;; bans) get_bans ;; unban) unban_ip ;; + get_waf_rules) get_waf_rules ;; + toggle_waf_category) toggle_waf_category ;; *) echo '{"error":"Unknown method"}' ;; esac ;; diff --git a/package/secubox/luci-app-mitmproxy/root/usr/share/luci/menu.d/luci-app-mitmproxy.json b/package/secubox/luci-app-mitmproxy/root/usr/share/luci/menu.d/luci-app-mitmproxy.json index f21ead8d..cc0d3efa 100644 --- a/package/secubox/luci-app-mitmproxy/root/usr/share/luci/menu.d/luci-app-mitmproxy.json +++ b/package/secubox/luci-app-mitmproxy/root/usr/share/luci/menu.d/luci-app-mitmproxy.json @@ -10,9 +10,14 @@ "action": { "type": "view", "path": "mitmproxy/status" }, "order": 1 }, + "admin/secubox/security/mitmproxy/waf-filters": { + "title": "WAF Filters", + "action": { "type": "view", "path": "mitmproxy/waf-filters" }, + "order": 2 + }, "admin/secubox/security/mitmproxy/settings": { "title": "Settings", "action": { "type": "view", "path": "mitmproxy/settings" }, - "order": 2 + "order": 3 } } diff --git a/package/secubox/luci-app-mitmproxy/root/usr/share/rpcd/acl.d/luci-app-mitmproxy.json b/package/secubox/luci-app-mitmproxy/root/usr/share/rpcd/acl.d/luci-app-mitmproxy.json index 68b47f63..471a1839 100644 --- a/package/secubox/luci-app-mitmproxy/root/usr/share/rpcd/acl.d/luci-app-mitmproxy.json +++ b/package/secubox/luci-app-mitmproxy/root/usr/share/rpcd/acl.d/luci-app-mitmproxy.json @@ -3,7 +3,7 @@ "description": "Grant access to mitmproxy", "read": { "ubus": { - "luci.mitmproxy": ["status", "settings", "alerts", "threat_stats", "subdomain_metrics", "bans"] + "luci.mitmproxy": ["status", "settings", "alerts", "threat_stats", "subdomain_metrics", "bans", "get_waf_rules"] }, "uci": ["mitmproxy"] }, @@ -24,7 +24,8 @@ "haproxy_enable", "haproxy_disable", "sync_routes", - "unban" + "unban", + "toggle_waf_category" ] }, "uci": ["mitmproxy"]