Add detection patterns for latest actively exploited vulnerabilities: - CVE-2025-55182 (React2Shell, CVSS 10.0) - CVE-2025-8110 (Gogs RCE), CVE-2025-53770 (SharePoint) - CVE-2025-52691 (SmarterMail), CVE-2025-40551 (SolarWinds) - CVE-2024-47575 (FortiManager), CVE-2024-21887 (Ivanti) - CVE-2024-3400, CVE-2024-0012, CVE-2024-9474 (PAN-OS) New attack categories based on OWASP Top 10 2025: - HTTP Request Smuggling (TE.CL/CL.TE conflicts) - AI/LLM Prompt Injection (ChatML, instruction markers) - WAF Bypass techniques (Unicode normalization, double encoding) - Supply Chain attacks (CI/CD poisoning, dependency confusion) - Extended SSTI (Jinja2, Freemarker, Velocity, Thymeleaf) - API Abuse (BOLA/IDOR, mass assignment) CrowdSec scenarios split into 11 separate files for reliability. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
181 lines
5.4 KiB
JavaScript
181 lines
5.4 KiB
JavaScript
'use strict';
|
|
'require view';
|
|
'require dom';
|
|
'require poll';
|
|
'require rpc';
|
|
'require ui';
|
|
'require secubox/kiss-theme';
|
|
|
|
var callGetClients = rpc.declare({
|
|
object: 'luci.client-guardian',
|
|
method: 'clients',
|
|
expect: { clients: [] }
|
|
});
|
|
|
|
var callApproveClient = rpc.declare({
|
|
object: 'luci.client-guardian',
|
|
method: 'approve_client',
|
|
params: ['mac', 'name', 'zone', 'notes']
|
|
});
|
|
|
|
var callBanClient = rpc.declare({
|
|
object: 'luci.client-guardian',
|
|
method: 'ban_client',
|
|
params: ['mac', 'reason']
|
|
});
|
|
|
|
var callUnbanClient = rpc.declare({
|
|
object: 'luci.client-guardian',
|
|
method: 'unban_client',
|
|
params: ['mac']
|
|
});
|
|
|
|
return view.extend({
|
|
load: function() {
|
|
return callGetClients();
|
|
},
|
|
|
|
render: function(data) {
|
|
var clients = Array.isArray(data) ? data : (data.clients || []);
|
|
var online = clients.filter(function(c) { return c.online; }).length;
|
|
var approved = clients.filter(function(c) { return c.status === 'approved'; }).length;
|
|
var banned = clients.filter(function(c) { return c.status === 'banned'; }).length;
|
|
|
|
var content = [
|
|
E('h2', { 'style': 'margin-bottom: 8px;' }, 'Client Guardian'),
|
|
E('div', { 'style': 'color: var(--kiss-muted); margin-bottom: 24px;' }, 'Network client management'),
|
|
|
|
// Stats
|
|
E('div', { 'class': 'kiss-grid kiss-grid-3', 'style': 'margin-bottom: 24px;' }, [
|
|
E('div', { 'class': 'kiss-stat' }, [
|
|
E('div', { 'class': 'kiss-stat-value', 'style': 'color: var(--kiss-green);' }, String(online)),
|
|
E('div', { 'class': 'kiss-stat-label' }, 'Online')
|
|
]),
|
|
E('div', { 'class': 'kiss-stat' }, [
|
|
E('div', { 'class': 'kiss-stat-value', 'style': 'color: var(--kiss-blue);' }, String(approved)),
|
|
E('div', { 'class': 'kiss-stat-label' }, 'Approved')
|
|
]),
|
|
E('div', { 'class': 'kiss-stat' }, [
|
|
E('div', { 'class': 'kiss-stat-value', 'style': 'color: var(--kiss-red);' }, String(banned)),
|
|
E('div', { 'class': 'kiss-stat-label' }, 'Banned')
|
|
])
|
|
]),
|
|
|
|
// Client Table
|
|
E('div', { 'class': 'kiss-card' }, [
|
|
E('div', { 'class': 'kiss-card-title' }, 'Clients'),
|
|
E('table', { 'class': 'kiss-table', 'id': 'client-table' }, [
|
|
E('tr', {}, [
|
|
E('th', {}, 'Status'),
|
|
E('th', {}, 'Name'),
|
|
E('th', {}, 'MAC'),
|
|
E('th', {}, 'IP'),
|
|
E('th', {}, 'Actions')
|
|
])
|
|
].concat(clients.map(L.bind(this.renderClientRow, this))))
|
|
])
|
|
];
|
|
|
|
poll.add(L.bind(this.refresh, this), 10);
|
|
return KissTheme.wrap(content, 'client-guardian/overview');
|
|
},
|
|
|
|
renderClientRow: function(client) {
|
|
var statusBadge;
|
|
var rowStyle = '';
|
|
if (client.status === 'banned') {
|
|
statusBadge = E('span', { 'class': 'kiss-badge kiss-badge-red' }, 'BANNED');
|
|
rowStyle = 'background: rgba(255,23,68,0.05);';
|
|
} else if (client.online) {
|
|
statusBadge = E('span', { 'class': 'kiss-badge kiss-badge-green' }, 'ONLINE');
|
|
} else {
|
|
statusBadge = E('span', { 'class': 'kiss-badge kiss-badge-yellow' }, 'OFFLINE');
|
|
}
|
|
|
|
return E('tr', { 'style': rowStyle, 'data-mac': client.mac }, [
|
|
E('td', {}, statusBadge),
|
|
E('td', {}, client.name || client.hostname || '-'),
|
|
E('td', { 'style': 'font-family: monospace;' }, client.mac),
|
|
E('td', {}, client.ip || '-'),
|
|
E('td', {}, this.renderActions(client))
|
|
]);
|
|
},
|
|
|
|
renderActions: function(client) {
|
|
var actions = E('div', { 'style': 'display: flex; gap: 8px;' });
|
|
|
|
if (client.status !== 'approved') {
|
|
var approveBtn = E('button', {
|
|
'class': 'kiss-btn kiss-btn-green',
|
|
'style': 'padding: 4px 12px; font-size: 12px;',
|
|
'data-mac': client.mac
|
|
}, 'Approve');
|
|
approveBtn.addEventListener('click', L.bind(this.handleApprove, this));
|
|
actions.appendChild(approveBtn);
|
|
}
|
|
|
|
if (client.status === 'banned') {
|
|
var unbanBtn = E('button', {
|
|
'class': 'kiss-btn kiss-btn-blue',
|
|
'style': 'padding: 4px 12px; font-size: 12px;',
|
|
'data-mac': client.mac
|
|
}, 'Unban');
|
|
unbanBtn.addEventListener('click', L.bind(this.handleUnban, this));
|
|
actions.appendChild(unbanBtn);
|
|
} else {
|
|
var banBtn = E('button', {
|
|
'class': 'kiss-btn kiss-btn-red',
|
|
'style': 'padding: 4px 12px; font-size: 12px;',
|
|
'data-mac': client.mac
|
|
}, 'Ban');
|
|
banBtn.addEventListener('click', L.bind(this.handleBan, this));
|
|
actions.appendChild(banBtn);
|
|
}
|
|
|
|
return actions;
|
|
},
|
|
|
|
handleApprove: function(ev) {
|
|
var mac = ev.currentTarget.dataset.mac;
|
|
callApproveClient(mac, '', 'lan_private', '').then(L.bind(function() {
|
|
ui.addNotification(null, E('p', 'Client approved'), 'info');
|
|
this.refresh();
|
|
}, this));
|
|
},
|
|
|
|
handleBan: function(ev) {
|
|
var mac = ev.currentTarget.dataset.mac;
|
|
if (confirm('Ban this client?\n' + mac)) {
|
|
callBanClient(mac, 'Manual ban').then(L.bind(function() {
|
|
ui.addNotification(null, E('p', 'Client banned'), 'info');
|
|
this.refresh();
|
|
}, this));
|
|
}
|
|
},
|
|
|
|
handleUnban: function(ev) {
|
|
var mac = ev.currentTarget.dataset.mac;
|
|
callUnbanClient(mac).then(L.bind(function() {
|
|
ui.addNotification(null, E('p', 'Client unbanned'), 'info');
|
|
this.refresh();
|
|
}, this));
|
|
},
|
|
|
|
refresh: function() {
|
|
return callGetClients().then(L.bind(function(data) {
|
|
var clients = Array.isArray(data) ? data : (data.clients || []);
|
|
var table = document.getElementById('client-table');
|
|
if (table) {
|
|
while (table.rows.length > 1) table.deleteRow(1);
|
|
clients.forEach(L.bind(function(client) {
|
|
table.appendChild(this.renderClientRow(client));
|
|
}, this));
|
|
}
|
|
}, this));
|
|
},
|
|
|
|
handleSaveApply: null,
|
|
handleSave: null,
|
|
handleReset: null
|
|
});
|