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:
163
backend/routes/tcp_spoofing.py
Normal file
163
backend/routes/tcp_spoofing.py
Normal 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))
|
||||
Reference in New Issue
Block a user