feat: ajout de 7 nouveaux dashboards d'analyse avancée

- 🔥 Brute Force & Credential Stuffing (view_form_bruteforce_detected)
- 🧬 TCP/OS Spoofing (view_tcp_spoofing_detected, 86K détections)
- 📡 Header Fingerprint Clustering (agg_header_fingerprint_1h, 1374 clusters)
- ⏱️ Heatmap Temporelle (agg_host_ip_ja4_1h, pic à 20h)
- 🌍 Botnets Distribués / JA4 spread (view_host_ja4_anomalies)
- 🔄 Rotation JA4 & Persistance (view_host_ip_ja4_rotation + view_ip_recurrence)
- 🤖 Features ML / Radar (view_ai_features_1h, radar SVG + scatter plot)

Backend: 7 nouveaux router FastAPI avec requêtes ClickHouse optimisées
Frontend: 7 nouveaux composants React + navigation 'Analyse Avancée' dans la sidebar
Fixes: alias fuzzing_index → max_fuzzing (ORDER BY ClickHouse), normalisation IPs ::ffff:

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
SOC Analyst
2026-03-15 23:57:27 +01:00
parent 1455e04303
commit e2bc4a47cd
16 changed files with 3499 additions and 1 deletions

View File

@ -0,0 +1,163 @@
"""
Endpoints pour la détection du TCP spoofing (TTL / window size anormaux)
"""
from fastapi import APIRouter, HTTPException, Query
from ..database import db
router = APIRouter(prefix="/api/tcp-spoofing", tags=["tcp_spoofing"])
def _suspected_os(ttl: int) -> str:
if 55 <= ttl <= 65:
return "Linux/Mac"
if 120 <= ttl <= 135:
return "Windows"
if ttl < 50:
return "Behind proxy (depleted)"
return "Unknown"
def _declared_os(ua: str) -> str:
ua = ua or ""
if "Windows" in ua:
return "Windows"
if "Mac OS X" in ua:
return "macOS"
if "Linux" in ua or "Android" in ua:
return "Linux/Android"
return "Unknown"
@router.get("/overview")
async def get_tcp_spoofing_overview():
"""Statistiques globales sur les détections de spoofing TCP."""
try:
sql = """
SELECT
count() AS total_detections,
uniq(src_ip) AS unique_ips,
countIf(tcp_ttl < 60) AS low_ttl_count,
countIf(tcp_ttl = 0) AS zero_ttl_count
FROM mabase_prod.view_tcp_spoofing_detected
"""
result = db.query(sql)
row = result.result_rows[0]
total_detections = int(row[0])
unique_ips = int(row[1])
low_ttl_count = int(row[2])
zero_ttl_count = int(row[3])
ttl_sql = """
SELECT
tcp_ttl,
count() AS cnt,
uniq(src_ip) AS ips
FROM mabase_prod.view_tcp_spoofing_detected
GROUP BY tcp_ttl
ORDER BY cnt DESC
LIMIT 15
"""
ttl_res = db.query(ttl_sql)
ttl_distribution = [
{"ttl": int(r[0]), "count": int(r[1]), "ips": int(r[2])}
for r in ttl_res.result_rows
]
win_sql = """
SELECT
tcp_window_size,
count() AS cnt
FROM mabase_prod.view_tcp_spoofing_detected
GROUP BY tcp_window_size
ORDER BY cnt DESC
LIMIT 10
"""
win_res = db.query(win_sql)
window_size_distribution = [
{"window_size": int(r[0]), "count": int(r[1])}
for r in win_res.result_rows
]
return {
"total_detections": total_detections,
"unique_ips": unique_ips,
"low_ttl_count": low_ttl_count,
"zero_ttl_count": zero_ttl_count,
"ttl_distribution": ttl_distribution,
"window_size_distribution":window_size_distribution,
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/list")
async def get_tcp_spoofing_list(
limit: int = Query(100, ge=1, le=1000),
offset: int = Query(0, ge=0),
):
"""Liste paginée des détections, triée par tcp_ttl ASC."""
try:
count_sql = "SELECT count() FROM mabase_prod.view_tcp_spoofing_detected"
total = int(db.query(count_sql).result_rows[0][0])
sql = """
SELECT
replaceRegexpAll(toString(src_ip), '^::ffff:', '') AS src_ip,
ja4, tcp_ttl, tcp_window_size, first_ua
FROM mabase_prod.view_tcp_spoofing_detected
ORDER BY tcp_ttl ASC
LIMIT %(limit)s OFFSET %(offset)s
"""
result = db.query(sql, {"limit": limit, "offset": offset})
items = []
for row in result.result_rows:
ip = str(row[0])
ja4 = str(row[1])
ttl = int(row[2])
window_size = int(row[3])
ua = str(row[4] or "")
sus_os = _suspected_os(ttl)
dec_os = _declared_os(ua)
spoof_flag = sus_os != dec_os and sus_os != "Unknown" and dec_os != "Unknown"
items.append({
"ip": ip,
"ja4": ja4,
"tcp_ttl": ttl,
"tcp_window_size": window_size,
"first_ua": ua,
"suspected_os": sus_os,
"declared_os": dec_os,
"spoof_flag": spoof_flag,
})
return {"items": items, "total": total}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/matrix")
async def get_tcp_spoofing_matrix():
"""Cross-tab suspected_os × declared_os avec comptage."""
try:
sql = """
SELECT src_ip, tcp_ttl, first_ua
FROM mabase_prod.view_tcp_spoofing_detected
"""
result = db.query(sql)
counts: dict = {}
for row in result.result_rows:
ttl = int(row[1])
ua = str(row[2] or "")
sus_os = _suspected_os(ttl)
dec_os = _declared_os(ua)
key = (sus_os, dec_os)
counts[key] = counts.get(key, 0) + 1
matrix = [
{"suspected_os": k[0], "declared_os": k[1], "count": v}
for k, v in counts.items()
]
matrix.sort(key=lambda x: x["count"], reverse=True)
return {"matrix": matrix}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))