Files
toto 039086a0b3 feat: nouvelles techniques de détection et page tactiques SOC
SQL:
- Ajout 5 colonnes d'agrégation (count_xff, count_unusual_ct,
  count_non_std_port, count_login_post, sec_ch_mobile_mismatch)
- Exposition de 5 features calculées dans view_ai_features_1h
- Migration ALTER TABLE pour déploiements existants

Bot-detector:
- 7 nouvelles features ML (has_xff, unusual_content_type_ratio,
  non_standard_port_ratio, login_post_concentration,
  sec_ch_mobile_mismatch, true_window_size, window_mss_ratio)
- Propagation campaign_id vers ml_all_scores (était toujours -1)
- Escalade campagne : HIGH→CRITICAL si cluster ≥5 membres

Dashboard:
- Page Tactiques SOC : brute-force, rotation JA4, récurrence,
  alertes temps réel — 4 KPIs + 4 panneaux + infobulles doc
- Ajout fmtDate() helper global
- Navigation sidebar mise à jour

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-09 14:29:18 +02:00

200 lines
12 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "base.html" %}
{% block page_title %}Tactiques d'attaque{% endblock %}
{% block content %}
<!-- ═══ Tactiques d'attaque — 4 panneaux de détection spécialisés ═══ -->
<!-- ── KPIs ── -->
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3 mb-4">
<div class="kpi-card"><div class="kpi-label">Brute-force IPs</div><div class="kpi-value text-red-400" id="kpi-bf"></div></div>
<div class="kpi-card"><div class="kpi-label">JA4 rotation IPs</div><div class="kpi-value text-orange-400" id="kpi-rot"></div></div>
<div class="kpi-card"><div class="kpi-label">IPs récurrentes</div><div class="kpi-value text-yellow-400" id="kpi-rec"></div></div>
<div class="kpi-card"><div class="kpi-label">Alertes 24h</div><div class="kpi-value text-purple-400" id="kpi-alerts"></div></div>
</div>
<!-- ── Brute-force / Credential stuffing ── -->
<div class="section-card border-red-600/40 mb-4">
<div class="section-header">
<span class="section-title">
<svg class="w-4 h-4 text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/></svg>
Brute-force / Credential Stuffing
</span>
<div class="relative">
<button class="doc-btn"></button>
<div class="doc-panel right-0 w-72">
<strong>Détection brute-force</strong><br>
IPs envoyant ≥10 requêtes POST/heure sur des endpoints d'authentification.
Signale les tentatives de credential stuffing, brute-force de mots de passe,
ou abus d'API. Données issues de <code>view_form_bruteforce_detected</code>.
</div>
</div>
</div>
<div class="section-body p-0">
<div class="overflow-x-auto">
<table class="data-table">
<thead><tr><th>IP</th><th>Host</th><th class="text-right">POST/h</th><th class="text-right">Paths distincts</th><th>Première vue</th><th>Dernière vue</th></tr></thead>
<tbody id="bf-body"><tr><td colspan="6" class="text-center text-gray-500 py-8">Chargement…</td></tr></tbody>
</table>
</div>
</div>
</div>
<!-- ── JA4 Rotation (évasion) ── -->
<div class="section-card border-orange-600/40 mb-4">
<div class="section-header">
<span class="section-title">
<svg class="w-4 h-4 text-orange-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
Rotation de fingerprint JA4
</span>
<div class="relative">
<button class="doc-btn"></button>
<div class="doc-panel right-0 w-72">
<strong>Détection d'évasion JA4</strong><br>
IPs utilisant ≥2 fingerprints JA4 distincts en 24h. Technique d'évasion
classique : rotation de la configuration TLS pour contourner les blocages
par fingerprint. Données issues de <code>view_host_ip_ja4_rotation</code>.
</div>
</div>
</div>
<div class="section-body p-0">
<div class="overflow-x-auto">
<table class="data-table">
<thead><tr><th>IP</th><th>Host</th><th class="text-right">JA4 distincts</th><th>Fingerprints</th><th class="text-right">Hits</th><th>Fenêtre</th></tr></thead>
<tbody id="rot-body"><tr><td colspan="6" class="text-center text-gray-500 py-8">Chargement…</td></tr></tbody>
</table>
</div>
</div>
</div>
<!-- ── Récurrence (menaces persistantes) ── -->
<div class="section-card border-yellow-600/40 mb-4">
<div class="section-header">
<span class="section-title">
<svg class="w-4 h-4 text-yellow-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
Menaces persistantes (récurrence)
</span>
<div class="relative">
<button class="doc-btn"></button>
<div class="doc-panel right-0 w-72">
<strong>IPs récurrentes</strong><br>
IPs détectées comme anomalies sur plusieurs fenêtres temporelles distinctes.
La récurrence augmente la confiance dans la classification malveillante.
Score agravé par <code>log1p(recurrence) × 0.005</code>.
Données issues de <code>view_ip_recurrence</code>.
</div>
</div>
</div>
<div class="section-body p-0">
<div class="overflow-x-auto">
<table class="data-table">
<thead><tr><th>IP</th><th class="text-right">Récurrence</th><th class="text-right">Pire score</th><th>Pire menace</th><th>Première vue</th><th>Dernière vue</th><th>JA4 top</th><th>Host top</th></tr></thead>
<tbody id="rec-body"><tr><td colspan="8" class="text-center text-gray-500 py-8">Chargement…</td></tr></tbody>
</table>
</div>
</div>
</div>
<!-- ── Alertes temps réel ── -->
<div class="section-card border-purple-600/40 mb-4">
<div class="section-header">
<span class="section-title">
<svg class="w-4 h-4 text-purple-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"/></svg>
Alertes récentes (24h)
</span>
<div class="relative">
<button class="doc-btn"></button>
<div class="doc-panel right-0 w-72">
<strong>Flux d'alertes</strong><br>
Dernières détections CRITICAL, HIGH et KNOWN_BOT sur les 24 dernières heures.
Chaque alerte inclut le score, la raison SHAP, et le lien vers l'investigation IP.
</div>
</div>
</div>
<div class="section-body p-0">
<div class="overflow-x-auto">
<table class="data-table">
<thead><tr><th>Date</th><th>IP</th><th class="text-right">Score</th><th>Menace</th><th>JA4</th><th>Host</th><th>ASN</th><th class="text-right">Hits</th><th>Raison</th></tr></thead>
<tbody id="alert-body"><tr><td colspan="9" class="text-center text-gray-500 py-8">Chargement…</td></tr></tbody>
</table>
</div>
</div>
</div>
<script>
/* ═══════════════════════════════════════════════════════════════════════
* Tactiques — chargement des 4 sources de données
* ═══════════════════════════════════════════════════════════════════════ */
document.addEventListener('DOMContentLoaded', () => {
Promise.all([
fetch('/api/brute-force').then(r => r.json()),
fetch('/api/ja4-rotation').then(r => r.json()),
fetch('/api/recurrence').then(r => r.json()),
fetch('/api/alerts?limit=50').then(r => r.json()),
]).then(([bf, rot, rec, alerts]) => {
renderBruteForce(bf.data || []);
renderRotation(rot.data || []);
renderRecurrence(rec.data || []);
renderAlerts(alerts.alerts || []);
}).catch(err => console.error('Tactics load error:', err));
});
function renderBruteForce(data) {
document.getElementById('kpi-bf').textContent = data.length;
const body = document.getElementById('bf-body');
if (!data.length) { body.innerHTML = '<tr><td colspan="6" class="text-center text-gray-500 py-8">Aucune tentative de brute-force détectée</td></tr>'; return; }
body.innerHTML = data.map(r => `<tr class="cursor-pointer hover:bg-gray-800/60" onclick="location.href='/ip/${r.src_ip}'">
<td>${fmtIP(r.src_ip)}</td><td class="text-gray-300">${escapeHtml(r.host||'')}</td>
<td class="text-right font-mono text-red-400 font-bold">${r.post_count}</td>
<td class="text-right">${r.distinct_paths}</td>
<td class="text-gray-400 text-xs">${fmtDate(r.first_seen)}</td>
<td class="text-gray-400 text-xs">${fmtDate(r.last_seen)}</td></tr>`).join('');
}
function renderRotation(data) {
document.getElementById('kpi-rot').textContent = data.length;
const body = document.getElementById('rot-body');
if (!data.length) { body.innerHTML = '<tr><td colspan="6" class="text-center text-gray-500 py-8">Aucune rotation JA4 détectée</td></tr>'; return; }
body.innerHTML = data.map(r => {
const ja4s = (r.ja4_list || []).map(j => fmtJA4(j)).join(', ');
return `<tr class="cursor-pointer hover:bg-gray-800/60" onclick="location.href='/ip/${r.src_ip}'">
<td>${fmtIP(r.src_ip)}</td><td class="text-gray-300">${escapeHtml(r.host||'')}</td>
<td class="text-right font-mono text-orange-400 font-bold">${r.distinct_ja4}</td>
<td class="max-w-xs truncate">${ja4s}</td>
<td class="text-right">${r.total_hits}</td>
<td class="text-gray-400 text-xs">${fmtDate(r.window_start)}</td></tr>`;
}).join('');
}
function renderRecurrence(data) {
document.getElementById('kpi-rec').textContent = data.length;
const body = document.getElementById('rec-body');
if (!data.length) { body.innerHTML = '<tr><td colspan="8" class="text-center text-gray-500 py-8">Aucune IP récurrente</td></tr>'; return; }
body.innerHTML = data.map(r => `<tr class="cursor-pointer hover:bg-gray-800/60" onclick="location.href='/ip/${r.src_ip}'">
<td>${fmtIP(r.src_ip)}</td>
<td class="text-right font-mono text-yellow-400 font-bold">${r.recurrence}×</td>
<td class="text-right font-mono">${fmtScore(r.worst_score)}</td>
<td>${threatBadge(r.worst_threat||r.worst_threat_level||'')}</td>
<td class="text-gray-400 text-xs">${fmtDate(r.first_seen)}</td>
<td class="text-gray-400 text-xs">${fmtDate(r.last_seen)}</td>
<td>${r.top_ja4 ? fmtJA4(r.top_ja4) : ''}</td>
<td class="text-gray-300">${escapeHtml(r.top_host||'')}</td></tr>`).join('');
}
function renderAlerts(data) {
document.getElementById('kpi-alerts').textContent = data.length;
const body = document.getElementById('alert-body');
if (!data.length) { body.innerHTML = '<tr><td colspan="9" class="text-center text-gray-500 py-8">Aucune alerte récente</td></tr>'; return; }
body.innerHTML = data.map(r => `<tr class="cursor-pointer hover:bg-gray-800/60" onclick="location.href='/ip/${r.src_ip}'">
<td class="text-gray-400 text-xs whitespace-nowrap">${fmtDate(r.detected_at)}</td>
<td>${fmtIP(r.src_ip)}</td>
<td class="text-right font-mono">${fmtScore(r.anomaly_score)}</td>
<td>${threatBadge(r.threat_level)}</td>
<td>${fmtJA4(r.ja4)}</td>
<td class="text-gray-300">${escapeHtml(r.host||'')}</td>
<td class="text-gray-400 text-xs">${escapeHtml(r.asn_org||'')}</td>
<td class="text-right">${r.hits||''}</td>
<td class="text-gray-400 text-xs max-w-[200px] truncate" title="${escapeHtml(r.reason||'')}">${escapeHtml((r.reason||'').substring(0,60))}</td></tr>`).join('');
}
</script>
{% endblock %}