Compare commits

..

No commits in common. "323363e7015ad20b570462dfc8914cab5833b559" and "dde96f212ebbc399a899a0f8651a966fe2fb918c" have entirely different histories.

3 changed files with 20 additions and 47 deletions

View File

@ -33,12 +33,7 @@ const (
upExfilBytes = 5 << 20 // >=5 MB outbound to a cloud → volume alert upExfilBytes = 5 << 20 // >=5 MB outbound to a cloud → volume alert
beaconMinFlows = 6 // >=6 flows same dst → candidate beacon beaconMinFlows = 6 // >=6 flows same dst → candidate beacon
beaconCVMax = 0.25 // iat coefficient-of-variation <= 0.25 → periodic beaconCVMax = 0.25 // iat coefficient-of-variation <= 0.25 → periodic
// #692 — period band (ndpiReader iat is in ms). Real C2 beacons sit at topN = 12
// seconds-to-minutes; sub-second cadence is app polling / media chunks /
// websocket keepalives, not exfil. Only flag a steady period in this band.
beaconMinIntervalMs = 1000.0 // >=1 s between flows
beaconMaxIntervalMs = 3600000.0 // <=1 h between flows
topN = 12
) )
var ( var (
@ -304,16 +299,13 @@ func main() {
alerts = append(alerts, base("new_cloud", "première sortie vers "+label)) alerts = append(alerts, base("new_cloud", "première sortie vers "+label))
} }
} }
// 3) beaconing: many flows, low inter-arrival variance, at a C2-plausible // 3) beaconing: many flows, low inter-arrival variance
// cadence (1 s1 h), to an external dest that is exfil-relevant or if a.Flows >= beaconMinFlows {
// unclassified. Excludes sub-second app chatter and periodic fetches
// to known media/game/social CDNs (#692).
if a.Flows >= beaconMinFlows && a.external && (exfilDest || a.Category == "") {
avg := a.iatAvg / float64(a.Flows) avg := a.iatAvg / float64(a.Flows)
std := a.iatStd / float64(a.Flows) std := a.iatStd / float64(a.Flows)
if avg >= beaconMinIntervalMs && avg <= beaconMaxIntervalMs && std/avg <= beaconCVMax { if avg > 0 && std/avg <= beaconCVMax {
alerts = append(alerts, base("beaconing", alerts = append(alerts, base("beaconing",
fmt.Sprintf("%d flux périodiques (~%.1f s)", a.Flows, avg/1000))) fmt.Sprintf("%d flux périodiques (~%.0f ms)", a.Flows, avg)))
} }
} }
// 4) unclassified flow to an external host with notable upload // 4) unclassified flow to an external host with notable upload

View File

@ -1,15 +1,3 @@
secubox-dpi (1.1.1-1~bookworm1) bookworm; urgency=low
* #692 collector: beaconing scenario now requires a C2-plausible cadence
(1 s1 h mean interval) to an external exfil-relevant/unclassified dest —
drops sub-second app/media chatter that previously raised false positives
(e.g. "~39 ms" alerts). Detail now reads in seconds.
* #693 dashboard: headline stat cards repointed to the real R3 exfil engine
(R3 devices / captured flows / categories / exfil alerts) instead of the
legacy netifyd widgets (netifyd is not used on the R3 boards).
-- Gerald KERMA <devel@cybermind.fr> Mon, 22 Jun 2026 10:15:00 +0000
secubox-dpi (1.1.0-1~bookworm1) bookworm; urgency=low secubox-dpi (1.1.0-1~bookworm1) bookworm; urgency=low
* #687 Phase 2/3: ship the per-device R3 cloud-exfiltration pipeline as a * #687 Phase 2/3: ship the per-device R3 cloud-exfiltration pipeline as a

View File

