Files
ja4-platform/docs/thesis/03_architecture.md
Jacquin Antoine 0e5f94dd0d docs: restructure thesis into chapter files with corrected references
Split monolithic thesis into separate chapter markdown files under
docs/thesis/. Remove fabricated bibliography entries, correct inflated
claims, add GNN/Transformers section, and rename MetaLearner to Fusion LR.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 13:51:38 +02:00

25 KiB
Raw Blame History

<< Sommaire | Suivant >>


3. Architecture de détection multi-couches

3.1 Vue d'ensemble du pipeline

┌──────────────────────────────────────────────────────────────────┐
│                    SOURCES DE DONNÉES                             │
├───────────────────────────┬──────────────────────────────────────┤
│  TC ingress (XDP/TC)      │  uprobe SSL_read                     │
│  Couches L3/L4/L5         │  Couche L7 HTTP déchiffré            │
│                           │                                      │
│                           │                                      │
│  réseau XDP/TC →          │  Go Magic Bytes dispatcher →         │
│  - SYN : TTL, IP-ID, DF,  │  HTTP/1.1 : method, path, query,    │
│    MSS, Window, Scale     │    headers (bruts + ordre),          │
│  - TLS ClientHello :      │    status, taille, durée_ms,         │
│    JA4, ALPN, SNI         │    timestamp_ns (nanoseconde)        │
│  - HTTP port 80/8080      │  HTTP/2 (depuis preface client) :    │
│                           │    ordre pseudo-headers              │
│                           │    WINDOW_UPDATE, PRIORITY flag,     │
│                           │    SETTINGS (7 params individuels),  │
│                           │    WINDOW_UPDATE, PRIORITY flag,     │
│                           │    ordre pseudo-headers              │
└───────────────────────────┴──────────────────────────────────────┘
              │                           │
              └─────────────┬─────────────┘
                            │
                  ┌─────────▼─────────┐
                  │  Corrélation in-memory  │
                  │  (ja4ebpf)              │
                  │  Clé: src_ip:port │
                  │  Keep-Alive multi-│
                  │  request tracking │
                  │  Timeout orphelin │
                  │  500ms            │
                  │  L3/L4 ou L7 seul │
                  └─────────┬─────────┘
                            │
                  ┌─────────▼─────────────────────────────┐
                  │          ClickHouse                    │
                  │                                        │
                  │  ja4_logs (brut, TTL 2h)               │
                  │  ┌─────────────────────────────────┐  │
                  │  │ 13 fichiers SQL (00→12)         │  │
                  │  │ AggregatingMergeTree views       │  │
                  │  │ view_ai_features_1h              │  │
                  │  │ view_thesis_features_1h          │  │
                  │  │ agg_path_sequences_1h            │  │
                  │  │ agg_request_timing_1h            │  │
                  │  │ agg_resource_cascade_1h          │  │
                  │  └─────────────────────────────────┘  │
                  │  ja4_processing (agrégé, TTL 7j)       │
                  └─────────┬─────────────────────────────┘
                            │
                  ┌─────────▼─────────────────────────────┐
                  │         bot_detector                   │
                  │         (16 modules Python,            │
                  │          4 800 lignes)                   │
                  │                                        │
                  │  Cycle 300s:                           │
                  │  ┌──────────────────────────────────┐  │
                  │  │ 1. Chargement features ClickHouse│  │
                  │  │ 2. dict lookups (IP, JA4, ASN)   │  │
                  │  │ 3. browser_matcher scoring        │  │
                  │  │ 3b. dynamic H2 profiling scoring │  │
                  │  │ 4. EIF bifurqué (complet/appli)  │  │
                  │  │ 5. AE reconstruction scoring      │  │
                  │  │ 6. XGBoost probabilité           │  │
                  │  │ 7. Fusion LR fusion             │  │
                  │  │ 8. HDBSCAN clustering (AE latent) │  │
                  │  │ 9. Écriture résultats ClickHouse  │  │
                  │  └──────────────────────────────────┘  │
                  └─────────┬─────────────────────────────┘
                            │
                  ┌─────────▼─────────────────────────────┐
                  │         Dashboard                      │
                  │         (16 pages, 37 routes API)      │
                  │                                        │
                  │  - Carte IP mondiale temps réel        │
                  │  - Heatmaps temporelles                │
                  │  - Top bots par famille                │
                  │  - SHAP / ExIFFI feature importance    │
                  │  - HDBSCAN cluster viewer              │
                  │  - Graphes de flottes NetworkX         │
                  └────────────────────────────────────────┘

