🛡️ Dashboard complet pour l'analyse et la classification des menaces Fonctionnalités principales: - Visualisation des détections en temps réel (24h) - Investigation multi-entités (IP, JA4, ASN, Host, User-Agent) - Analyse de corrélation pour classification SOC - Clustering automatique par subnet/JA4/UA - Export des classifications pour ML Composants: - Backend: FastAPI (Python) + ClickHouse - Frontend: React + TypeScript + TailwindCSS - 6 routes API: metrics, detections, variability, attributes, analysis, entities - 7 types d'entités investigables Documentation ajoutée: - NAVIGATION_GRAPH.md: Graph complet de navigation - SOC_OPTIMIZATION_PROPOSAL.md: Proposition d'optimisation pour SOC • Réduction de 7 à 2 clics pour classification • Nouvelle vue /incidents clusterisée • Panel latéral d'investigation • Quick Search (Cmd+K) • Timeline interactive • Graph de corrélations Sécurité: - .gitignore configuré (exclut .env, secrets, node_modules) - Credentials dans .env (à ne pas committer) ⚠️ Audit sécurité réalisé - Voir recommandations dans SOC_OPTIMIZATION_PROPOSAL.md Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
143 lines
4.5 KiB
TypeScript
143 lines
4.5 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
|
|
interface JA4SubnetData {
|
|
subnet: string;
|
|
count: number;
|
|
}
|
|
|
|
interface JA4Analysis {
|
|
ja4: string;
|
|
shared_ips_count: number;
|
|
top_subnets: JA4SubnetData[];
|
|
other_ja4_for_ip: string[];
|
|
}
|
|
|
|
interface JA4AnalysisProps {
|
|
ip: string;
|
|
}
|
|
|
|
export function JA4Analysis({ ip }: JA4AnalysisProps) {
|
|
const [data, setData] = useState<JA4Analysis | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
const fetchJA4Analysis = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const response = await fetch(`/api/analysis/${encodeURIComponent(ip)}/ja4`);
|
|
if (!response.ok) throw new Error('Erreur chargement JA4');
|
|
const result = await response.json();
|
|
setData(result);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Erreur inconnue');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
fetchJA4Analysis();
|
|
}, [ip]);
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="bg-background-secondary rounded-lg p-6">
|
|
<div className="text-center text-text-secondary">Chargement...</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (error || !data || !data.ja4) {
|
|
return (
|
|
<div className="bg-background-secondary rounded-lg p-6">
|
|
<div className="text-center text-text-secondary">JA4 non disponible</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="bg-background-secondary rounded-lg p-6">
|
|
<div className="flex items-center justify-between mb-4">
|
|
<h3 className="text-lg font-medium text-text-primary">3. JA4 FINGERPRINT ANALYSIS</h3>
|
|
{data.shared_ips_count > 50 && (
|
|
<span className="bg-threat-high text-white px-3 py-1 rounded text-xs font-medium">
|
|
🔴 {data.shared_ips_count} IPs
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
<div className="space-y-6">
|
|
{/* JA4 Fingerprint */}
|
|
<div>
|
|
<div className="text-sm text-text-secondary mb-2">JA4 Fingerprint</div>
|
|
<div className="bg-background-card rounded-lg p-3 font-mono text-sm text-text-primary break-all">
|
|
{data.ja4}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
{/* IPs avec même JA4 */}
|
|
<div>
|
|
<div className="text-sm text-text-secondary mb-2">
|
|
IPs avec le MÊME JA4 (24h)
|
|
</div>
|
|
<div className="text-3xl font-bold text-text-primary mb-2">
|
|
{data.shared_ips_count}
|
|
</div>
|
|
{data.shared_ips_count > 50 && (
|
|
<div className="text-threat-high text-sm">
|
|
🔴 PATTERN: Même outil/bot sur {data.shared_ips_count} IPs
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Autres JA4 pour cette IP */}
|
|
<div>
|
|
<div className="text-sm text-text-secondary mb-2">
|
|
Autres JA4 pour cette IP
|
|
</div>
|
|
{data.other_ja4_for_ip.length > 0 ? (
|
|
<div className="space-y-1">
|
|
{data.other_ja4_for_ip.slice(0, 3).map((ja4, idx) => (
|
|
<div key={idx} className="bg-background-card rounded p-2 font-mono text-xs text-text-primary truncate">
|
|
{ja4}
|
|
</div>
|
|
))}
|
|
{data.other_ja4_for_ip.length > 3 && (
|
|
<div className="text-text-secondary text-xs">
|
|
+{data.other_ja4_for_ip.length - 3} autres
|
|
</div>
|
|
)}
|
|
</div>
|
|
) : (
|
|
<div className="text-text-secondary text-sm">
|
|
1 seul JA4 → Comportement stable
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Top subnets */}
|
|
{data.top_subnets.length > 0 && (
|
|
<div>
|
|
<div className="text-sm text-text-secondary mb-2">
|
|
Top subnets pour ce JA4
|
|
</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2">
|
|
{data.top_subnets.map((subnet, idx) => (
|
|
<div
|
|
key={idx}
|
|
className="bg-background-card rounded-lg p-3 flex items-center justify-between"
|
|
>
|
|
<div className="font-mono text-sm text-text-primary">{subnet.subnet}</div>
|
|
<div className="text-text-primary font-bold">{subnet.count} IPs</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|