@ -327,23 +327,22 @@
</div> </div>
</div> </div>
<!-- #693 headline metrics driven by the R3 exfil engine (/exfil) -->
<div class="stats-row"> <div class="stats-row">
<div class="stat-card yellow">
<div class="value" id="exfil-devices-count">0</div>
<div class="label">📟 R3 Devices</div>
</div>
<div class="stat-card cyan"> <div class="stat-card cyan">
<div class="value" id="exfil-flows-count">0</div> <div class="value" id="flow-count">0</div>
<div class="label">🌐 Flows (60s)</div> <div class="label">Active Flows</div>
</div> </div>
<div class="stat-card green"> <div class="stat-card green">
<div class="value" id="exfil-cats-count">0</div> <div class="value" id="apps-count">0</div>
<div class="label">🏷️ Categories</div> <div class="label">Applications</div>
</div> </div>
<div class="stat-card red"> <div class="stat-card purple">
<div class="value" id="exfil-alert-count">0</div> <div class="value" id="protocols-count">0</div>
<div class="label">🛰️ Exfil Alerts</div> <div class="label">Protocols</div>
</div>
<div class="stat-card yellow">
<div class="value" id="devices-count">0</div>
<div class="label">Devices</div>
</div> </div>
</div> </div>
@ -447,6 +446,7 @@
const data = await api('/top_apps?limit=10'); const data = await api('/top_apps?limit=10');
const container = document.getElementById('top-apps'); const container = document.getElementById('top-apps');
const apps = Array.isArray(data) ? data : []; const apps = Array.isArray(data) ? data : [];
document.getElementById('apps-count').textContent = apps.length;
container.innerHTML = apps.length > 0 ? apps.map(app => ` container.innerHTML = apps.length > 0 ? apps.map(app => `
<div class="app-item"> <div class="app-item">
@ -460,6 +460,7 @@
const data = await api('/top_protocols?limit=10'); const data = await api('/top_protocols?limit=10');
const container = document.getElementById('top-protocols'); const container = document.getElementById('top-protocols');
const protocols = Array.isArray(data) ? data : []; const protocols = Array.isArray(data) ? data : [];
document.getElementById('protocols-count').textContent = protocols.length;
container.innerHTML = protocols.length > 0 ? protocols.map(proto => ` container.innerHTML = protocols.length > 0 ? protocols.map(proto => `
<div class="app-item"> <div class="app-item">
@ -473,6 +474,7 @@
const data = await api('/bandwidth_by_device'); const data = await api('/bandwidth_by_device');
const container = document.getElementById('bandwidth-devices'); const container = document.getElementById('bandwidth-devices');
const devices = Array.isArray(data) ? data : []; const devices = Array.isArray(data) ? data : [];
document.getElementById('devices-count').textContent = devices.length;
container.innerHTML = devices.length > 0 ? devices.map(dev => ` container.innerHTML = devices.length > 0 ? devices.map(dev => `
<div class="app-item"> <div class="app-item">
@ -486,6 +488,7 @@
const data = await api('/active_flows'); const data = await api('/active_flows');
const container = document.getElementById('active-flows'); const container = document.getElementById('active-flows');
const flows = data.flows || []; const flows = data.flows || [];
document.getElementById('flow-count').textContent = flows.length;
container.innerHTML = flows.length > 0 ? flows.slice(0, 15).map(f => ` container.innerHTML = flows.length > 0 ? flows.slice(0, 15).map(f => `
<div class="app-item"> <div class="app-item">
@ -553,16 +556,6 @@
badge.className = 'badge ' + (count > 0 ? 'badge-red' : 'badge-green'); badge.className = 'badge ' + (count > 0 ? 'badge-red' : 'badge-green');
card.style.borderColor = count > 0 ? '#ff4466' : 'var(--p31-decay)'; card.style.borderColor = count > 0 ? '#ff4466' : 'var(--p31-decay)';
// #693 headline stat cards from the real DPI engine
const totalFlows = devices.reduce((n, d) => n + (d.flows || 0), 0);
const cats = new Set();
devices.forEach(d => Object.keys(d.by_category || {}).forEach(c => cats.add(c)));
const setCard = (id, v) => { const el = document.getElementById(id); if (el) el.textContent = v; };
setCard('exfil-devices-count', devices.length);
setCard('exfil-flows-count', totalFlows);
setCard('exfil-cats-count', cats.size);
setCard('exfil-alert-count', count);
// alert feed (severity-first) // alert feed (severity-first)
alertsBox.innerHTML = alerts.length ? alerts.map(a => { alertsBox.innerHTML = alerts.length ? alerts.map(a => {
const k = EXFIL_KINDS[a.kind] || { label: (a.kind || '?').toUpperCase(), cls: 'badge-amber', icon: '⚠️' }; const k = EXFIL_KINDS[a.kind] || { label: (a.kind || '?').toUpperCase(), cls: 'badge-amber', icon: '⚠️' };