Composants clés :

uprobe eBPF : mécanisme du noyau Linux permettant d'attacher dynamiquement une sonde eBPF à n'importe quelle fonction en espace utilisateur (par exemple SSL_read d'OpenSSL/BoringSSL), en utilisant l'infrastructure de breakpoints du noyau. L'uprobe intercepte l'appel à l'entrée ou à la sortie de la fonction cible, copie ses arguments ou sa valeur de retour dans un ring buffer eBPF, et rend le contrôle immédiatement — sans modifier le code source du processus cible, sans le mettre en pause, et sans nécessiter de recompilation. Dans ja4ebpf, l'uprobe sur SSL_read capture le buffer de données déchiffrées avant qu'il soit consommé par le serveur web (Apache, Nginx, Varnish, HAProxy), rendant l'interception totalement transparente côté applicatif.

AggregatingMergeTree (ClickHouse) : moteur de table ClickHouse spécialisé pour l'agrégation incrémentale. Contrairement à un merge ordinaire (qui fusionne des lignes brutes), il fusionne des états d'agrégation partiels (AggregateFunction columns) — permettant à SUM, AVG, uniqCombined, etc. d'être calculés correctement même si les données arrivent dans le désordre. Cela permet des vues matérialisées en temps réel à faible latence : chaque cycle d'insertion met à jour l'état partiel, et la vue finale est calculée à la demande en fusionnant tous les états partiels.

3.2 Couche L3 — IP et paquets

La couche L3 (couche réseau IP) fournit trois catégories de signaux :

IP ID (Identification) : champ 16 bits dans l'en-tête IP servant à l'identification unique des fragments IP. Valeur 0 (ou toujours identique) indique une pile TCP sans état ou une implémentation qui génère des paquets SYN sans identifiant séquentiel. Feature ip_id_zero_ratio : ratio de connexions avec IP ID = 0 dans la fenêtre de 300 secondes. Les scanners comme Masscan génèrent systématiquement IP ID = 0.

TTL (Time to Live) : champ 8 bits décrémenté par chaque routeur traversé. Quand TTL atteint 0, le paquet est rejeté et un message ICMP TTL Exceeded est envoyé à la source. Valeurs initiales par OS : Linux = 64, Windows = 128, iOS/macOS = 64. Après K sauts, TTL_observé = TTL_initial - K.

Features : avg_ttl (TTL moyen sur la fenêtre), ttl_std (écart-type du TTL — un TTL constant indique une topologie fixe ou une manipulation). Un TTL_observé divisible par 64 ou 128 selon le chemin réseau attendu valide l'OS déclaré ; une incohérence signale un VPN ou un tunnel qui modifie le TTL.

DF bit (Don't Fragment) : bit dans l'en-tête IP interdisant la fragmentation. Quand DF=1 et que le paquet est trop grand pour un lien, les routeurs retournent ICMP Fragmentation Needed. Linux utilise typiquement DF=1 (Path MTU Discovery actif). Certaines piles VPN utilisent DF=0. Feature ip_df_variance : variance du bit DF sur une session (instabilité anormale = stack réseau inhabituel).

3.3 Couche L4 — TCP

TCP Keep-Alive et multiplexage HTTP : à ne pas confondre avec HTTP Keep-Alive (Connection: keep-alive). Le TCP Keep-Alive est un mécanisme de détection de connexions mortes au niveau du noyau (envoi de paquets ACK vides après inactivité). L'HTTP Keep-Alive, en revanche, maintient la connexion TCP ouverte pour réutilisation par plusieurs requêtes HTTP successives. ja4ebpf trace le nombre de requêtes HTTP dans chaque connexion TCP via max_keepalives dans le gestionnaire de corrélation in-memory.

Coefficient de variation (CV) : mesure adimensionnelle de variabilité, CV = σ/μ. Un CV ≈ 0 indique une régularité élevée (automatisation à timer fixe) ; un CV ≈ 13 indique une variabilité naturelle (humain). Applicable à syn_timing_cv (variabilité du délai SYN→ClientHello) et à cadence_cv (variabilité des intervalles inter-requêtes).

Features TCP :

Feature Définition Signal
tcp_jitter_variance Variance des délais inter-connexions Faible = automatisé à cadence fixe
syn_timing_cv CV du délai SYN→TLS ClientHello < 0.1 = script, > 0.5 = humain
no_window_scale_ratio Ratio connexions sans Window Scale option > 0.5 = stack TCP minimal ou obsolète
tcp_shared_count Requêtes HTTP par connexion TCP (Keep-Alive) 1 = bot sans Keep-Alive, >100 = bot pipeline
port_exhaustion_ratio Ratio de ports source proches de l'épuisement (> 60000) Injection rapide depuis un hôte unique
src_port_density Densité des ports source (plage utilisée / connexions) Faible = réutilisation systématique
true_window_size Window Size × 2^window_scale Fingerprint OS combiné
window_mss_ratio true_window_size / MSS Ratio diagnostique : écart = configuration non-standard

3.4 Couche L5 — TLS

Features TLS agrégées par session :

Feature Définition Valeur humain Valeur bot
tls12_ratio Ratio connexions TLS 1.2 / total TLS ≈ 0 (Chrome/Firefox ≥1.3 exclusivement) Variable selon outil
fingerprint_coherence_score Cohérence JA4 à travers les connexions ≈ 1 (même navigateur = même JA4) < 0.5 si rotation JA4
is_alpn_missing 1 si ALPN absent dans ClientHello Toujours 0 pour navigateurs 1 pour stacks TLS minimalistes
sni_host_mismatch 1 si SNI ≠ en-tête HTTP Host 0 > 0 si domain fronting ou proxy mal configuré
distinct_ja4_count Nombre de JA4 distincts dans la session 1 (un seul navigateur) > 3 si rotation de pile TLS
ja4_drift_ratio Transitions du JA4 dominant / (segments-1) 0 > 0.5 si changement intentionnel
ja3_diversity_ratio Distinct JA3 / distinct JA4 ≈ 1 > 5 si randomisation GREASE anti-JA3

3.5 Couche L7 — HTTP

La couche L7 constitue la couche la plus riche en features comportementales. L'agent ja4ebpf capture le flux HTTP déchiffré via un uprobe sur SSL_read (OpenSSL/BoringSSL), avec une précision nanoseconde via bpf_ktime_get_ns().

Données capturées par ja4ebpf : src_ip, src_port, timestamp_ns (nanoseconde absolu), method, path, query_string, http_version, headers_raw (en-têtes bruts dans leur ordre d'émission), header_order_signature (hash de l'ordre), status_code, response_size, duration_ms. L'horodatage nanoseconde est critique pour le calcul des features temporelles F8 (cadence_cv, lag1_autocorrelation, benford_deviation, root_to_first_asset_delay).

Fingerprinting HTTP/2 passif intégré : pour les connexions HTTP/2, l'uprobe sur SSL_read retourne un flux d'octets bruts déchiffrés qui ne respecte pas les frontières des trames HTTP/2. En raison de la fragmentation TCP et du buffering TLS, un seul appel SSL_read peut contenir un fragment partiel de trame, plusieurs trames complètes, ou une combinaison des deux. Le Go Magic Bytes dispatcher maintient donc un buffer circulaire de réassemblage par connexion (identifiée par src_ip:src_port) : il accumule les données de plusieurs appels SSL_read successifs jusqu'à ce que la logique de parsing HTTP/2 confirme qu'une trame complète est disponible (9 octets d'en-tête + longueur payload déclarée). L'identification du protocole HTTP/2 se fait en cherchant la connection preface PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n dans le buffer accumulé. Tout le parsing lourd — décodage des trames, HPACK pour les pseudo-headers — s'effectue dans l'agent Go en espace utilisateur, et non dans la VM eBPF (dont la complexité est limitée par le vérificateur noyau). Ce découplage est essentiel : la VM eBPF collecte les octets bruts le plus rapidement possible, et le parsing est réalisé de manière asynchrone côté Go sans jamais interrompre le flux applicatif du serveur web.

Une fois les trames complètes reconstituées, le parser Go en extrait les éléments suivants du preface client :

  • Les 7 paramètres SETTINGS individuels (IDs 16 et 8), chacun stocké dans une colonne ClickHouse dédiée (h2_header_table_size, h2_enable_push, h2_max_concurrent_streams, h2_initial_window_size, h2_max_frame_size, h2_max_header_list_size, h2_enable_connect_protocol), avec la valeur -1 pour les paramètres absents du preface client
  • L'incrément h2_window_update de la frame WINDOW_UPDATE sur la connexion (stream ID 0)
  • Le flag h2_has_priority indiquant la présence d'un champ PRIORITY dans la frame HEADERS
  • L'ordre des pseudo-headers h2_pseudo_order (ex. m,a,s,p) extrait par décodage HPACK partiel de la première frame HEADERS
  • Le fingerprint composite h2_fingerprint au format Akamai et la chaîne brute h2_settings_fp

Les données H2 sont associées à la session courante via la clé src_ip:src_port dans le gestionnaire de corrélation in-memory de ja4ebpf, puis transmises en batch à ClickHouse avec les données TCP/TLS correspondantes.

Colonnes HTTP/2 dans ja4_logs.http_logs :

Colonne ClickHouse Type Défaut Description
h2_fingerprint String '' Fingerprint composite au format Akamai (ex. 1:65536,2:0,4:6291456,6:262144|15663105|0|m,a,s,p)
h2_settings_fp String '' Chaîne brute des entrées SETTINGS (ex. 3:100,4:65536,2:0)
h2_header_table_size Int32 -1 SETTINGS ID 1 — HEADER_TABLE_SIZE (octets). -1 = absent du preface
h2_enable_push Int32 -1 SETTINGS ID 2 — ENABLE_PUSH (0/1). -1 = absent
h2_max_concurrent_streams Int32 -1 SETTINGS ID 3 — MAX_CONCURRENT_STREAMS. -1 = absent
h2_initial_window_size Int64 -1 SETTINGS ID 4 — INITIAL_WINDOW_SIZE (octets). -1 = absent
h2_max_frame_size Int32 -1 SETTINGS ID 5 — MAX_FRAME_SIZE (octets). -1 = absent
h2_max_header_list_size Int32 -1 SETTINGS ID 6 — MAX_HEADER_LIST_SIZE (octets). -1 = absent
h2_enable_connect_protocol Int32 -1 SETTINGS ID 8 — ENABLE_CONNECT_PROTOCOL (RFC 8441). -1 = absent
h2_window_update UInt32 0 Incrément WINDOW_UPDATE connexion (stream ID 0). 0 = absent
h2_has_priority UInt8 0 1 si le flag PRIORITY est présent dans la frame HEADERS
h2_pseudo_order String '' Ordre des pseudo-headers (ex. m,a,s,p pour Chrome)

La convention -1 pour les paramètres SETTINGS absents est essentielle : elle distingue un paramètre non envoyé par le client (valeur par défaut RFC implicite) d'un paramètre explicitement fixé à 0 (ex. ENABLE_PUSH = 0 signifie « Server Push désactivé » vs -1 signifie « non envoyé, le serveur utilise la valeur par défaut RFC de 1 »).

Toutes les features des familles F1F6 et F8 proviennent de cette couche, agrégées sur des fenêtres temporelles de 300 secondes (5 minutes) par session (src_ip).

3.6 Corrélation inter-couches (ja4ebpf)

Clé de corrélation : (src_ip, src_port) — le tuple source identifie de manière unique une connexion TCP à un instant donné (une connexion TCP est identifiée par le 4-tuple src_ip:src_port:dst_ip:dst_port, mais dst_ip:dst_port étant fixes pour un serveur, le 2-tuple src suffit).

Gestion du HTTP Keep-Alive : une connexion TCP peut transporter plusieurs requêtes HTTP successives. ja4ebpf maintient un gestionnaire de corrélation in-memory organisé en 256 shards (partitionnement par hash de src_ip pour éviter la contention). Chaque requête HTTP capturée via l'uprobe SSL_read est associée à l'enregistrement TCP/TLS ouvert correspondant, et max_keepalives est incrémenté. Un GC toutes les 100 ms libère les sessions expirées.

Timeout orphelin : si aucun enregistrement réseau (L3/L4) ne peut être associé à une requête HTTP dans les 500 ms — ce qui se produit quand le trafic arrive via un CDN ou un proxy inverse établissant une nouvelle connexion TCP entre le proxy et le serveur — la session est exportée avec uniquement les données HTTP disponibles. Les champs TCP/TLS restent absents dans l'enregistrement ClickHouse, et les features correspondantes sont imputées à zéro dans le pipeline ML.

Conséquences pour le pipeline ML : pour les sessions dont les données TCP/TLS sont absentes (proxy CDN, load balancer avec terminaison TLS), le Modèle Applicatif EIF (≈ 35 features L7 uniquement) est utilisé à la place du Modèle Complet (≈ 45 features L3→L7), évitant le biais d'imputation à zéro des features réseau.

Impact sur HTTP/2 : quand has_xff=1 (en-tête X-Forwarded-For présent, indiquant un proxy CDN), les dimensions H2 du browser_matcher sont neutralisées à 0.5 (score neutre). Le CDN réouvre sa propre connexion HTTP/2 vers le serveur d'origine ; les SETTINGS HTTP/2 capturés sont ceux du CDN (Cloudflare, Akamai), pas du client original.

X-Forwarded-For : en-tête HTTP ajouté par les proxies et load balancers pour préserver l'adresse IP du client original à travers la chaîne de proxies. Format : X-Forwarded-For: client_ip, proxy1_ip, proxy2_ip. Feature has_xff détecte la présence de cet en-tête.

3.7 Agrégation temporelle et features dérivées

Cycle d'analyse : 300 secondes (5 minutes). À chaque cycle, le bot_detector :

  1. Interroge ClickHouse pour les features agrégées des sessions actives dans la dernière heure
  2. Joint view_ai_features_1h (features L3L6, corrélées) avec view_thesis_features_1h (features F8, temporelles)
  3. Exécute le scoring ML
  4. Écrit les résultats dans ja4_processing

Vues matérialisées ClickHouse :

Vue Contenu Features extraites
view_ai_features_1h Features ML principales par session/heure F1F7 corrélées
view_thesis_features_1h Features temporelles avancées F8 (Benford, entropie, autocorrélation)
agg_path_sequences_1h Séquences de chemins visités path_transition_entropy, path_diversity
agg_request_timing_1h Timing inter-requêtes en ms cadence_cv, lag1_autocorrelation, burst_ratio
agg_resource_cascade_1h Cascade de ressources (HTML→CSS→JS→images) root_to_first_asset_delay, asset_load_stddev

TTL strategy :

  • ja4_logs bruts : TTL 2h — les logs bruts sont trop volumineux pour une conservation longue durée. Après 2 heures, les informations utiles ont été agrégées dans les vues.
  • ja4_processing agrégé : TTL 7j — les features agrégées et les résultats de scoring sont conservés 7 jours pour l'analyse de tendances, la corrélation de campagnes, et l'entraînement des modèles.

Métriques de déploiement : > 3 millions de logs ingérés, ≈ 34 000 sessions par cycle de 300 s, ≈ 777 anomalies détectées par cycle (≈ 2,3 %), cycle d'analyse 300 s.

3.8 Détection ML semi-supervisée (full pipeline)

Trifurcation du trafic

Session entrante
        │
        ├── dict_bot_ip CIDR match ?
        │   OU dict_bot_ja4 match ?
        │   OU Anubis DENY ?
        │   ── OUI → KNOWN_BOT (étiquette entraînement XGB)
        │
        ├── browser_matcher score ≥ seuil ?
        │   (Chrome ≥ 0.72, Firefox/Safari ≥ 0.68)
        │   ET aucune signature négative ?
        │   ── OUI → LEGITIMATE_BROWSER
        │
        ├── Zone grise [0.45, seuil[ ?
        │   ── OUI → score_final × (1 - 0.5 × match_score)
        │            (pénalité partielle, pas de blocage)
        │
        ├── asn_label == 'human' ?
        │   ── OUI → baseline EIF training (sans étiquette bot)
        │
        └── Sinon → Triple-voix : EIF + AE + XGBoost + Fusion LR

Seuil adaptatif

threshold = min(percentile_5(neg_scores_history), -0.05)

La valeur percentile_5 du historique des scores négatifs (anomalies confirmées) établit un seuil qui capture au moins le top 5 % des anomalies observées récemment. Le plancher -0.05 garantit une sensibilité minimale même lors de périodes de faible activité botique. Cette adaptation évite les faux négatifs systémiques en période calme et les faux positifs en période d'activité élevée.

Scoring bifurqué

Modèle Features Trafic applicable Indicateur
EIF Complet ≈ 45 features L3→L7 Données L3/L4 disponibles eif_score_full
EIF Applicatif ≈ 35 features L7 L3/L4 absentes (CDN/proxy) eif_score_app
AE Même dimensionnalité que EIF actif Toutes sessions ae_reconstruction_error
XGBoost Ensemble complet 96 features Toutes sessions xgb_probability

Niveaux de sévérité

Étiquette Condition Action recommandée
CRITICAL score_final < -0.30 Blocage immédiat + alerte SOC
HIGH score_final < -0.15 Blocage + journalisation
MEDIUM score_final < -0.05 CAPTCHA challenge + journalisation
LOW score_final < 0 Journalisation + surveillance accrue
KNOWN_BOT Match dict_bot_ip / dict_bot_ja4 / Anubis DENY Blocage + label entraînement
ANUBIS_DENY Règle Anubis DENY explicite Blocage + label entraînement
LEGITIMATE_BROWSER browser_matcher ≥ seuil Passage sans friction

HDBSCAN pour le clustering de campagnes

HDBSCAN (Hierarchical Density-Based Spatial Clustering of Applications with Noise, Campello, Moulavi, Sander, 2013) est un algorithme de clustering hiérarchique basé sur la densité. Il étend DBSCAN (Density-Based Spatial Clustering of Applications with Noise) en :

  1. Calculant la matrice de distances de réachabilité mutuelle (mutual reachability distance) : d_mreach(a,b) = max(core_k(a), core_k(b), d(a,b))core_k(x) est la distance au k-ème voisin le plus proche
  2. Construisant l'arbre couvrant minimal (Minimum Spanning Tree, MST) sur cette matrice
  3. Transformant le MST en une hiérarchie de clusters à tous les niveaux de densité
  4. Extrayant les clusters les plus stables par un score de persistance : stability(C) = Σ_{x ∈ C} (λ_death(x,C) - λ_birth(C))
  5. Assignant les points non intégrés à la classe bruit (-1)

Avantages sur DBSCAN : gère les clusters de densités variables, ne nécessite que min_cluster_size (pas d'ε), robuste au bruit.

Application dans l'architecture : HDBSCAN est appliqué sur l'espace latent AE de 16 dimensions. Les sessions dont la représentation latente est proche correspondent à des bots utilisant le même outil ou la même configuration. Le clustering regroupe ces sessions en campagnes identifiables, permettant de :

  • Détecter les campagnes distribuées sur plusieurs adresses IP
  • Lier des sessions séparées par des gaps temporels (même campagne qui reprend)
  • Fournir des contextes de menace au SOC (« cluster #7 = 34 IPs, JA4 identique, asset_ratio ≈ 0 »)