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>
This commit is contained in:
79
docs/thesis/00_resume.md
Normal file
79
docs/thesis/00_resume.md
Normal file
@ -0,0 +1,79 @@
|
||||
[<< Sommaire](README.md) | [Suivant >>](01_introduction.md)
|
||||
|
||||
---
|
||||
|
||||
# Détection et Classification du Trafic HTTP Malveillant : Approche Multi-Couches par Corrélation Passive L3–L7
|
||||
|
||||
**Document technique — Avril 2026**
|
||||
**Version 4.1 — Révision scientifique**
|
||||
|
||||
---
|
||||
|
||||
## Résumé
|
||||
|
||||
Ce document présente une architecture opérationnelle de détection et classification du trafic HTTP malveillant, s'inscrivant dans la continuité des approches de génération 3 (fingerprinting multi-protocole et ML comportemental). Le système exploite 96 features organisées en 8 familles couvrant les couches réseau L3 à L7, corrélant des signaux TCP, TLS et HTTP en un vecteur unifié par session. La détection repose sur un ensemble triple-voix combinant un Extended Isolation Forest (EIF), un autoencodeur (AE) et XGBoost, fusionnés par une régression logistique calibrée activée à partir de 1 000 étiquettes accumulées. L'explicabilité est assurée par l'importance des features par profondeur d'isolation (EIF) et SHAP TreeExplainer (XGBoost). Le clustering de campagnes est réalisé par HDBSCAN dans l'espace latent 16 dimensions de l'autoencodeur, et la détection de flottes coordonnées par graphes bipartis via NetworkX. Le fingerprinting HTTP/2 passif — extraction des trames SETTINGS, WINDOW_UPDATE et de l'ordre des pseudo-headers côté serveur — exploite un signal déjà utilisé par des solutions industrielles (Akamai, Cloudflare, F5), ici implémenté via eBPF. L'infrastructure repose sur 16 modules Python (4 800 lignes), une base ClickHouse à double schéma (ja4_logs bruts TTL 2 h, ja4_processing agrégés TTL 7 j), des cycles d'analyse de 300 secondes, et traite en production plus de 3 millions de logs, environ 34 000 sessions par cycle, avec approximativement 777 anomalies détectées par cycle (≈ 2,3 % — chiffre opérationnel brut, non validé comme taux de détection). Le système intègre un moteur de profiling dynamique automatique des navigateurs (HDBSCAN sur les vecteurs H2 observés, centroïdes auto-appris, scoring temps réel par distance normalisée) qui s'adapte aux évolutions des piles HTTP/2 sans intervention manuelle.
|
||||
|
||||
**Mots-clés** : fingerprinting réseau, JA4+, HTTP/2 fingerprinting, détection de bots, Extended Isolation Forest, autoencodeurs, ensemble hybride, corrélation TCP/TLS/HTTP, WAF, classification de trafic, apprentissage semi-supervisé, clustering HDBSCAN
|
||||
|
||||
---
|
||||
|
||||
## Table des matières
|
||||
|
||||
1. [Introduction](#1-introduction)
|
||||
- 1.1 Contexte et ampleur de la menace
|
||||
- 1.2 Quatre générations de défenses
|
||||
- 1.3 Portée et caractéristiques de ce document
|
||||
|
||||
2. [État de l'art](#2-état-de-lart)
|
||||
- 2.1 Détection par règles statiques
|
||||
- 2.1.1 OWASP Core Rule Set (CRS)
|
||||
- 2.1.2 Listes de réputation IP et ASN
|
||||
- 2.1.3 Projet Anubis (TecharoHQ)
|
||||
- 2.2 Fingerprinting réseau
|
||||
- 2.2.1 TLS Fingerprinting : de JA3 à JA4+
|
||||
- 2.2.2 TCP Fingerprinting
|
||||
- 2.2.3 Fingerprinting TLS avancé
|
||||
- 2.3 Analyse comportementale HTTP
|
||||
- 2.3.1 Signaux d'en-têtes HTTP
|
||||
- 2.3.2 Patterns de navigation
|
||||
- 2.3.3 Brute-force et credential stuffing
|
||||
- 2.4 Apprentissage automatique pour la détection d'intrusions
|
||||
- 2.4.1 Approches supervisées et leurs limites
|
||||
- 2.4.2 Approches semi-supervisées
|
||||
- 2.4.2b Autoencoders (AE) et détection d'anomalies
|
||||
- 2.4.2c Ensembles hybrides supervisé + non-supervisé
|
||||
- 2.4.3 Concept Drift et retraining adaptatif
|
||||
- 2.4.4 Modélisation des phases d'attaque
|
||||
- 2.4.5 Explicabilité par SHAP et ExIFFI
|
||||
- 2.5 Détection côté client (Browser Fingerprinting)
|
||||
- 2.5.1 JavaScript Challenges
|
||||
- 2.5.2 FingerprintJS BotD
|
||||
- 2.5.3 Fingerprinting HTTP/2 passif côté serveur
|
||||
- 2.6 Synthèse des limites de l'état de l'art
|
||||
|
||||
3. [Architecture de détection multi-couches](#3-architecture-de-détection-multi-couches)
|
||||
- 3.1 Vue d'ensemble du pipeline
|
||||
- 3.2 Couche L3 — IP et paquets
|
||||
- 3.3 Couche L4 — TCP
|
||||
- 3.4 Couche L5 — TLS
|
||||
- 3.5 Couche L7 — HTTP
|
||||
- 3.6 Corrélation inter-couches (ja4ebpf)
|
||||
- 3.7 Agrégation temporelle et features dérivées
|
||||
- 3.8 Détection ML semi-supervisée (full pipeline)
|
||||
|
||||
4. [Taxonomie des features de détection](#4-taxonomie-des-features-de-détection)
|
||||
- Famille 1 : Volumétrie et vitesse (4 features)
|
||||
- Famille 2 : Diversité et exploration (7 features)
|
||||
- Famille 3 : Authenticité protocolaire (12 features)
|
||||
- Famille 4 : Cohérence cross-layer (14 features)
|
||||
- Famille 5 : Empreinte réseau (13 features)
|
||||
- Famille 6 : Comportement de navigation (10 features)
|
||||
- Famille 7 : Intelligence contextuelle (23 features)
|
||||
- Famille 8 : Features comportementales avancées (13 features)
|
||||
|
||||
5. [Techniques comportementales avancées](#5-techniques-comportementales-avancées)
|
||||
6. [Discussion et limites](#6-discussion-et-limites)
|
||||
7. [Conclusion et perspectives](#7-conclusion-et-perspectives)
|
||||
8. [Références](#8-références)
|
||||
|
||||
---
|
||||
54
docs/thesis/01_introduction.md
Normal file
54
docs/thesis/01_introduction.md
Normal file
@ -0,0 +1,54 @@
|
||||
[<< Sommaire](README.md) | [Suivant >>](02_etat_de_lart.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. Introduction
|
||||
|
||||
### 1.1 Contexte et ampleur de la menace
|
||||
|
||||
Le trafic automatisé constitue désormais la majorité du trafic internet mondial. Selon le [rapport Imperva Bad Bot Report 2025](https://www.imperva.com/blog/five-key-takeaways-from-the-2024-imperva-bad-bot-report/), le trafic automatisé a dépassé le trafic humain pour la première fois en une décennie, représentant 51 % de l'ensemble du trafic web en 2024. Parmi ce trafic automatisé, les bots malveillants (« bad bots ») représentent 37 % de l'ensemble du trafic internet, en augmentation significative par rapport à 32 % en 2023 — soit la sixième année consécutive de croissance. Les secteurs les plus touchés sont le voyage (41 % de trafic bot) et le commerce en ligne (59 % de trafic bot), avec des conséquences directes sur la disponibilité, la propriété intellectuelle et la sécurité des données utilisateurs.
|
||||
|
||||
La menace est structurellement diverse. On distingue trois grandes catégories d'acteurs malveillants automatisés :
|
||||
|
||||
**Frameworks de scraping** : Scrapy (Python), Playwright, Puppeteer, Selenium — ces outils sont légitimement utilisés pour les tests fonctionnels, mais massivement détournés pour l'extraction de contenus protégés (tarification, catalogue produit, données personnelles agrégées).
|
||||
|
||||
**Services de résolution de CAPTCHA** : 2captcha, Anti-Captcha, CapSolver — des services commerciaux emploient des humains (souvent dans des pays à faible salaire) ou des modèles ML pour résoudre les challenges visuels en temps réel, éliminant l'efficacité des mécanismes de friction traditionnels.
|
||||
|
||||
**Botnets résidentiels** : Bright Data, Oxylabs, et des réseaux d'appareils IoT compromis fournissent des adresses IP résidentielles légitimes, contournant les blocages par liste noire d'ASN (Autonomous System Number) de datacenters. Ces proxies résidentiels (SOCKS4/5) opèrent depuis des plages d'adresses IP associées à des fournisseurs d'accès à internet grand public, rendant la réputation IP inefficace.
|
||||
|
||||
Face à cette menace, l'industrie a développé des générations successives de mécanismes de défense, chacune surmontant les limites de la précédente, mais créant de nouvelles vulnérabilités.
|
||||
|
||||
### 1.2 Quatre générations de défenses
|
||||
|
||||
**Génération 1 (2000–2010) : Règles statiques sur les en-têtes HTTP**
|
||||
|
||||
La première génération reposait sur l'inspection statique des en-têtes HTTP : blocage par User-Agent (chaîne de caractères identifiant le client HTTP, ex. `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36`), blocage par adresse IP ou plage CIDR, et reconnaissance de patterns de requêtes (fréquence, chemins ciblés). Ces défenses sont triviales à contourner par rotation de User-Agent : un bot peut simplement choisir aléatoirement un User-Agent parmi une liste de navigateurs légitimes connus, rendant la détection par UA essentiellement inopérante contre les acteurs même peu sophistiqués.
|
||||
|
||||
**Génération 2 (2010–2018) : Fingerprinting TLS et challenges JavaScript**
|
||||
|
||||
La deuxième génération a introduit le fingerprinting TLS avec JA3 ([Althouse et al., 2017, Salesforce Engineering](https://github.com/salesforce/ja3)) — un hash MD5 des caractéristiques du message ClientHello TLS — ainsi que les challenges JavaScript (injection de code côté client pour mesurer les capacités du navigateur). Ces mécanismes ont une efficacité limitée pour deux raisons fondamentales : d'abord, l'instabilité de JA3 due à la méthode GREASE ([RFC 8701](https://www.rfc-editor.org/rfc/rfc8701) — Generate Random Extensions And Sustain Extensibility, qui injecte des valeurs aléatoires dans les listes de cipher suites pour prévenir l'ossification du protocole) ; ensuite, la capacité des navigateurs sans tête modernes (Puppeteer, Playwright avec `headless: false`) à passer la majorité des challenges JavaScript, car ils exécutent un véritable moteur Chromium ou WebKit.
|
||||
|
||||
**Génération 3 (2018–2023) : Fingerprinting multi-protocole et ML comportemental**
|
||||
|
||||
La troisième génération introduit JA4+ ([Althouse, FoxIO, septembre 2023](https://blog.foxio.io/ja4%2B-network-fingerprinting)) — une suite de 12 méthodes de fingerprinting couvrant TLS, TCP, HTTP, SSH, X509 — ainsi que la corrélation multi-protocole et des modèles de machine learning sur des vecteurs comportementaux. Cette génération est limitée par l'émergence d'outils d'imitation de pile TLS : BotBrowser et CloakBrowser annoncent « 30/30 tests passed » sur les suites de détection standard, en patching le moteur Chromium à bas niveau pour imiter les empreintes TLS d'un navigateur légitime. La Gen 3 reste donc vulnérable aux acteurs disposant de ressources pour développer ou acheter ces outils d'imitation avancée.
|
||||
|
||||
**Génération 4 (2024–) : Analyse multi-couches corrélée, dérive conceptuelle et graphes**
|
||||
|
||||
La quatrième génération en émergence combine : analyse corrélée en temps réel sur L3→L7, détection semi-supervisée avec adaptation au concept drift (dérive statistique des données d'entrée dans le temps), graphes de co-occurrence réseau pour détecter les flottes coordonnées, features temporelles haute résolution (variance de jitter TCP, autocorrélation lag-1, déviation de Benford), et fingerprinting HTTP/2 passif côté serveur. L'avantage fondamental de la Gen 4 est que la corrélation cross-layer crée des invariants difficiles à imiter simultanément : un outil peut imiter l'empreinte TLS de Chrome, mais s'il utilise la pile HTTP/2 de curl, l'incohérence entre les couches L5 et L7 devient un signal de détection à haute précision.
|
||||
|
||||
### 1.3 Portée et caractéristiques de ce document
|
||||
|
||||
Ce document décrit une architecture opérationnelle s'inscrivant dans la continuité des approches de génération 3, déployée en production sur un serveur Apache. Les caractéristiques techniques de l'implémentation incluent :
|
||||
|
||||
1. **Corrélation TCP/TLS/HTTP** en temps réel via ja4ebpf (clé : `src_ip:src_port`, 256 shards, timeout orphelin 500 ms)
|
||||
2. **Fingerprinting HTTP/2 passif** : extraction des trames SETTINGS, WINDOW_UPDATE, PRIORITY et de l'ordre des pseudo-headers directement depuis le stream TCP — approche déjà exploitée par des solutions industrielles (Akamai, Cloudflare, F5), ici implémentée via eBPF
|
||||
3. **Architecture EIF bifurquée** : modèle complet (≈ 45 features L3→L7) et modèle applicatif (≈ 35 features L7 uniquement), évitant le biais de zérotage sur le trafic non corrélé — choix pragmatique de gestion des données manquantes
|
||||
4. **Ensemble triple-voix avec fusion par régression logistique** : combinaison EIF + AE + XGBoost avec régression logistique apprise sur étiquettes accumulées
|
||||
5. **HDBSCAN dans l'espace latent AE** : clustering de campagnes par similarité de comportement compressé en 16 dimensions
|
||||
6. **Détection de dérive adversariale** : distinction entre dérive organique (mises à jour navigateur) et manipulation directionnelle coordonnée
|
||||
7. **8 features comportementales avancées** : application de statistiques standard (déviation de Benford, entropie de transition markovienne, autocorrélation lag-1, délai root-to-first-asset, diversité de hosts, uniformité de couverture cross-host) au domaine de la détection de bots
|
||||
8. **Graphes bipartis NetworkX** pour la détection de flottes
|
||||
|
||||
La suite de ce document est organisée comme suit : la Section 2 passe en revue l'état de l'art des techniques de détection. La Section 3 décrit l'architecture complète du pipeline. La Section 4 présente la taxonomie exhaustive des 96 features en 8 familles. Les Sections 5–8 couvrent les techniques comportementales avancées, la discussion des limites et les perspectives.
|
||||
|
||||
---
|
||||
702
docs/thesis/02_etat_de_lart.md
Normal file
702
docs/thesis/02_etat_de_lart.md
Normal file
@ -0,0 +1,702 @@
|
||||
[<< Sommaire](README.md) | [Suivant >>](03_architecture.md)
|
||||
|
||||
---
|
||||
|
||||
## 2. État de l'art
|
||||
|
||||
### 2.1 Détection par règles statiques
|
||||
|
||||
#### 2.1.1 OWASP Core Rule Set (CRS)
|
||||
|
||||
L'[OWASP Core Rule Set (CRS)](https://coreruleset.org/) est un ensemble de règles génériques de détection d'attaques pour les pare-feux applicatifs web (WAF — Web Application Firewall) compatibles ModSecurity et Coraza. La version 4.0, publiée en [février 2024](https://coreruleset.org/20240214/let-crs-4-be-your-valentine/), couvre les catégories d'attaques suivantes :
|
||||
|
||||
**SQLi (SQL Injection)** : injection de code SQL malveillant dans des paramètres de requête ou formulaires pour manipuler les requêtes de base de données. Exemple : `' OR '1'='1` dans un champ de connexion pour contourner l'authentification.
|
||||
|
||||
**XSS (Cross-Site Scripting)** : injection de code JavaScript dans des pages web dynamiques pour qu'il s'exécute dans le navigateur d'autres utilisateurs. Exemple : `<script>document.cookie</script>` dans un commentaire.
|
||||
|
||||
**LFI/RFI (Local File Inclusion / Remote File Inclusion)** : exploitation de fonctions d'inclusion de fichiers pour charger des fichiers arbitraires depuis le système local (LFI : `../../etc/passwd`) ou depuis un serveur distant (RFI : `http://attacker.com/shell.php`).
|
||||
|
||||
**RCE (Remote Code Execution)** : exécution de commandes système arbitraires sur le serveur via une vulnérabilité applicative. Particulièrement destructeur car il donne à l'attaquant le contrôle complet de l'hôte.
|
||||
|
||||
**Scanners connus** : signatures de Nikto, Nessus, OpenVAS, sqlmap, Nuclei — outils de reconnaissance et de fuzzing dont les User-Agents et patterns de requêtes sont documentés.
|
||||
|
||||
**Points forts du CRS** :
|
||||
|
||||
| Propriété | Valeur |
|
||||
|-----------|--------|
|
||||
| Déterminisme | Totalement déterministe, explicable |
|
||||
| Latence | < 1 ms par requête |
|
||||
| Couverture | OWASP Top 10, CWE courants |
|
||||
| Maintenance | Communauté active, mises à jour fréquentes |
|
||||
| Intégration | ModSecurity, Coraza, Nginx, Apache, F5 |
|
||||
|
||||
**Limites fondamentales** :
|
||||
|
||||
- **Absence de contexte session/comportemental** : chaque requête est évaluée de manière indépendante. Un scraper qui envoie 10 000 requêtes GET syntaxiquement correctes est totalement invisible pour le CRS.
|
||||
- **Vulnérabilité aux payloads polymorphiques augmentés par LLM** : [Osama et al., 2025 (arXiv:2512.23610)](https://arxiv.org/abs/2512.23610) démontrent que les WAF basés sur des règles (ModSecurity + CRS) n'ont qu'un taux de blocage de 10 à 14 % contre des payloads d'injection SQL et XSS générés et mutés par LLM, contre 96 à 100 % pour XGBoost entraîné sur des features comportementales. Les LLMs (GPT-4, Claude) peuvent paraphraser les payloads malveillants de manière à préserver leur sémantique d'injection tout en contournant les expressions régulières du CRS.
|
||||
- **Détection comportementale nulle** : un scraper syntaxiquement conforme, sans charge utile malveillante, est structurellement invisible pour le CRS.
|
||||
- **Coût de maintenance élevé** : les nouvelles techniques d'attaque nécessitent des mises à jour manuelles de règles, créant un délai systématique entre l'émergence d'une menace et sa couverture.
|
||||
|
||||
#### 2.1.2 Listes de réputation IP et ASN
|
||||
|
||||
La deuxième couche de défense statique repose sur des dictionnaires de réputation. Trois structures sont utilisées en production :
|
||||
|
||||
**dict_bot_ip (IP_TRIE)** : dictionnaire de plages CIDR associées à des bots connus, implémenté comme un arbre radix (trie sur les bits de l'adresse IP) permettant une recherche par plus long préfixe en O(k) où k est la longueur de la clé (32 bits pour IPv4, 128 bits pour IPv6). L'opération de lookup par CIDR en O(1) amorti est possible grâce à l'indexation des nœuds internes du trie.
|
||||
|
||||
**CIDR (Classless Inter-Domain Routing)** : notation pour spécifier des plages d'adresses IP, ex. `192.168.1.0/24` désigne les 256 adresses de `192.168.1.0` à `192.168.1.255`. Le suffixe `/24` indique que les 24 premiers bits sont le préfixe réseau.
|
||||
|
||||
**ASN (Autonomous System Number)** : numéro unique identifiant un système autonome — un groupe de préfixes IP contrôlés par un seul opérateur réseau et partageant une politique de routage unifiée. Exemple : AS15169 = Google, AS32934 = Meta, AS14618 = Amazon AWS. Attribués par les RIR (Regional Internet Registry : ARIN, RIPE NCC, APNIC). Les ASNs sont la base de BGP (Border Gateway Protocol), le protocole de routage inter-domaines d'internet.
|
||||
|
||||
**dict_bot_ja4** : mapping JA4 hash → bot_name pour les empreintes TLS connues associées à des outils spécifiques.
|
||||
|
||||
**dict_asn_reputation** : mapping ASN → label parmi : `human` (FAI résidentiel), `datacenter` (AWS, GCP, Azure, OVH), `proxy` (Bright Data, Oxylabs), `tor` (nœuds de sortie Tor), `vpn` (ExpressVPN, NordVPN), `scanner` (Shodan, Censys, Masscan), `bot` (botnets connus).
|
||||
|
||||
**Limite critique** : les botnets résidentiels opèrent depuis des ASNs étiquetés `human`. Les proxies résidentiels (Bright Data, Oxylabs) vendent un accès à des millions d'adresses IP de particuliers — souvent via des applications mobiles dont les conditions d'utilisation autorisent la réutilisation de la bande passante — pour contourner précisément les listes de réputation ASN. Une adresse IP Bright Data apparaît dans le même sous-réseau qu'un voisin résidentiel légitime, avec le même ASN de FAI. Ce vecteur de contournement est structurellement invisible pour les approches purement basées sur la réputation IP/ASN.
|
||||
|
||||
#### 2.1.3 Projet Anubis (TecharoHQ)
|
||||
|
||||
[Anubis](https://github.com/TecharoHQ/anubis) est un système de règles communautaire en YAML permettant de définir des actions granulaires par bot identifié. Les quatre actions disponibles sont :
|
||||
|
||||
- **ALLOW** : autorisation explicite (bots légitimes : Googlebot, Bingbot, bots de recherche académique)
|
||||
- **DENY** : blocage avec retour 403 Forbidden — signal de vérité terrain fort pour l'entraînement XGBoost
|
||||
- **WEIGH** : ajout d'un score de pondération sans blocage — signal auxiliaire dans le vecteur de features
|
||||
- **CHALLENGE** : redirection vers un challenge (PoW ou CAPTCHA)
|
||||
|
||||
**Implémentation dans l'architecture** : deux structures de lookup sont maintenues en mémoire :
|
||||
- `dict_anubis_ip` : IP_TRIE CIDR pour les correspondances par plage IP
|
||||
- `dict_anubis_asn` : dictionnaire ASN → action/score
|
||||
|
||||
**Priorité de correspondance** : `COALESCE(IP match, ASN match)` — une correspondance CIDR précise sur l'IP prend la priorité sur la correspondance ASN plus générale. Cela reflète le principe que l'information la plus spécifique est la plus fiable.
|
||||
|
||||
**Valeur pour le pipeline ML** :
|
||||
- Les sessions `DENY` fournissent des étiquettes de bot à haute confiance pour l'entraînement supervisé de XGBoost, sans nécessiter d'annotation manuelle.
|
||||
- Les sessions `WEIGH` contribuent une feature binaire `anubis_is_flagged` dans la famille F7, enrichissant le vecteur de features sans déclencher de blocage.
|
||||
|
||||
---
|
||||
|
||||
### 2.2 Fingerprinting réseau
|
||||
|
||||
#### 2.2.1 TLS Fingerprinting : de JA3 à JA4+
|
||||
|
||||
**Fondements : le message ClientHello TLS**
|
||||
|
||||
Lorsqu'un client initie une connexion TLS (Transport Layer Security), il envoie un message `ClientHello` qui contient les informations suivantes :
|
||||
|
||||
- **TLS versions supportées** : ex. TLS 1.2, TLS 1.3
|
||||
- **Cipher suites** : paires algorithme de chiffrement symétrique + AEAD proposées par le client, ex. `TLS_AES_256_GCM_SHA384` (TLS 1.3), `TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256` (TLS 1.2). Le serveur choisit la suite la plus forte qu'il supporte.
|
||||
- **Extensions** : ensemble de champs optionnels étendant le protocole de base : SNI, ALPN, session ticket, supported_versions, key_share, signature_algorithms, etc.
|
||||
- **Elliptic curve groups (supported_groups)** : courbes elliptiques pour l'échange de clés ECDH, ex. x25519, secp256r1, secp384r1
|
||||
- **Point formats** : formats de compression des points de courbe elliptique (généralement `uncompressed` uniquement en TLS 1.3)
|
||||
|
||||
Ces champs sont déterminés par la pile TLS du client (OpenSSL, BoringSSL, NSS, SChannel) et varient peu d'une connexion à l'autre pour le même client, constituant une empreinte stable et distinctive.
|
||||
|
||||
**JA3**
|
||||
|
||||
[JA3 (Althouse et al., 2017, Salesforce)](https://github.com/salesforce/ja3) est un algorithme de fingerprinting TLS qui calcule le hash MD5 d'une chaîne de valeurs concaténées extraites du ClientHello :
|
||||
|
||||
```
|
||||
JA3 = MD5(TLSVersion,Ciphers,Extensions,EllipticCurves,EllipticCurvePointFormats)
|
||||
```
|
||||
|
||||
Exemple : `769,47-53-5-10-49172-49162-49157-49167-49161-100-56-19-49156-49162-49159-49160-22-3,0-10-11,23-24-25,0`
|
||||
|
||||
JA3 a été largement adopté (Cloudflare, AWS, Akamai, Suricata, Zeek) mais présente deux problèmes fondamentaux :
|
||||
|
||||
**Instabilité GREASE** : [RFC 8701](https://www.rfc-editor.org/rfc/rfc8701) (Generate Random Extensions And Sustain Extensibility) introduit un mécanisme par lequel les clients TLS injectent des valeurs « factices » dans leurs listes de cipher suites et d'extensions. Ces valeurs GREASE (0x0A0A, 0x1A1A, 0x2A2A, 0x3A3A, 0x4A4A, 0x5A5A, 0x6A6A, 0x7A7A, 0x8A8A, 0x9A9A, 0xAAAA, 0xBABA, 0xCACA, 0xDADA, 0xEAEA, 0xFAFA) sont choisies aléatoirement à chaque connexion. L'objectif est de s'assurer que les serveurs ne rejettent pas les extensions inconnues, évitant l'ossification du protocole. Conséquence : deux connexions successives du même navigateur Chrome peuvent produire des JA3 différents en raison du changement des valeurs GREASE, rendant l'empreinte non déterministe.
|
||||
|
||||
**Collision inter-versions** : les mises à jour majeures de navigateur changent la liste des cipher suites supportés, modifiant le JA3 de manière légitime. Un JA3 associé à Chrome 110 est différent de celui de Chrome 120.
|
||||
|
||||
**JA4**
|
||||
|
||||
[JA4 (Althouse, FoxIO, septembre 2023)](https://github.com/FoxIO-LLC/ja4) est une refonte complète du fingerprinting TLS avec un format lisible par l'humain structuré en trois sections `a_b_c` :
|
||||
|
||||
**Section a (lisible par l'humain)** : chaîne de 10 caractères encodant les métadonnées structurelles :
|
||||
- Type de protocole : `t` (TCP), `q` (QUIC), `d` (DTLS)
|
||||
- Version TLS négociée : `13` (TLS 1.3), `12` (TLS 1.2), `10` (TLS 1.0)
|
||||
- Nombre de cipher suites (2 chiffres, GREASE filtrés)
|
||||
- Nombre d'extensions (2 chiffres, GREASE filtrés)
|
||||
- Valeur ALPN de premier choix (`h2`, `h1`, `00` si absent)
|
||||
- Présence du SNI : `d` (domain, SNI présent), `i` (IP, SNI absent)
|
||||
|
||||
**ALPN (Application-Layer Protocol Negotiation)** : extension TLS définie dans [RFC 7301](https://www.rfc-editor.org/rfc/rfc7301) permettant au client d'annoncer les protocoles applicatifs qu'il supporte dans le ClientHello (ex. `h2` pour HTTP/2, `http/1.1`, `h3`). Le serveur sélectionne un protocole dans la liste et le communique dans le ServerHello. ALPN permet le multiplexage de protocoles sur un même port TLS sans identification de port séparée. Son absence dans un ClientHello signale un client non-navigateur ou une bibliothèque TLS minimaliste.
|
||||
|
||||
**SNI (Server Name Indication)** : extension TLS définie dans [RFC 6066](https://www.rfc-editor.org/rfc/rfc6066) qui envoie le nom d'hôte cible dans le ClientHello, permettant à un serveur d'héberger plusieurs certificats TLS sur une seule adresse IP (hébergement virtuel TLS). Sans SNI, un serveur multi-domaines ne peut pas savoir quel certificat présenter avant que la session TLS soit établie.
|
||||
|
||||
**Section b** : SHA-256 tronqué aux 12 premiers caractères hexadécimaux de la liste de cipher suites triée (GREASE filtrés). Le tri élimine les variations non sémantiques dues à l'ordre aléatoire des cipher suites.
|
||||
|
||||
**Section c** : SHA-256 tronqué aux 12 premiers caractères hexadécimaux de la liste d'extensions triée (GREASE filtrés, codes extensions seulement sans valeurs).
|
||||
|
||||
Exemple de JA4 : `t13d1516h2_8daaf6152771_e5627efa2ab1`
|
||||
|
||||
La combinaison du tri et du filtrage GREASE élimine la variabilité non sémantique, produisant une empreinte stable pour le même navigateur entre deux connexions.
|
||||
|
||||
**Famille JA4+ (12 méthodes)**
|
||||
|
||||
| Nom court | Nom complet | Signal capturé | Couche |
|
||||
|-----------|-------------|----------------|--------|
|
||||
| JA4 | JA4 TLS Client | Empreinte ClientHello TLS (version, ciphers, extensions, ALPN, SNI) | L5 TLS |
|
||||
| JA4S | JA4 Server | Réponse TLS serveur (version négociée, cipher choisi, extensions) | L5 TLS |
|
||||
| JA4H | JA4 HTTP | Ordre et présence des en-têtes HTTP, Accept-Language, cookies | L7 HTTP |
|
||||
| JA4L | JA4 Latency | Latence client→serveur (mesure de la distance physique) | L4 TCP |
|
||||
| JA4LS | JA4 Latency Server | Latence serveur→client | L4 TCP |
|
||||
| JA4X | JA4 X509 | Certificat TLS : algorithme de signature, extensions, OID | L5 TLS/X.509 |
|
||||
| JA4SSH | JA4 SSH | Échange SSH : kex algorithms, host key, encryption, MAC | L5 SSH |
|
||||
| JA4T | JA4 TCP | Paramètres SYN : Window Size, Options TCP, Window Scale, MSS | L4 TCP |
|
||||
| JA4TS | JA4 TCP Server | Réponse SYN-ACK serveur | L4 TCP |
|
||||
| JA4TScan | JA4 TCP Scan | Fingerprint de scanner TCP actif | L4 TCP |
|
||||
| JA4+ (composite) | Multi-protocol | Combinaison JA4+JA4H+JA4T pour cohérence cross-layer | L4–L7 |
|
||||
| (en développement) | | | |
|
||||
|
||||
**Adoption en production (2026)** : [Cloudflare](https://developers.cloudflare.com/bots/concepts/ja3-ja4-fingerprint/), [AWS CloudFront](https://aws.amazon.com/about-aws/whats-new/2024/10/amazon-cloudfront-ja4-fingerprinting/), [AWS WAF](https://aws.amazon.com/about-aws/whats-new/2025/03/aws-waf-ja4-fingerprinting-aggregation-ja3-ja4-fingerprints-rate-based-rules/), Google Cloud Armor, Azure Front Door, Akamai, F5 BIG-IP, Suricata, Zeek, Arkime, VirusTotal, Fastly, Vercel.
|
||||
|
||||
#### 2.2.2 TCP Fingerprinting
|
||||
|
||||
**JA4T** capture les paramètres du paquet SYN TCP pour identifier le système d'exploitation et la configuration réseau du client.
|
||||
|
||||
**Paquet SYN TCP** : premier paquet de la poignée de main à trois voies TCP (SYN → SYN-ACK → ACK). Il contient dans son en-tête :
|
||||
|
||||
- **MSS (Maximum Segment Size)** : taille maximale du payload TCP en octets qu'un segment peut transporter. Pour Ethernet standard (MTU 1500), MSS = 1500 - 20 (IP header) - 20 (TCP header) = 1460 octets. Un MSS réduit (ex. 1380) signale la présence d'un tunnel VPN qui consomme une partie de l'espace disponible pour son propre en-tête d'encapsulation.
|
||||
- **Window Size** : taille du tampon de réception en octets, indiquant combien de données le récepteur peut accepter sans acquittement. Déterminé par l'implémentation de la pile TCP dans le noyau OS.
|
||||
- **TCP Options** : champ extensible contenant des options négociées. Les plus importantes : type 1 (NOP padding), type 2 (MSS), type 3 (Window Scale), type 4 (SACK Permitted), type 8 (Timestamps).
|
||||
- **Window Scale (RFC 7323)** : option TCP définie dans [RFC 7323](https://www.rfc-editor.org/rfc/rfc7323) permettant de dépasser la limite de 65 535 octets du champ Window Size (16 bits). Le facteur d'échelle est un exposant binaire (0–14) : `taille_effective = window_size × 2^scale`. Permet des fenêtres TCP jusqu'à 1 Go, indispensable pour les connexions à haute bande passante et forte latence (liaisons satellite, intercontinentales).
|
||||
- **TTL IP (Time to Live)** : compteur décrémenté par chaque routeur traversé. Valeurs initiales typiques : Linux=64, Windows=128, iOS/macOS=64. Après K sauts, TTL_observé = TTL_initial - K, permettant d'inférer TTL_initial et donc l'OS source.
|
||||
|
||||
**Signatures OS caractéristiques** :
|
||||
|
||||
| OS | Window Size | Options TCP | Window Scale | MSS | TTL initial |
|
||||
|----|------------|-------------|--------------|-----|-------------|
|
||||
| Linux 5.x+ | 64240 | 2-4-8-1-3 | 7 | 1460 | 64 |
|
||||
| Windows 11 | 64240 | 2-4-8-1-3 | 8 | 1460 | 128 |
|
||||
| macOS 14+ | 65535 | 2-4-8-1-3 | 6 | 1460 | 64 |
|
||||
| iOS 17+ | 65535 | 2-4-8-1-3 | 6 (ou absent) | 1460 | 64 |
|
||||
| Windows (sans TS) | 8192 | 2-4-1-3 | 2 | 1460 | 128 |
|
||||
|
||||
**Détection VPN/Tunnel** :
|
||||
- MSS = 1380 : présence d'un tunnel VPN (overhead d'encapsulation ≈ 80 octets)
|
||||
- MSS = 1360 : double encapsulation (VPN sur VPN, ou VPN sur réseau mobile)
|
||||
- MSS = 1452 : réseau PPPoE (DSL, certains opérateurs mobiles)
|
||||
|
||||
**Signatures de scanners** :
|
||||
|
||||
| Outil | Window Size | Options TCP | MSS | Comportement caractéristique |
|
||||
|-------|------------|-------------|-----|------------------------------|
|
||||
| Masscan | 1024 | 2 (MSS seulement) | 1460 | Pas de SACK, pas de timestamps, pas de window scale |
|
||||
| ZMap | 65535 | 2-4-8-1-3 | 1460 | Fenêtre maximale |
|
||||
| Nmap | 1024 | 2-4-8-1-3 | 1460 | Options standard mais fenêtre minimale |
|
||||
| Nmap (SYN stealth) | 1024 | 2-4-1-3 | variable | Pas de timestamps |
|
||||
|
||||
**Détection de carriers mobiles** :
|
||||
- AT&T : MSS = 1340 (réseau LTE avec overhead GTP)
|
||||
- Verizon : MSS = 1392 (optimisation réseau propriétaire)
|
||||
- T-Mobile : MSS = 1420
|
||||
|
||||
**Features TCP capturées** : `tcp_meta_window_size`, `tcp_meta_mss`, `tcp_meta_window_scale`, `tcp_meta_options`, `ip_meta_ttl`
|
||||
|
||||
**Features dérivées** : `mss_mobile_mismatch` (MSS mobile mais UA desktop), `no_window_scale_ratio` (ratio de connexions sans Window Scale — obsolète ou stack TCP minimal), `avg_ttl`, `ttl_std`, `ip_df_variance`
|
||||
|
||||
#### 2.2.3 Fingerprinting TLS avancé
|
||||
|
||||
Au-delà du JA4 de base, plusieurs signaux TLS dérivés améliorent la précision de classification :
|
||||
|
||||
**Ratio de diversité JA3** : `count(distinct JA3) / count(distinct JA4)`. Pour un navigateur légitime, le ratio est proche de 1 (même JA4 stable, légère variation JA3 due à GREASE). Un bot qui randomise les valeurs GREASE pour contourner la détection JA3 produit un ratio élevé (5–20 JA3 différents pour le même JA4), révélant la stratégie d'évasion elle-même.
|
||||
|
||||
**Absence d'ALPN** (`is_alpn_missing`) : l'absence de l'extension ALPN dans le ClientHello signale un client non-navigateur. Tous les navigateurs modernes (Chrome, Firefox, Safari) incluent systématiquement ALPN avec `h2` et `http/1.1` depuis 2016. Les bibliothèques TLS minimalistes (Go `crypto/tls` configurée minimalement, certaines versions d'OpenSSL sans configuration ALPN) omettent cette extension. Valeur de précision élevée.
|
||||
|
||||
**Divergence SNI↔Host** (`sni_host_mismatch`) : détecte une discordance entre le nom d'hôte dans le SNI TLS et la valeur de l'en-tête HTTP `Host`. Cette discordance peut indiquer :
|
||||
- **Domain fronting** : technique d'évasion de censure où le SNI pointe vers un CDN de confiance (ex. `cloudfront.net`) mais l'en-tête HTTP Host pointe vers le domaine cible réel, masquant la destination dans la couche TLS. Bloqué par la plupart des CDN majeurs depuis 2018 mais encore utilisé dans certains contextes.
|
||||
- **Proxy mal configuré** : un proxy réécrit l'en-tête Host sans mettre à jour la session TLS.
|
||||
- **Outil d'automatisation** : certains frameworks HTTP ne synchronisent pas SNI et Host correctement.
|
||||
|
||||
**Timing SYN→ClientHello** : le délai entre le paquet SYN TCP et le message ClientHello TLS mesure le temps nécessaire au client pour initier la négociation TLS après l'établissement de la connexion TCP. Variance et coefficient de variation (`tcp_jitter_variance`, `syn_timing_cv`) : les scripts automatisés montrent une variance proche de zéro (délai constant, déterminé par le code), les humains montrent une variance naturelle (réseau + délai de clavier/application).
|
||||
|
||||
---
|
||||
|
||||
### 2.3 Analyse comportementale HTTP
|
||||
|
||||
#### 2.3.1 Signaux d'en-têtes HTTP
|
||||
|
||||
Les navigateurs web modernes envoient un ensemble précis d'en-têtes HTTP à chaque requête. L'absence ou la déformation de ces en-têtes constitue un signal de détection fiable pour les clients non-navigateurs.
|
||||
|
||||
| En-tête | Signification | Comportement navigateur | Signal bot si absent/anormal |
|
||||
|---------|---------------|------------------------|------------------------------|
|
||||
| `Accept-Language` | Préférence de langue, ex. `fr-FR,fr;q=0.9,en;q=0.8` | Toujours présent | Souvent absent dans les scripts |
|
||||
| `Accept-Encoding` | Encodages supportés : `gzip, deflate, br` | Inclut `br` (Brotli, [RFC 7932](https://www.rfc-editor.org/rfc/rfc7932)) depuis Chrome 49 (2016) | Absence de `br` = client pré-2016 ou non-navigateur |
|
||||
| `Sec-Fetch-Site` | Origine de la requête : `same-origin`, `cross-site`, `none` | Présent depuis Chrome 76, Firefox 90 | Jamais généré par les non-navigateurs |
|
||||
| `Sec-Fetch-Mode` | Mode de la requête : `navigate`, `cors`, `no-cors`, `same-origin` | Présent, valeur sémantique stricte | Signal d'authenticité très fort si cohérent |
|
||||
| `Sec-Fetch-Dest` | Destination : `document`, `script`, `image`, `style`, etc. | Présent, cohérent avec le type de ressource | Incohérence = navigation simulée |
|
||||
| `Sec-Fetch-User` | Initié par l'utilisateur : `?1` | Présent sur les navigations top-level utilisateur | Absent sur requêtes programmatiques |
|
||||
| `Sec-CH-UA` | Client Hints UA : `"Chromium";v="120", "Not(A:Brand";v="24"` | Envoyé par Chromium depuis Chrome 89 | Absent dans les non-Chromium |
|
||||
| `Sec-CH-UA-Mobile` | Indicateur mobile : `?0` (desktop) ou `?1` (mobile) | Cohérent avec l'UA et le type d'appareil | `?1` avec UA desktop = mismatch |
|
||||
| `Sec-CH-UA-Platform` | Plateforme : `"Windows"`, `"macOS"`, `"Linux"`, `"Android"` | Cohérent avec l'OS dans l'UA | Incohérence détectable |
|
||||
| `Cookie` | Cookies de session et tracking | Maintenu automatiquement par le navigateur | Absent pour les bots stateless |
|
||||
| `Referer` | URL d'où provient la requête | Présent lors de la navigation dans une page | Absent = accès direct (typique des bots) |
|
||||
| Ordre des en-têtes | Séquence fixe propre à chaque implémentation | Chrome: Host, Connection, sec-ch-ua... | Ordre non-Chrome révèle la bibliothèque réelle |
|
||||
|
||||
**Fetch Metadata** : les en-têtes `Sec-Fetch-*` sont définis dans la [spécification W3C Fetch Metadata](https://www.w3.org/TR/fetch-metadata/) et envoyés automatiquement par les navigateurs Chromium (depuis Chrome 76, 2019) et Firefox (depuis Firefox 90, 2021). Ils fournissent au serveur des informations sur le contexte de la requête : est-elle initiée par l'utilisateur ? Par un script cross-origin ? Vers une iframe ? Ces en-têtes ne sont jamais générés par des bibliothèques HTTP non-navigateur (Python requests, curl, Go net/http, Node.js fetch de base).
|
||||
|
||||
**Brotli** : algorithme de compression [RFC 7932](https://www.rfc-editor.org/rfc/rfc7932) développé par Google, plus efficace que gzip (15–25 % de compression supplémentaire). Supporté nativement par tous les navigateurs modernes depuis 2016. L'absence de `br` dans `Accept-Encoding` indique fortement un client non-navigateur ou une très vieille version.
|
||||
|
||||
**Client Hints** : mécanisme [MDN Client Hints](https://developer.mozilla.org/en-US/docs/Web/HTTP/Client_hints) permettant aux navigateurs Chromium d'envoyer des informations structurées sur le client (UA, plateforme, mobilité) à travers des en-têtes `Sec-CH-*` standardisés. Le `ua_ch_mismatch` détecte une incohérence entre `Sec-CH-UA-Mobile: ?1` (mobile) et un User-Agent desktop, ou l'absence de `Sec-CH-UA` dans un UA se revendiquant Chrome récent.
|
||||
|
||||
**Features extraites** : `has_accept_language`, `has_cookie`, `has_referer`, `sec_fetch_absence_rate` (ratio de requêtes sans Sec-Fetch), `generic_accept_ratio` (ratio Accept générique `*/*`), `missing_accept_enc_ratio`, `modern_browser_score`, `ua_ch_mismatch`, `header_count`, `header_order_confidence`
|
||||
|
||||
#### 2.3.2 Patterns de navigation
|
||||
|
||||
Les patterns de navigation à travers un site web distinguent les comportements humains des comportements automatisés selon plusieurs dimensions :
|
||||
|
||||
**asset_ratio** : `count_assets / hits` où count_assets = requêtes vers CSS, JavaScript, images, fonts. Un navigateur humain charge typiquement 60 à 80 % de ressources statiques pour chaque page HTML visitée (chargement du template, des scripts, des images). Un scraper qui n'extrait que le HTML produit un `asset_ratio` proche de 0. Valeurs typiques : navigateur humain 0.6–0.8, scraper HTML 0.01–0.05, bot API 0.
|
||||
|
||||
**direct_access_ratio** : ratio de requêtes sans en-tête `Referer`. Les humains naviguent en suivant des liens (Referer présent sauf première page), les bots accèdent directement aux URLs. Valeurs : humain 0.1–0.3, bot de scraping 0.7–1.0.
|
||||
|
||||
**path_diversity_ratio** : `uniq_paths / hits`. Un crawler systématique accède à une URL différente à chaque requête (ratio ≈ 1). Un bot répétitif accède toujours au même chemin (ratio ≈ 0.01). La navigation humaine est intermédiaire (0.3–0.8) car les utilisateurs visitent plusieurs pages mais revisitent parfois certaines.
|
||||
|
||||
**url_depth_variance** : variance de la profondeur des URLs (nombre de `/` dans le chemin). Un crawler systématique parcourt méthodiquement une profondeur fixe (variance faible). La navigation humaine génère une variance naturelle.
|
||||
|
||||
#### 2.3.3 Brute-force et credential stuffing
|
||||
|
||||
Les attaques d'authentification par force brute et credential stuffing (utilisation de couples identifiant/mot de passe volés) produisent des patterns comportementaux distincts :
|
||||
|
||||
**fuzzing_index** : `uniq_query_params / uniq_paths`. Un fuzzer d'API teste de nombreuses valeurs de paramètres sur un nombre restreint d'endpoints (ratio élevé, ex. 50 paramètres différents sur `/api/search` → ratio 50). La navigation normale génère environ autant de chemins distincts que de paramètres distincts (ratio ≈ 1).
|
||||
|
||||
**post_ratio** : `count_POST / hits`. Un trafic normal a un faible ratio de requêtes POST (soumissions de formulaires). Un attaquant en credential stuffing maintient un ratio POST élevé (0.5–1.0) car chaque tentative d'authentification est un POST.
|
||||
|
||||
**count_login_post** : compteur direct des requêtes POST vers les endpoints d'authentification (`/login`, `/signin`, `/auth`, `/wp-login.php`, `/account/login`). Valeurs : humain 0–2 par session, credential stuffer 100–10 000.
|
||||
|
||||
**login_post_concentration** : `count_login_post / total_POST` — mesure si les POSTs se concentrent sur les endpoints de connexion, distinguant un utilisateur qui utilise beaucoup de formulaires d'un attaquant ciblant uniquement l'authentification.
|
||||
|
||||
---
|
||||
|
||||
### 2.4 Apprentissage automatique pour la détection d'intrusions
|
||||
|
||||
#### 2.4.1 Approches supervisées et leurs limites
|
||||
|
||||
**XGBoost (eXtreme Gradient Boosting)**
|
||||
|
||||
XGBoost ([Chen & Guestrin, 2016](https://arxiv.org/abs/1603.02754)) est un algorithme d'ensemble d'arbres de décision entraînés séquentiellement (boosting). Principe : chaque arbre est entraîné pour corriger les erreurs résiduelles de l'ensemble des arbres précédents, en minimisant un gradient de la fonction de perte. La "descente de gradient" s'effectue dans l'espace des fonctions (arbres) plutôt que dans l'espace des paramètres.
|
||||
|
||||
**Mécanisme technique** :
|
||||
1. Initialisation : prédiction initiale constante f0(x) = argmin Σ L(yi, γ)
|
||||
2. Pour chaque itération m = 1,...,M :
|
||||
- Calcul des résidus pseudo-gradients : rim = -[∂L(yi, F(xi)) / ∂F(xi)] pour chaque xi
|
||||
- Entraînement d'un arbre hm sur les résidus rim
|
||||
- Mise à jour : F_m(x) = F_{m-1}(x) + η × hm(x) où η est le learning rate
|
||||
3. La prédiction finale est : F(x) = f0(x) + Σ η × hm(x)
|
||||
|
||||
**Optimisations XGBoost** :
|
||||
- **Parallélisation** : la construction de chaque arbre est parallélisée par sous-échantillonnage de colonnes (colsample_bytree) et par tri par histogramme (histogram approximation du split optimal)
|
||||
- **Régularisation L1/L2** : termes de régularisation ajoutés à la fonction objectif pour contrôler la complexité des arbres
|
||||
- **Hyperparamètres clés** : `max_depth` (profondeur maximale des arbres), `n_estimators` (nombre d'arbres), `learning_rate` (η), `subsample` (fraction des lignes par arbre), `colsample_bytree` (fraction des features par arbre)
|
||||
|
||||
**Performances** : [Osama et al., 2025 (arXiv:2512.23610)](https://arxiv.org/abs/2512.23610) rapportent 99,59 % de précision et une inférence en microsecondes pour XGBoost sur des features comportementales de bot detection, contre 10–14 % de blocage pour les WAF à règles face aux mêmes payloads augmentés par LLM.
|
||||
|
||||
**Limites des approches supervisées** :
|
||||
- **Concept drift** : un modèle entraîné sur des bots de 2024 peut être aveugle aux nouvelles techniques de 2025
|
||||
- **Rareté des étiquettes** : annoter manuellement des millions de sessions HTTP est coûteux et sujet à erreur
|
||||
- **Bruit des étiquettes** : les labels fournis par les analystes SOC contiennent des erreurs systématiques (faux positifs mal corrigés, biais de confirmation). Ces étiquettes bruitées empoisonnent le modèle supervisé — un problème bien documenté par [Northcutt et al., 2021 (Cleanlab)](https://arxiv.org/abs/1911.00068) qui montre que les jeux de données réels contiennent 8 à 20 % de labels incorrects. Pour mitiger ce risque, notre pipeline intègre un filtre Cleanlab avant l'entraînement XGBoost (détail §3.8).
|
||||
- **Biais de jeu de données** : les modèles entraînés sur des données de laboratoire (CICIDS2017, NSL-KDD) généralisent mal au trafic en production, comme documenté dans la littérature sur les benchmarks de détection d'intrusions
|
||||
- **Attaque par évasion adversariale** : un attaquant ayant accès ou connaissance du modèle peut crafting des sessions qui maximisent le score de légitimité
|
||||
|
||||
#### 2.4.2 Approches semi-supervisées
|
||||
|
||||
**Isolation Forest (Liu et al., 2008)**
|
||||
|
||||
[Isolation Forest (Liu, Ting, Zhou, ICDM 2008)](https://cs.nju.edu.cn/zhouzh/zhouzh.files/publication/icdm08b.pdf) est un algorithme de détection d'anomalies basé sur la notion d'isolation aléatoire. Insight fondamental : les anomalies, étant rares et différentes des points normaux, sont isolées plus rapidement (en moins de partitions récursives) que les points normaux.
|
||||
|
||||
**Algorithme** :
|
||||
1. Pour chaque arbre d'isolation (n_estimators arbres) :
|
||||
- Sélectionner aléatoirement ψ points (sous-échantillon)
|
||||
- Construire récursivement une partition binaire : (a) choisir une feature aléatoire q, (b) choisir un seuil p uniformément entre min(q) et max(q), (c) partitionner en q ≤ p et q > p, jusqu'à isolation de chaque point
|
||||
2. Le score d'anomalie d'un point x est inversement proportionnel à la longueur moyenne du chemin d'isolation :
|
||||
|
||||
```
|
||||
score(x, n) = 2^( -E[h(x)] / c(n) )
|
||||
```
|
||||
|
||||
où E[h(x)] est la longueur de chemin moyenne à travers tous les arbres, et c(n) = 2H(n-1) - 2(n-1)/n est la longueur de chemin moyenne attendue dans une BST (Binary Search Tree) unsuccessful, avec H(i) = ln(i) + 0.5772 (constante d'Euler-Mascheroni).
|
||||
|
||||
Un score proche de 1 indique une anomalie (chemin court, isolé rapidement) ; un score proche de 0.5 est normal ; un score < 0.5 est clairement normal.
|
||||
|
||||
**Extended Isolation Forest (EIF)**
|
||||
|
||||
[Extended Isolation Forest (Hariri, Kind, Brunner, IEEE TKDE 2021)](https://ieeexplore.ieee.org/document/8888179) résout un problème fondamental de l'Isolation Forest standard dans les espaces à haute dimension.
|
||||
|
||||
**Problème des « fantômes de clusters » (ghost clusters)** : l'IF standard utilise des coupes axis-alignées (hyperplans parallèles aux axes des features). En haute dimension (> 10 features), cette stratégie produit des artéfacts géométriques : des zones de l'espace des features sans données reçoivent artificellement des scores d'anomalie bas, car les coupes parallèles aux axes ont tendance à partitionner les régions denses (là où se concentrent les données normales) plutôt que les régions vides aux frontières. Ces zones vides — les « fantômes de clusters » — sont des minima artificiels du score d'anomalie créés par la géométrie des hyperplans axis-alignés, et non par la présence réelle de données normales.
|
||||
|
||||
**Solution EIF** : remplacer les coupes axis-alignées par des hyperplans à pente aléatoire. À chaque nœud de l'arbre :
|
||||
1. Choisir un vecteur normal aléatoire n ∈ ℝ^d (uniform sur la sphère unité)
|
||||
2. Choisir un point d'interception aléatoire p ∈ [min(X·n), max(X·n)]
|
||||
3. Partitionner : {x : x·n ≤ p} et {x : x·n > p}
|
||||
|
||||
Résultat : les coupes ne sont plus liées aux axes, éliminant les artéfacts géométriques. Les scores d'anomalie deviennent cohérents et fiables, particulièrement dans les espaces à 47–59 dimensions de notre architecture.
|
||||
|
||||
**Relation IF/EIF** : l'IF standard est un cas particulier de EIF avec `extension_level=0` (vecteur normal aligné sur un seul axe aléatoire).
|
||||
|
||||
**Implémentation** : bibliothèque [isotree (Cortes, 2021+)](https://github.com/david-cortes/isotree) — implémentation C++ native avec interface Python. Paramètres de production : `ntrees=300`, `contamination=0.001`.
|
||||
|
||||
**Calibration des scores** : isotree retourne un score ∈ [0, 1] où > 0.5 = anomalie. Transformation pour compatibilité sklearn : `sklearn_equiv = 0.5 - isotree_score` (négatif = anomalie). Le modèle est sérialisé avec joblib accompagné des statistiques de baseline (quantiles par feature : p10, p25, p50, p75, p90) pour la détection de dérive.
|
||||
|
||||
**Architecture bifurquée EIF** :
|
||||
|
||||
Deux modèles EIF s'exécutent en parallèle à chaque cycle de 300 secondes :
|
||||
|
||||
- **Modèle Complet** (≈ 45 features, L3→L7) : appliqué sur les sessions pour lesquelles ja4ebpf a pu corréler les métadonnées TCP/TLS avec la requête HTTP (données L3/L4 disponibles). Inclut toutes les features des familles F1–F7.
|
||||
- **Modèle Applicatif** (≈ 35 features, L7 uniquement) : appliqué sur les sessions dont les données TCP/TLS sont absentes — trafic passant par un CDN ou proxy qui ne permet pas la corrélation TCP/TLS. Utiliser les features TCP/TLS imputées à zéro pour ce modèle introduirait un biais systématique.
|
||||
|
||||
La bifurcation est justifiée par le fait que les features TCP/TLS ne sont disponibles que lorsque ja4ebpf a corrélé la connexion réseau avec la requête HTTP. Imputer ces features à zéro pour le trafic non corrélé créerait un signal artificiel (zéro n'est pas neutre pour un EIF — il est interprété comme une valeur réelle).
|
||||
|
||||
#### 2.4.2b Autoencoders (AE) et détection d'anomalies
|
||||
|
||||
**Principe des autoencoders**
|
||||
|
||||
Un autoencodeur est un réseau de neurones entraîné à reconstruire son entrée après compression à travers un espace latent de dimension réduite (bottleneck). Architecture :
|
||||
|
||||
```
|
||||
Encodeur : x (dim D) → z (dim d, d << D)
|
||||
Décodeur : z (dim d) → x̂ (dim D)
|
||||
```
|
||||
|
||||
Entraîné à minimiser l'erreur de reconstruction :
|
||||
|
||||
```
|
||||
MSE = (1/n) × Σ(xi - x̂i)²
|
||||
```
|
||||
|
||||
Le bottleneck (espace latent dimension 16 << dimension d'entrée 45–59) force le réseau à apprendre la représentation la plus compacte du trafic normal. L'erreur de reconstruction est le score d'anomalie : les échantillons normaux se reconstruisent bien (MSE faible) ; les anomalies se reconstruisent mal (MSE élevé) car le réseau n'a jamais été entraîné sur des patterns de bot.
|
||||
|
||||
**Avantage fondamental sur EIF** : l'AE capture les corrélations non-linéaires entre features. Un bot utilisant httpcloak peut imiter individuellement chaque feature Chrome (JA4 correct, ALPN correct, en-têtes HTTP corrects) mais leurs inter-corrélations inhabituelles (tcp_jitter_variance × sec_fetch_absence_rate × asset_ratio) trahissent l'imitation dans l'espace de reconstruction de l'AE. EIF détecte les anomalies ponctuelles (points isolés dans l'espace des features) ; l'AE détecte les anomalies distributionnelles (corrélations inter-features anormales).
|
||||
|
||||
**Implémentation** : PyTorch, espace latent 16 dimensions, arrêt précoce sur la loss de validation, score d'anomalie = erreur de reconstruction MSE.
|
||||
|
||||
**Kitsune**
|
||||
|
||||
[Kitsune (Mirsky et al., NDSS 2018)](https://www.ndss-symposium.org/wp-content/uploads/2018/02/ndss2018_03A-3_Mirsky_paper.pdf) est un NIDS (Network Intrusion Detection System) en ligne basé sur un ensemble d'autoencoders légers (KitNET). Architecture : N petits autoencoders (~64 neurones chacun) correspondant à des groupes de features corrélées, dont les erreurs de reconstruction sont agrégées par un autoencodeur de sortie servant de vote non-linéaire. Démonstration que de petits AE sur Raspberry Pi atteignent des performances comparables aux détecteurs hors ligne, validant l'approche d'ensemble d'AE pour la détection d'intrusions en production.
|
||||
|
||||
**β-VAE**
|
||||
|
||||
Les Variational Autoencoders (VAE) ajoutent une régularisation KL-divergence structurant l'espace latent. La KL-divergence (divergence de Kullback-Leibler) mesure comment une distribution de probabilité P diffère d'une distribution de référence Q :
|
||||
|
||||
```
|
||||
KL(P||Q) = Σ P(x) × log(P(x)/Q(x))
|
||||
```
|
||||
|
||||
Pour un VAE, la régularisation KL(q(z|x) || p(z)) pénalise l'encodeur si sa distribution postérieure q(z|x) s'éloigne d'une normale standard p(z) = N(0, I). Score d'anomalie VAE : `-log p(x|z) + KL(q(z|x) || p(z))`. Cette régularisation rend l'espace latent plus lisse et mieux organisé, potentiellement améliorant la qualité du clustering HDBSCAN.
|
||||
|
||||
**Complémentarité AE + IF**
|
||||
|
||||
La complémentarité entre IF et AE est un résultat bien établi en détection d'anomalies :
|
||||
|
||||
| Critère | Isolation Forest (EIF) | Autoencoder (AE) |
|
||||
|---------|------------------------|------------------|
|
||||
| Type d'anomalie détecté | Points isolés dans l'espace des features | Corrélations non-linéaires anormales |
|
||||
| Interprétabilité | ExIFFI (profondeur d'isolation par feature) | Erreur par dimension |
|
||||
| Dimensionnalité | Optimal jusqu'à ~100 features | Robuste, compresse l'espace |
|
||||
| Détection d'imitation | Partielle | Plus forte (corrélations inter-features) |
|
||||
| Espace pour clustering | Non adapté | Espace latent 16D idéal pour HDBSCAN |
|
||||
| Coût computationnel | Faible (arbres) | Modéré (réseau de neurones) |
|
||||
|
||||
**Espace latent AE pour HDBSCAN** : les 16 dimensions latentes de l'AE capturent la variance la plus discriminante du comportement des bots. Le clustering HDBSCAN dans cet espace regroupe les sessions dont le comportement compressé est similaire — c'est-à-dire les bots utilisant le même outil ou la même configuration — identifiant ainsi les campagnes coordonnées.
|
||||
|
||||
#### 2.4.2c Ensembles hybrides supervisé + non-supervisé
|
||||
|
||||
**Architecture triple-voix**
|
||||
|
||||
Le système de détection combine trois « voix » complémentaires :
|
||||
|
||||
```
|
||||
┌─────────────────────┐
|
||||
│ Vecteur de 85 │
|
||||
│ features par │
|
||||
│ session (5min) │
|
||||
└────────┬────────────┘
|
||||
│
|
||||
┌──────────────┼──────────────┐
|
||||
│ │ │
|
||||
┌─────▼─────┐ ┌─────▼─────┐ ┌────▼──────┐
|
||||
│ EIF │ │ AE │ │ XGBoost │
|
||||
│ (semi- │ │ (semi- │ │(supervisé)│
|
||||
│supervisé) │ │supervisé) │ │ │
|
||||
└─────┬─────┘ └─────┬─────┘ └────┬──────┘
|
||||
│ │ │
|
||||
eif_norm ae_norm xgb_prob
|
||||
│ │ │
|
||||
└──────────────┼──────────────┘
|
||||
│
|
||||
┌────────▼────────┐
|
||||
│ Fusion LR │
|
||||
│ (régression │
|
||||
│ logistique, │
|
||||
│ ≥1000 labels) │
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌────────▼────────┐
|
||||
│ score_final │
|
||||
│ → étiquette │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
**Formule de combinaison** :
|
||||
|
||||
```python
|
||||
final = (1 - XGB_WEIGHT) × ((1 - AE_WEIGHT) × eif_norm + AE_WEIGHT × ae_norm) \
|
||||
+ XGB_WEIGHT × xgb_prob
|
||||
```
|
||||
|
||||
Valeurs par défaut : `AE_WEIGHT=0.30`, `XGB_WEIGHT=0.20`. Configurable via variables d'environnement pour ajustement en production sans modification du code.
|
||||
|
||||
**Fusion LR**
|
||||
|
||||
Le Fusion LR est une régression logistique activée lorsqu'au moins 1 000 étiquettes ont été accumulées (sessions DENY Anubis + annotations manuelles + seuillage de confiance).
|
||||
|
||||
Régression logistique : modèle linéaire probabiliste qui apprend des poids w pour chaque signal intermédiaire en minimisant l'entropie croisée binaire sur les étiquettes accumulées :
|
||||
|
||||
```
|
||||
P(bot) = σ(w1×eif + w2×ae + w3×xgb + w4×volume + bias)
|
||||
```
|
||||
|
||||
où σ est la fonction sigmoïde : σ(z) = 1 / (1 + e^{-z})
|
||||
|
||||
Les poids w1–w4 sont appris, permettant au système de calibrer automatiquement l'importance relative de chaque voix en fonction du type de trafic en production. En dessous de 1 000 étiquettes, le système revient aux poids fixes : `(eif: 0.50, ae: 0.30, xgb: 0.20)`.
|
||||
|
||||
**Calendrier de retraining** :
|
||||
- XGBoost : hebdomadaire sur les étiquettes accumulées, après filtrage Cleanlab des labels SOC bruyants (voir ci-dessous)
|
||||
- EIF : toutes les 24 heures
|
||||
- AE : continu avec arrêt précoce sur la loss de validation
|
||||
|
||||
**Filtrage des labels SOC bruyants (Cleanlab)** :
|
||||
|
||||
Avant chaque entraînement XGBoost, les labels fournis par les analystes SOC sont filtrés via [Cleanlab](https://cleanlab.ai/) ([Northcutt et al., 2021](https://arxiv.org/abs/1911.00068)). Ce framework de *confident learning* identifie les exemples dont l'étiquette est probablement erronée en comparant les prédictions out-of-fold d'un modèle aux labels observés.
|
||||
|
||||
```python
|
||||
# 1. Obtenir pred_probs via cross-validation (3 folds)
|
||||
quick_model = XGBClassifier(n_estimators=80, max_depth=4)
|
||||
pred_probs = cross_val_predict(quick_model, X, y, cv=3, method='predict_proba')
|
||||
|
||||
# 2. Identifier les labels douteux
|
||||
issues = find_label_issues(labels=y, pred_probs=pred_probs)
|
||||
|
||||
# 3. Exclure les exemples bruités avant l'entraînement final
|
||||
X_clean, y_clean = X[~noisy_mask], y[~noisy_mask]
|
||||
```
|
||||
|
||||
Ce mécanisme protège le modèle contre l'empoisonnement par des faux positifs mal corrigés ou des biais de confirmation des analystes. Le taux de labels filtrés est loggé pour surveillance. En cas d'échec de Cleanlab (erreur mémoire, dépendance manquante), le pipeline revient aux données brutes sans interruption.
|
||||
|
||||
#### 2.4.3 Concept Drift et retraining adaptatif
|
||||
|
||||
**Définition du concept drift**
|
||||
|
||||
En apprentissage automatique, le concept drift désigne le changement des propriétés statistiques de la variable cible ou de la distribution d'entrée dans le temps, causant une dégradation des prédictions du modèle. Types :
|
||||
|
||||
- **Drift soudain** : changement abrupt (ex. mise à jour majeure du navigateur modifiant les valeurs JA4)
|
||||
- **Drift graduel** : évolution progressive (ex. changement des patterns de navigation au fil des mois)
|
||||
- **Drift récurrent** : patterns saisonniers (ex. comportements différents le weekend vs. la semaine)
|
||||
- **Drift incrémental** : dérive lente et continue (ex. adaptation progressive des techniques d'évasion)
|
||||
|
||||
En détection de bots, le drift adversarial est particulièrement critique : les attaquants adaptent délibérément leurs outils pour contourner les modèles déployés.
|
||||
|
||||
**Test de Kolmogorov-Smirnov**
|
||||
|
||||
Le test KS (Kolmogorov-Smirnov) mesure la différence maximale entre deux fonctions de distribution empiriques cumulées (ECDF) :
|
||||
|
||||
```
|
||||
D = max|F1(x) - F2(x)|
|
||||
```
|
||||
|
||||
où F1 et F2 sont les ECDFs de la distribution courante et de la distribution de référence (baseline). Si D dépasse la valeur critique (déterminée par les tables de la distribution KS pour un niveau de confiance α), les deux échantillons sont considérés comme provenant de distributions différentes. Avantage : test non-paramétrique, aucune hypothèse sur la forme de la distribution.
|
||||
|
||||
**Méthode de détection de dérive** :
|
||||
|
||||
1. Sauvegarde avec chaque modèle sérialisé de l'approximation 5-quantiles par feature : (p10, p25, p50, p75, p90)
|
||||
2. Génération d'échantillons synthétiques par interpolation de la CDF inverse (quantile function)
|
||||
3. Test KS + divergence KL sur chaque feature entre la distribution courante et la baseline
|
||||
4. Feature marquée comme « en dérive » si le test KS OU la divergence KL dépasse le seuil configuré
|
||||
5. Retraining forcé si > 30 % des features dérivent simultanément
|
||||
6. **Détection de dérive adversariale** : si de nombreuses features dérivent simultanément dans la même direction (score de corrélation directionnelle élevé), génération d'une alerte spécifique distinguant la manipulation intentionnelle (dérive adversariale coordonnée) de l'évolution organique (mises à jour navigateur ou changements de comportement naturels)
|
||||
|
||||
**Validation gate** : si le taux d'anomalie sur le jeu de validation dépasse 20 % après retraining, le nouveau modèle est rejeté et le modèle précédent est conservé, avec génération d'une alerte (baseline contaminée).
|
||||
|
||||
**Limites de l'approximation 5-quantiles** : adéquate pour les distributions unimodales, mais peut manquer les dérives bimodales dans `asset_ratio`, `post_ratio`, `orphan_ratio`. Extension à p5/p95 ou à t-digest identifiée comme travail futur.
|
||||
|
||||
#### 2.4.4 Modélisation des phases d'attaque
|
||||
|
||||
La modélisation des phases d'attaque (Reconnaissance → Mouvement latéral → Intrusion → Exfiltration) par des modèles d'état-espace ou des processus de Markov cachés constitue une piste de recherche. L'enrichissement du clustering HDBSCAN avec ce signal de phase permettrait de distinguer des campagnes en phase de reconnaissance de campagnes en phase d'exploitation active.
|
||||
|
||||
La modélisation des phases d'attaque par des modèles d'état-espace ou des processus de Markov cachés constitue une piste de recherche pour distinguer les campagnes en phase de reconnaissance de celles en phase d'exploitation active.
|
||||
|
||||
#### 2.4.5 Explicabilité par SHAP et ExIFFI
|
||||
|
||||
**SHAP (SHapley Additive exPlanations)**
|
||||
|
||||
[SHAP (Lundberg & Lee, 2017, shap.readthedocs.io)](https://shap.readthedocs.io/) est un framework d'explicabilité locale basé sur la théorie des jeux coopératifs (valeurs de Shapley). La contribution d'une feature i à la prédiction pour un exemple x est définie comme sa contribution marginale moyenne sur toutes les ordonnances possibles des features :
|
||||
|
||||
```
|
||||
φi(f, x) = Σ_{S ⊆ F\{i}} [|S|!(|F|-|S|-1)!/|F|!] × [f(S∪{i}) - f(S)]
|
||||
```
|
||||
|
||||
où F est l'ensemble de toutes les features, S est un sous-ensemble, et f(S) est la prédiction du modèle en utilisant seulement les features dans S (les autres étant marginalisées).
|
||||
|
||||
**SHAP TreeExplainer** est une implémentation exacte et efficace pour les modèles basés sur des arbres (XGBoost, Random Forest, LightGBM) en complexité O(TLD²) où T = nombre d'arbres, L = nombre de feuilles, D = profondeur maximale — beaucoup plus rapide que l'estimation par permutation (O(2^|F|)).
|
||||
|
||||
**ExIFFI (Extended Isolation Forest Feature Importance)**
|
||||
|
||||
L'importance des features par profondeur d'isolation (approche de type ExIFFI) est une méthode native d'importance des features pour EIF, basée sur la profondeur moyenne d'isolation par feature. Principe : une feature ayant une profondeur d'isolation moyenne faible (isole rapidement les anomalies) est plus importante pour la détection. Activé comme fallback lorsque SHAP n'est pas disponible. Comparé aux top-5 SHAP dans l'interface SOC.
|
||||
|
||||
**Erreur de reconstruction AE par dimension** : `(xi - x̂i)²` par dimension d'entrée identifie quelles features contribuent à l'anomalie AE. Cela fournit une explicabilité locale pour chaque session anormale : les features dont la reconstruction est la plus dégradée sont les plus discriminantes.
|
||||
|
||||
---
|
||||
|
||||
**Approches émergentes : GNN et Transformers**
|
||||
|
||||
L'analyse de séquences de navigation et la détection de flottes distribuées bénéficient également d'approches plus récentes :
|
||||
|
||||
- **Graph Neural Networks (GNN)** : les GNN opèrent directement sur des graphes de co-occurrence (par exemple IP↔JA4 ou IP↔chemin), apprenant des embeddings de nœuds qui capturent la structure topologique des flottes. Par rapport à la détection de communautés Louvain utilisée dans cette architecture, les GNN offrent une capacité de généralisation supérieure mais nécessitent un volume de données d'entraînement labellisées significatif.
|
||||
|
||||
- **Transformers pour l'analyse de séquences** : les architectures Transformer (self-attention) permettent de modéliser les dépendances longues dans les séquences de chemins ou les séries temporelles d'intervalles inter-requêtes, capturant des patterns que les chaînes de Markov d'ordre 1 ne peuvent pas représenter.
|
||||
|
||||
Cette architecture utilise des méthodes plus légères (Louvain, Markov d'ordre 1, HDBSCAN) en raison de contraintes opérationnelles : pas de données labellisées en volume suffisant, inférence en temps réel sur des cycles de 300 secondes, et interopérabilité avec les outils du SOC.
|
||||
|
||||
---
|
||||
|
||||
### 2.5 Détection côté client (Browser Fingerprinting)
|
||||
|
||||
#### 2.5.1 JavaScript Challenges
|
||||
|
||||
Les challenges JavaScript constituent la principale approche de détection côté client : injection de code JS dans les pages pour collecter Canvas fingerprint, WebGL renderer, propriétés du navigateur, timing d'exécution JS, et soumettre ces données au serveur pour scoring.
|
||||
|
||||
**Principaux acteurs** : Cloudflare Turnstile, DataDome, PerimeterX (HUMAN Security), Akamai Bot Manager, Shape Security.
|
||||
|
||||
**Limites** :
|
||||
|
||||
- **Contournement par navigateurs sans tête patchés** : BotBrowser et CloakBrowser annoncent « 30/30 tests passés » sur les suites de détection standard comme [botcheck.me](https://botcheck.me), [creepjs](https://abrahamjuliot.github.io/creepjs/), et les challenges Cloudflare. Ces outils patchent Chromium au niveau binaire pour supprimer les artefacts d'automatisation (`navigator.webdriver`, propriétés `__proto__` de l'injection Selenium, etc.) et spoofer les valeurs Canvas/WebGL.
|
||||
- **Inapplicabilité aux APIs** : les endpoints d'API ne peuvent pas injecter de JavaScript. Toute protection API repose nécessairement sur des signaux passifs côté serveur.
|
||||
- **Pénalité de latence** : le challenge ajoute une requête supplémentaire (et parfois un délai intentionnel de preuve de travail) à la première visite, dégradant l'expérience utilisateur.
|
||||
|
||||
**Position de cette architecture** : 100 % passive, aucun challenge JavaScript. L'ensemble des signaux de détection provient de l'analyse des paquets réseau et des en-têtes HTTP.
|
||||
|
||||
#### 2.5.2 FingerprintJS BotD
|
||||
|
||||
[FingerprintJS BotD](https://github.com/fingerprintjs/BotD) est une bibliothèque JavaScript côté client qui détecte les frameworks d'automatisation en inspectant :
|
||||
- `navigator.webdriver` : propriété mise à `true` par Selenium et certaines versions de Puppeteer
|
||||
- Artefacts Phantom (`window.__phantom`, `window.callPhantom`)
|
||||
- Artefacts Selenium (`window.selenium`, `document.__webdriver_evaluate`)
|
||||
- Propriétés de prototypes modifiées par les injections automatisées
|
||||
|
||||
**Limitation** : contournable par des patches bas niveau comme [rebrowser-patches](https://github.com/rebrowser/rebrowser-patches), qui modifient directement le runtime V8 et les propriétés exposées par Chromium.
|
||||
|
||||
#### 2.5.3 Fingerprinting HTTP/2 passif côté serveur
|
||||
|
||||
**HTTP/2 : fondements du protocole**
|
||||
|
||||
HTTP/2 ([RFC 7540](https://www.rfc-editor.org/rfc/rfc7540), remplacé par [RFC 9113](https://www.rfc-editor.org/rfc/rfc9113) en 2022) est un protocole binaire multiplexé remplaçant HTTP/1.1 pour les connexions TLS. Caractéristiques fondamentales :
|
||||
|
||||
**Framing binaire** : toute communication HTTP/2 est découpée en frames de type : HEADERS, DATA, SETTINGS, WINDOW_UPDATE, PRIORITY, RST_STREAM, PUSH_PROMISE, PING, GOAWAY, CONTINUATION. Chaque frame a un en-tête de 9 octets contenant la longueur, le type, les flags et l'identifiant de stream.
|
||||
|
||||
**Multiplexage** : plusieurs streams HTTP/2 coexistent sur une seule connexion TCP, chacun identifié par un stream ID (impair pour les streams initiés par le client : 1, 3, 5...). Élimine le head-of-line blocking de HTTP/1.1 pipelines.
|
||||
|
||||
**Compression d'en-têtes HPACK** : [RFC 7541](https://www.rfc-editor.org/rfc/rfc7541) définit HPACK, qui compresse les en-têtes HTTP/2 via :
|
||||
1. **Table statique** : 61 paires nom/valeur prédéfinies (ex. entrée 1 = `:authority`, entrée 5 = `:path /`). Référencées par index 1–61.
|
||||
2. **Table dynamique** : paires ajoutées dynamiquement au fur et à mesure de la connexion, référencées par index 62+.
|
||||
3. **Codage Huffman** : pour les valeurs littérales, encodage Huffman réduit la taille de 20–30 %.
|
||||
Résultat : réduction de la taille des en-têtes de 85–88 % par rapport à HTTP/1.1.
|
||||
|
||||
**Pseudo-headers** : HTTP/2 remplace la ligne de requête HTTP/1.1 (`GET /path HTTP/1.1`) par quatre pseudo-headers préfixés par `:` : `:method`, `:path`, `:scheme`, `:authority`.
|
||||
|
||||
**Contrôle de flux (Flow Control)** : [RFC 7540 §5.2](https://www.rfc-editor.org/rfc/rfc7540#section-5.2) empêche un émetteur rapide de saturer un récepteur lent. Deux niveaux : par stream (stream-level) et par connexion (connection-level). Les frames WINDOW_UPDATE incrémentent la fenêtre de contrôle de flux d'un montant spécifié.
|
||||
|
||||
**Fingerprinting HTTP/2 passif**
|
||||
|
||||
L'approche de fingerprinting passif HTTP/2 ([Bartlett, Cloudflare, 2023](https://blog.cloudflare.com/cloudflare-bot-management-machine-learning-and-more/), [Akamai Client Fingerprint](https://www.akamai.com/)) extrait des signaux depuis les frames HTTP/2 reçues par le serveur immédiatement après l'établissement de la connexion.
|
||||
|
||||
**Avantage fondamental** : le fingerprint HTTP/2 est produit par la couche transport, en dessous du niveau applicatif. Un outil d'évasion doit implémenter une pile HTTP/2 complète et fidèle à un navigateur cible pour l'éviter — effort d'ingénierie significativement supérieur à la simple rotation d'un User-Agent.
|
||||
|
||||
**Frame SETTINGS (RFC 7540 §6.5)** :
|
||||
|
||||
Envoyée immédiatement par le client après le préambule de connexion (`PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n`). Contient des paires clé-valeur identifiant les paramètres de la session HTTP/2 :
|
||||
|
||||
| ID | Nom | Signification |
|
||||
|----|-----|---------------|
|
||||
| 1 | HEADER_TABLE_SIZE | Taille maximale de la table de compression HPACK (octets) |
|
||||
| 2 | ENABLE_PUSH | Activation du Server Push HTTP/2 (0=désactivé, 1=activé) |
|
||||
| 3 | MAX_CONCURRENT_STREAMS | Nombre maximum de streams simultanés |
|
||||
| 4 | INITIAL_WINDOW_SIZE | Taille initiale de la fenêtre de contrôle de flux par stream (octets) |
|
||||
| 5 | MAX_FRAME_SIZE | Taille maximale des frames (octets, min 16384) |
|
||||
| 6 | MAX_HEADER_LIST_SIZE | Taille maximale de la liste d'en-têtes (octets) |
|
||||
| 8 | ENABLE_CONNECT_PROTOCOL | [RFC 8441](https://www.rfc-editor.org/rfc/rfc8441) - WebSocket over HTTP/2 |
|
||||
|
||||
Valeurs caractéristiques par implémentation :
|
||||
|
||||
| Client | HEADER_TABLE_SIZE | ENABLE_PUSH | MAX_CONCURRENT_STREAMS | INITIAL_WINDOW_SIZE | MAX_FRAME_SIZE | MAX_HEADER_LIST_SIZE |
|
||||
|--------|-------------------|-------------|------------------------|---------------------|----------------|----------------------|
|
||||
| Chrome 119+ | 65536 | 0 | 1000 | 6291456 | 16384 | 262144 |
|
||||
| Firefox 90+ | 65536 | 0 | 100 | 131072 | 16384 | absent |
|
||||
| Safari 15+ | 4096 | 0 | 100 | 2097152 | 16384 | absent |
|
||||
| curl 7.x | 0 (absent) | — | — | 0 (absent) | — | — (min settings) |
|
||||
| Python httpx | 65536 | 0 | 100 | 65535 | 16384 | absent |
|
||||
| Go net/http | 4096 | 0 | 250 | 1048576 | 16384 | absent |
|
||||
| Node.js http2 | 4096 | 0 | 100 | 65535 | 16384 | absent |
|
||||
|
||||
**WINDOW_UPDATE** :
|
||||
|
||||
Après l'échange SETTINGS, le client envoie une frame WINDOW_UPDATE incrémentant la fenêtre de contrôle de flux de connexion. Cette valeur est fixe par implémentation et constitue un signal très discriminant :
|
||||
|
||||
| Client | Incrément WINDOW_UPDATE |
|
||||
|--------|------------------------|
|
||||
| Chrome 119+ | 15 663 105 (± 1 000) |
|
||||
| Firefox 90+ | 12 517 377 (± 1 000) |
|
||||
| Safari 15+ | 10 420 225 (± 1 000) |
|
||||
| curl | absent (0) |
|
||||
| Python httpx | absent (0) |
|
||||
| Go net/http | 1 073 676 289 (± 1 000) |
|
||||
| Node.js http2 | 65535 (valeur par défaut) |
|
||||
|
||||
**Ordre des pseudo-headers** :
|
||||
|
||||
L'ordre dans lequel les pseudo-headers `:method`, `:authority`, `:scheme`, `:path` apparaissent dans la première frame HEADERS est fixe par implémentation (abréviation : m=method, a=authority, s=scheme, p=path) :
|
||||
|
||||
| Client | Ordre pseudo-headers |
|
||||
|--------|---------------------|
|
||||
| Chrome 119+ | m a s p |
|
||||
| Firefox 90+ | m p a s |
|
||||
| Safari 15+ | m s p a |
|
||||
| curl | m p s a |
|
||||
| Python httpx | m p s a (comme curl) |
|
||||
| Go net/http | m s a p |
|
||||
|
||||
**Frames PRIORITY** :
|
||||
|
||||
Firefox 90+ envoie des frames PRIORITY de démarrage pour les streams 3, 5, 7, 9, 11, 13 avant d'initier la première vraie requête. Chrome ≥ 119 n'envoie plus de frames PRIORITY (migration vers [RFC 9218](https://www.rfc-editor.org/rfc/rfc9218) Extended PRIORITY_UPDATE). Safari n'envoie pas de frames PRIORITY.
|
||||
|
||||
**Format fingerprint composite** (format Akamai) :
|
||||
|
||||
```
|
||||
SETTINGS[HEADER_TABLE_SIZE,ENABLE_PUSH,MAX_CONCURRENT_STREAMS,INITIAL_WINDOW_SIZE,MAX_FRAME_SIZE,MAX_HEADER_LIST_SIZE]|WINDOW_UPDATE[increment]|PRIORITY[stream:exclusive:dep:weight...]|PSEUDO_HEADER_ORDER[m,a,s,p]
|
||||
```
|
||||
|
||||
Exemple Chrome 119 :
|
||||
```
|
||||
1:65536,2:0,3:1000,4:6291456,5:16384,6:262144|WU:15663105|noPRIORITY|masp
|
||||
```
|
||||
|
||||
**Cas d'étude httpcloak** :
|
||||
|
||||
httpcloak est un outil d'évasion qui tente d'imiter l'empreinte TLS de Chrome. Il réussit à reproduire correctement le JA4 Chrome (SETTINGS H2 et WINDOW_UPDATE), mais son ordre de pseudo-headers par défaut correspond à l'ordre curl (`mpsa`) plutôt qu'à l'ordre Chrome (`masp`). Cette incohérence entre la couche L5 (TLS : empreinte Chrome) et la couche L7 (HTTP/2 : ordre curl) crée un signal de détection à haute précision : score de browser_matcher ≈ 0.60, plaçant la session dans la zone grise et déclenchant l'analyse EIF + AE.
|
||||
|
||||
---
|
||||
|
||||
### 2.6 Synthèse des limites de l'état de l'art
|
||||
|
||||
| Technique | Limite principale | Mitigation dans cette architecture |
|
||||
|-----------|-------------------|--------------------------------------|
|
||||
| Règles statiques CRS | Aveugle au trafic syntaxiquement correct | Couche comportementale ML (F1–F8) |
|
||||
| Listes IP/ASN | Botnets résidentiels échappent aux blacklists | ASN = feature parmi d'autres, pas bloquant seul |
|
||||
| JA3 | Instabilité GREASE, collisions inter-versions | JA4 (GREASE filtré, trié), ratio JA3/JA4 comme feature |
|
||||
| JS challenges | Contournés par BotBrowser/CloakBrowser | Architecture 100 % passive, HTTP/2 fingerprinting |
|
||||
| UA matching | Trivial à usurper | UA est une parmi 96 features, cohérence cross-layer |
|
||||
| ML supervisé seul | Concept drift, label scarcity | Semi-supervisé EIF + AE + retraining adaptatif |
|
||||
| Isolation Forest std | Ghost clusters en haute dimension | Extended Isolation Forest (hyperplans aléatoires) |
|
||||
| AE seul | Ne détecte pas les anomalies ponctuelles | Ensemble EIF + AE complémentaires |
|
||||
| Fingerprinting TLS seul | Imitable par httpcloak, CloakBrowser | Corrélation L5 TLS + L7 HTTP/2 (tls_h2_family_mismatch) |
|
||||
| HTTP/2 fingerprinting seul | HTTP/1.1 fallback ne produit pas ce signal | Dégradation gracieuse : features H2 à 0.5 si HTTP/1.1 |
|
||||
| Détection par session | Flottes distribuées → 1 IP semble normale | Graphes bipartis NetworkX (Part B) |
|
||||
| Seuil fixe de classification | Sur-blocage en période calme, sous-blocage en pic | Seuil adaptatif percentile_5 des scores négatifs |
|
||||
|
||||
---
|
||||
295
docs/thesis/03_architecture.md
Normal file
295
docs/thesis/03_architecture.md
Normal file
@ -0,0 +1,295 @@
|
||||
[<< Sommaire](README.md) | [Suivant >>](04_browser_matcher.md)
|
||||
|
||||
---
|
||||
|
||||
## 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 ≈ 1–3 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 1–6 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 F1–F6 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 L3–L6, 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 | F1–F7 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
|
||||
|
||||
```python
|
||||
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)](https://link.springer.com/chapter/10.1007/978-3-642-37456-2_14) 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))` où `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 »)
|
||||
|
||||
---
|
||||
|
||||
|
||||
---
|
||||
485
docs/thesis/04_browser_matcher.md
Normal file
485
docs/thesis/04_browser_matcher.md
Normal file
@ -0,0 +1,485 @@
|
||||
[<< Sommaire](README.md) | [Suivant >>](05_features.md)
|
||||
|
||||
---
|
||||
|
||||
## 3.9 Browser Signature Detection (browser_matcher)
|
||||
|
||||
`[impl.]` **Statut global : capture H2, scoring statique et profiling dynamique entièrement implémentés**
|
||||
|
||||
Le système `browser_confidence` à 6 axes (§3.8) fournit un score agrégé utile comme feature ML, mais sa logique de bypass (seuil 0,55) manque de précision face aux outils d'évasion modernes — notamment `httpcloak`, `curl_cffi` et `python-requests` patchés — qui reproduisent les couches TLS et HTTP/1.1 sans reproduire les subtilités HTTP/2. `browser_matcher` adresse cette lacune en implémentant une correspondance structurée par famille de navigateur, fondée sur l'empreinte passive du protocole HTTP/2. Le module de scoring à 7 dimensions pondérées (`browser_matcher.py`, 497 lignes) et la base de signatures Chrome/Firefox/Safari (`browser_signatures.py`, 165 lignes) sont entièrement opérationnels. En complément, un moteur de **profiling dynamique automatique** (`profile_builder.py`, 614 lignes et `browser_matcher_dynamic.py`, 387 lignes) apprend les signatures des navigateurs à partir des logs réels via HDBSCAN, éliminant la dépendance aux signatures codées en dur et s'adaptant automatiquement aux nouvelles versions de navigateurs (§3.9.6).
|
||||
|
||||
### 3.9.1 Principes du fingerprinting HTTP/2 passif
|
||||
|
||||
#### Rappel du protocole HTTP/2
|
||||
|
||||
[HTTP/2 (RFC 7540)](https://www.rfc-editor.org/rfc/rfc7540) est un protocole binaire, multiplexé, qui remplace la communication textuelle d'HTTP/1.1. Ses principales caractéristiques architecturales pertinentes pour le fingerprinting sont :
|
||||
|
||||
- **Framing binaire** : toute communication est découpée en **frames** (trames) de type défini. Les types principaux sont SETTINGS, HEADERS, DATA, WINDOW_UPDATE, PRIORITY, PUSH_PROMISE, PING, GOAWAY.
|
||||
- **Multiplexage de streams** : plusieurs échanges requête/réponse coexistent sur une seule connexion TCP, identifiés par un **stream ID** entier.
|
||||
- **Compression d'en-têtes HPACK** : [RFC 7541](https://www.rfc-editor.org/rfc/rfc7541) définit HPACK, une compression d'en-têtes HTTP par table d'indexation. L'ordre des entrées dans la table statique HPACK est normalisé, mais l'ordre de sérialisation des pseudo-headers est laissé à l'implémentation.
|
||||
- **Contrôle de flux** : mécanisme de fenêtres (`WINDOW_UPDATE`) limitant le débit pour éviter la saturation du récepteur.
|
||||
|
||||
Après terminaison TLS par le serveur web, le flux HTTP/2 est déchiffré et disponible en clair dans le contexte d'OpenSSL/BoringSSL. Le fingerprinting passif est réalisé par **ja4ebpf** via un uprobe sur `SSL_read`. Cependant, `SSL_read` retourne des octets bruts déchiffrés sans respecter les frontières des trames HTTP/2 — la fragmentation TCP et le buffering TLS signifient qu'un seul appel peut correspondre à un fragment de trame ou à plusieurs trames complètes. Le **Go Magic Bytes dispatcher** maintient un **buffer circulaire de réassemblage** par connexion, accumulant les données de plusieurs appels `SSL_read` jusqu'à ce que la logique de parsing HTTP/2 confirme qu'une trame complète est disponible (9 octets d'en-tête de trame + longueur payload). L'identification du preface `PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n` se fait dans ce buffer accumulé. Tout le parsing des trames SETTINGS, WINDOW_UPDATE et HEADERS (y compris le décodage HPACK partiel pour extraire l'ordre des pseudo-headers) s'effectue dans l'agent Go en espace utilisateur, et non dans la VM eBPF. Cette approche est agnostique au serveur web (Apache, Nginx, Varnish, HAProxy) et ne nécessite aucun module natif installé côté serveur.
|
||||
|
||||
#### Frame SETTINGS
|
||||
|
||||
La frame **SETTINGS** ([RFC 7540 §6.5](https://www.rfc-editor.org/rfc/rfc7540#section-6.5)) est envoyée par le client immédiatement après le **connection preface** (octet sequence `PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n`). Elle contient des paires clé-valeur dont les clés sont des identifiants 16 bits normalisés. L'ensemble et les valeurs de ces paramètres constituent une empreinte stable par implémentation HTTP/2.
|
||||
|
||||
**Table complète des paramètres SETTINGS par client (format : ID — Nom — Chrome 119+ / Firefox 90+ / Safari 15+ / curl 8.x / Go net/http)** :
|
||||
|
||||
| ID | Nom | Chrome 119+ | Firefox 90+ | Safari 15+ | curl 8.x | Go net/http |
|
||||
|-----|-------------------------|-------------|-------------|------------|----------|-------------|
|
||||
| 1 | HEADER_TABLE_SIZE | 65536 | 65536 | absent | absent | absent |
|
||||
| 2 | ENABLE_PUSH | 0 | 0 | 0 | 0 | absent |
|
||||
| 3 | MAX_CONCURRENT_STREAMS | absent | absent | 100 | absent | absent |
|
||||
| 4 | INITIAL_WINDOW_SIZE | 6291456 | 131072 | 2097152 | 65535 | 4194304 |
|
||||
| 5 | MAX_FRAME_SIZE | absent | 16384 | absent | absent | absent |
|
||||
| 6 | MAX_HEADER_LIST_SIZE | 262144 | absent | absent | absent | 10485760 |
|
||||
| 9 | ENABLE_CONNECT_PROTOCOL | absent | absent | 1 | absent | absent |
|
||||
|
||||
**Analyse discriminante** : le paramètre `INITIAL_WINDOW_SIZE` (ID 4) discrimine à lui seul les quatre grandes familles :
|
||||
- **Chrome** : 6 291 456 octets (6 Mo) — valeur la plus élevée, signalant une optimisation agressive pour les connexions haut débit
|
||||
- **Safari** : 2 097 152 octets (2 Mo) — intermédiaire
|
||||
- **Go net/http** : 4 194 304 octets (4 Mo) — non-navigateur, valeur élevée
|
||||
- **Firefox** : 131 072 octets (128 Ko) — valeur conservatrice
|
||||
- **curl** : 65 535 octets (64 Ko) — valeur par défaut minimale du standard
|
||||
|
||||
**Explication de INITIAL_WINDOW_SIZE** : ce paramètre fixe la taille initiale de la fenêtre de contrôle de flux pour les nouveaux streams. Une grande valeur (Chrome : 6 Mo) permet au serveur d'envoyer davantage de données avant d'attendre un accusé de réception `WINDOW_UPDATE`, optimisant les connexions à haute bande passante. Une petite valeur (curl : 64 Ko) limite le débit mais réduit les exigences mémoire. Cette différence reflète les priorités de conception : Chrome est optimisé pour la performance perçue par l'utilisateur sur des réseaux modernes ; curl est un outil en ligne de commande dont la sobriété mémoire est une contrainte historique.
|
||||
|
||||
#### Frame WINDOW_UPDATE
|
||||
|
||||
La frame **WINDOW_UPDATE** ([RFC 7540 §6.9](https://www.rfc-editor.org/rfc/rfc7540#section-6.9)) met à jour le crédit de contrôle de flux au niveau de la connexion (stream ID 0) ou d'un stream particulier. Elle est envoyée par le client après le SETTINGS initial pour élargir la fenêtre de réception globale. Sa valeur constitue une signature extrêmement stable par implémentation :
|
||||
|
||||
| Client | Valeur WINDOW_UPDATE | Tolérance |
|
||||
|-----------------|-----------------------|-----------|
|
||||
| Chrome 119+ | 15 663 105 | ±1 000 |
|
||||
| Firefox 90+ | 12 517 377 | ±1 000 |
|
||||
| Safari 15+ | 10 420 225 | ±1 000 |
|
||||
| curl 8.x | absent (0) | — |
|
||||
| Python httpx | absent (0) | — |
|
||||
| Go net/http | 1 073 676 289 | ±1 000 |
|
||||
|
||||
L'absence de WINDOW_UPDATE (valeur 0) indique avec certitude un outil non-navigateur : aucun navigateur grand public n'omet ce frame dans son handshake H2. Go net/http envoie une valeur exceptionnellement grande (≈1 Go) correspondant à l'augmentation maximale de fenêtre autorisée par le standard.
|
||||
|
||||
#### Ordre des pseudo-headers
|
||||
|
||||
HTTP/2 remplace la ligne de requête HTTP/1.1 (par exemple `GET /chemin HTTP/1.1`) par des **champs pseudo-header** préfixés par deux points. Ces pseudo-headers sont définis par [RFC 7540 §8.1.2](https://www.rfc-editor.org/rfc/rfc7540#section-8.1.2) :
|
||||
|
||||
- `:method` (**:m**) : verbe HTTP (GET, POST, etc.)
|
||||
- `:authority` (**:a**) : équivalent de l'en-tête `Host`
|
||||
- `:scheme` (**:s**) : schéma de l'URI (http, https)
|
||||
- `:path` (**:p**) : chemin et query string de l'URI
|
||||
|
||||
Les pseudo-headers **doivent** apparaître avant les en-têtes réguliers dans les frames HEADERS. Leur ordre entre eux n'est pas imposé par le standard mais est déterminé par l'implémentation HTTP/2 et reste **stable par version de client**. Cet ordre constitue donc une signature passive supplémentaire :
|
||||
|
||||
| Client | Ordre observé | Notation abrégée |
|
||||
|-----------------|---------------|------------------|
|
||||
| Chrome 119+ | method, authority, scheme, path | `masp` |
|
||||
| Firefox 90+ | method, path, scheme, authority | `mpsa` |
|
||||
| Safari 15+ | method, authority, scheme, path | `masp` |
|
||||
| curl 8.x | method, path, scheme, authority | `mpsa` |
|
||||
|
||||
Source : [Bartlett, Cloudflare 2023](https://blog.cloudflare.com/cloudflare-bot-management-machine-learning-and-more/).
|
||||
|
||||
**Note sur les valeurs observées vs. références Cloudflare** : les captures réelles effectuées par `ja4ebpf` montrent des écarts avec les données de référence Cloudflare pour certaines implémentations. Firefox présente un ordre `mpsa` (method, path, scheme, authority) dans nos captures, et non `mpas` (method, path, authority, scheme) comme rapporté par Cloudflare. Safari présente un ordre `masp` identique à Chrome dans nos captures, et non `mspa`. Safari envoie `INITIAL_WINDOW_SIZE=65535` et `WINDOW_UPDATE=10485760` dans nos observations, contre respectivement 2 097 152 et 10 420 225 dans les données Cloudflare. Ces écarts peuvent résulter de différences de version (nos captures Safari correspondent à WebKit ≥ 613.1.x sur macOS/iOS), de variantes de build, ou de l'évolution des piles HTTP/2 entre les mesures. Les signatures dans `browser_signatures.py` reflètent nos valeurs capturées réelles, garantissant la cohérence du scoring en production.
|
||||
|
||||
**Cas httpcloak** : httpcloak reproduit correctement le SETTINGS H2 de Chrome et la valeur WINDOW_UPDATE de Chrome, mais conserve l'ordre pseudo-header de curl (`mpsa`) au lieu de Chrome (`masp`). Résultat : score browser_matcher Chrome ≈ 0,60 — zone grise. La pénalité de zone grise (voir §3.9.3) réduit le score final ML sans le neutraliser totalement. Note : `mpsa` est l'ordre réel capturé pour Firefox dans notre infrastructure — httpcloak peut donc tromper partiellement la dimension pseudo-header, mais les dimensions SETTINGS et WINDOW_UPDATE révèlent l'incohérence.
|
||||
|
||||
#### Frames PRIORITY
|
||||
|
||||
Les frames **PRIORITY** ([RFC 7540 §6.3](https://www.rfc-editor.org/rfc/rfc7540#section-6.3)) établissent un arbre de dépendances pour l'ordonnancement des streams HTTP/2. Elles permettent au client de communiquer au serveur ses priorités de rendu — par exemple, les ressources CSS bloquantes reçoivent une priorité plus élevée que les images de bas de page.
|
||||
|
||||
Comportement par famille :
|
||||
|
||||
- **Firefox 90+** : envoie des frames PRIORITY de démarrage pour les streams 3, 5, 7, 9, 11, 13, formant un arbre de priorités préétabli pour les ressources anticipées
|
||||
- **Chrome ≥119** : n'envoie **pas** de frames PRIORITY — migré vers [RFC 9218 PRIORITY_UPDATE](https://www.rfc-editor.org/rfc/rfc9218), qui transmet les priorités via des en-têtes HTTP plutôt que des frames dédiées
|
||||
- **Safari** : n'envoie pas de frames PRIORITY
|
||||
- **curl** : n'envoie pas de frames PRIORITY
|
||||
- **Go net/http** : n'envoie pas de frames PRIORITY
|
||||
|
||||
La présence de frames PRIORITY est donc un **marqueur exclusif de Firefox**. Leur absence ne discrimine pas entre Chrome, Safari, curl et Go — elle doit être combinée avec les autres dimensions.
|
||||
|
||||
#### Stabilité des empreintes H2
|
||||
|
||||
La signature H2 de Chrome est stable depuis la version 119 (novembre 2023) jusqu'à la version 142 (2026) — plus de deux ans de stabilité. Ceci contraste avec JA4 TLS qui change à chaque mise à jour de suite de chiffrement. Cette stabilité s'explique par le fait que les paramètres H2 sont liés aux décisions d'architecture réseau du moteur de rendu Blink/Chrome, qui évoluent beaucoup moins fréquemment que la politique TLS.
|
||||
|
||||
### 3.9.2 Architecture du moteur browser_matcher
|
||||
|
||||
Le moteur browser_matcher est structuré en quatre modules Python distincts : la base de données de signatures statiques, la logique de scoring statique, le moteur de profiling dynamique hors-ligne, et le scorer dynamique temps réel.
|
||||
|
||||
#### Module browser_signatures.py
|
||||
|
||||
Ce module constitue la **base de données de signatures** par famille de navigateur. Chaque entrée est un objet structuré définissant :
|
||||
|
||||
```python
|
||||
# Extrait de browser_signatures.py — signature Chrome
|
||||
BROWSER_SIGNATURES["Chrome"] = {
|
||||
"h2_settings_exact": {
|
||||
1: 65536, # HEADER_TABLE_SIZE
|
||||
2: 0, # ENABLE_PUSH (désactivé)
|
||||
4: 6291456, # INITIAL_WINDOW_SIZE
|
||||
6: 262144, # MAX_HEADER_LIST_SIZE
|
||||
},
|
||||
"h2_settings_forbidden_keys": [3, 5], # MAX_CONCURRENT_STREAMS et MAX_FRAME_SIZE absents
|
||||
"h2_window_update": 15663105,
|
||||
"h2_window_update_tolerance": 1000,
|
||||
"h2_priority_frames_expected": False, # Chrome ≥119 utilise PRIORITY_UPDATE (RFC 9218)
|
||||
"pseudo_header_order": "m,a,s,p",
|
||||
"tls": {"ja4_families": ["Chromium", "Chrome", "Edge"], "grease_expected": True},
|
||||
"headers_sec_fetch_required": True,
|
||||
"headers_ch_ua_required": True,
|
||||
"accept_language_required": True,
|
||||
}
|
||||
|
||||
# Signature Firefox (valeurs capturées réelles)
|
||||
BROWSER_SIGNATURES["Firefox"] = {
|
||||
"h2_settings_exact": {1: 65536, 4: 131072, 5: 16384},
|
||||
"h2_settings_forbidden_keys": [2, 3, 6],
|
||||
"h2_window_update": 12517377,
|
||||
"pseudo_header_order": "m,p,s,a", # ordre réel capturé (pas m,p,a,s)
|
||||
"tls": {"ja4_families": ["Firefox"], "grease_expected": False},
|
||||
"headers_ch_ua_required": False, # Firefox ne supporte pas les Client Hints
|
||||
}
|
||||
|
||||
# Signature Safari (valeurs capturées réelles)
|
||||
BROWSER_SIGNATURES["Safari"] = {
|
||||
"h2_settings_exact": {1: 4096, 3: 100, 4: 65535},
|
||||
"h2_settings_forbidden_keys": [2, 5, 6],
|
||||
"h2_window_update": 10485760,
|
||||
"pseudo_header_order": "m,a,s,p", # identique à Chrome (WU=10485760 distingue)
|
||||
"tls": {"ja4_families": ["Safari"], "grease_expected": False},
|
||||
"headers_sec_fetch_forbidden": True, # Présence = incohérence
|
||||
}
|
||||
```
|
||||
|
||||
Le champ `h2_settings_exact` est désormais consommé directement par `_d1_h2_settings()` via les colonnes individuelles de `view_ai_features_1h` (`h2_header_table_size`, `h2_enable_push`, `h2_initial_window_size`, `h2_max_header_list_size`, etc.). La comparaison colonne par colonne permet de scorer partiellement un fingerprint inconnu du dictionnaire mais structurellement proche (ex. variante de navigateur non encore répertoriée).
|
||||
|
||||
**Implémentation de `_d1_h2_settings()` — algorithme de scoring** :
|
||||
|
||||
```python
|
||||
# Pour chaque clé attendue (h2_settings_exact) : val_col == val_attendue → 1, sinon 0
|
||||
# Pour chaque clé interdite (h2_settings_forbidden_keys) : col < 0 (absent) → 1, sinon 0
|
||||
direct_score = nb_vérifications_réussies / nb_total_vérifications
|
||||
base = direct_score × 0.60 + dict_match × 0.30 + h2_ja4_coherence × 0.10
|
||||
```
|
||||
|
||||
**Avantage vs l'approche dict-only** : un fingerprint légèrement modifié (ex. une variante de Chrome avec un champ SETTINGS supplémentaire) serait rejeté par le dictionnaire (dict_match=0) mais obtiendrait quand même un score D1 élevé si les paramètres clés sont corrects. Par exemple, un client envoyant `1:65536, 2:0, 4:6291456, 6:262144` (4 clés Chrome attendues) + `3:200` (clé interdite) obtiendrait direct_score = 5/6 × 0.60 = 0.50 au lieu de 0 avec le dict seul.
|
||||
|
||||
**Fallback** : si les colonnes individuelles sont absentes du DataFrame (compatibilité ascendante), la fonction revient au comportement original `dict_match × 0.80 + h2_ja4_coherence × 0.20`.
|
||||
|
||||
#### Module browser_matcher.py
|
||||
|
||||
Ce module implémente le **moteur de scoring**. Il calcule un score de correspondance pour chaque famille de navigateur connue, sur 7 dimensions pondérées :
|
||||
|
||||
**Table des 7 dimensions de scoring avec poids et logique de calcul** :
|
||||
|
||||
| # | Dimension | Poids | Logique de scoring |
|
||||
|---|-----------|-------|--------------------|
|
||||
| 1 | H2 SETTINGS match | 0,30 | **Comparaison directe par paramètre** (colonnes individuelles) : chaque clé attendue exacte → 1,0 ; chaque clé interdite absente → 1,0 ; score = proportion de vérifications réussies. Pondération : `direct × 0,60 + dict_lookup × 0,30 + ja4_coherence × 0,10`. Fallback dict-only si colonnes indisponibles |
|
||||
| 2 | H2 WINDOW_UPDATE | 0,15 | `|observé − attendu| ≤ tolérance` → 1,0 ; absent (=0) → 0,0 ; hors tolérance → 0,0 |
|
||||
| 3 | Ordre pseudo-headers | 0,15 | Correspondance exacte → 1,0 ; absent → neutre 0,5 ; sinon 0,0 |
|
||||
| 4 | Frames H2 PRIORITY | 0,10 | Présence/absence correspond à l'attendu → 1,0 ; pas de données H2 → neutre 0,5 |
|
||||
| 5 | Cohérence en-têtes HTTP | 0,15 | Accept-Language (+0,25) + Sec-Fetch cohérent (+0,25) + Sec-CH-UA cohérent (+0,25) + bonus (+0,25) |
|
||||
| 6 | Structure TLS | 0,10 | Famille JA4 correcte (×0,7) + TLS 1.3 (×0,3) |
|
||||
| 7 | JA4 dict lookup | 0,05 | Correspondance dans `dict_browser_ja4` pour cette famille → 1,0 ; sinon 0,0 |
|
||||
|
||||
**Formule générale** :
|
||||
|
||||
```
|
||||
score_family = Σ (weight_i × score_dim_i) ∈ [0, 1]
|
||||
```
|
||||
|
||||
Le score est calculé pour chaque famille (Chrome, Firefox, Safari). Le résultat maximal et la famille correspondante sont retenus. Les patterns NON_BROWSER_SIGNATURES sont appliqués en post-traitement : si un pattern négatif est détecté, `score_family = min(score_family, 0.30)` quelle que soit la famille.
|
||||
|
||||
#### Pseudo-code du pipeline browser_matcher
|
||||
|
||||
```python
|
||||
def match_browser(session: SessionFeatures) -> BrowserMatchResult:
|
||||
scores = {}
|
||||
for family, sig in BROWSER_SIGNATURES.items():
|
||||
s = 0.0
|
||||
# Dimension 1 : H2 SETTINGS
|
||||
s += 0.30 * score_h2_settings(session.h2_settings, sig)
|
||||
# Dimension 2 : WINDOW_UPDATE
|
||||
s += 0.15 * score_window_update(session.h2_window_update, sig)
|
||||
# Dimension 3 : pseudo-headers
|
||||
s += 0.15 * score_pseudo_order(session.h2_pseudo_order, sig)
|
||||
# Dimension 4 : PRIORITY frames
|
||||
s += 0.10 * score_priority_frames(session.h2_has_priority, sig)
|
||||
# Dimension 5 : en-têtes HTTP
|
||||
s += 0.15 * score_headers(session.headers, sig)
|
||||
# Dimension 6 : TLS structure
|
||||
s += 0.10 * score_tls(session.tls, sig)
|
||||
# Dimension 7 : JA4 dict
|
||||
s += 0.05 * score_ja4_dict(session.ja4, sig)
|
||||
scores[family] = s
|
||||
|
||||
# Patterns négatifs
|
||||
for pattern in NON_BROWSER_SIGNATURES:
|
||||
if pattern.matches(session):
|
||||
for family in scores:
|
||||
scores[family] = min(scores[family], 0.30)
|
||||
|
||||
best_family = max(scores, key=scores.get)
|
||||
best_score = scores[best_family]
|
||||
return BrowserMatchResult(
|
||||
family=best_family if best_score >= THRESHOLDS[best_family] else "",
|
||||
score_chrome=scores.get("chrome", 0.0),
|
||||
score_firefox=scores.get("firefox", 0.0),
|
||||
score_safari=scores.get("safari", 0.0),
|
||||
match_score=best_score,
|
||||
)
|
||||
```
|
||||
|
||||
### 3.9.3 Logique de décision et intégration dans le pipeline
|
||||
|
||||
#### Seuils de décision par famille
|
||||
|
||||
Les seuils de confiance sont différenciés par famille en raison de la variabilité inter-version observée :
|
||||
|
||||
| Famille | Seuil de confiance | Justification |
|
||||
|----------|--------------------|---------------|
|
||||
| Chrome | ≥ 0,72 | Grande stabilité H2 depuis v119 ; seuil élevé car JA4 Chrome très imitée |
|
||||
| Firefox | ≥ 0,68 | Frames PRIORITY discriminantes ; seuil légèrement plus bas |
|
||||
| Safari | ≥ 0,68 | MAX_CONCURRENT_STREAMS=100 et ENABLE_CONNECT_PROTOCOL=1 très spécifiques |
|
||||
|
||||
#### Flowchart de trifurcation (ASCII)
|
||||
|
||||
```
|
||||
Session entrante
|
||||
│
|
||||
▼
|
||||
has_xff = 1 ?
|
||||
┌────┴────┐
|
||||
│Oui │Non
|
||||
▼ ▼
|
||||
Neutraliser Dimensions H2
|
||||
dim. H2 → complètes
|
||||
0.5 par
|
||||
défaut
|
||||
│ │
|
||||
└────┬────┘
|
||||
▼
|
||||
Calcul score_family
|
||||
(7 dimensions)
|
||||
│
|
||||
▼
|
||||
Patterns NON_BROWSER ?
|
||||
┌────┴────┐
|
||||
│Oui │Non
|
||||
▼ ▼
|
||||
Cap score Score intact
|
||||
à 0.30 │
|
||||
│ │
|
||||
└────┬────┘
|
||||
▼
|
||||
best_score ≥ seuil ?
|
||||
┌────┴────────────────┐
|
||||
│Oui │Non
|
||||
▼ │
|
||||
browser_family_detected │
|
||||
= best_family ▼
|
||||
│ 0.45 ≤ best_score < seuil ?
|
||||
│ ┌─────┴─────┐
|
||||
│ │Oui │Non
|
||||
│ ▼ ▼
|
||||
│ ZONE GRISE Aucun effet
|
||||
│ final_score (best_score < 0.45)
|
||||
│ × (1 - 0.5 × best_score)
|
||||
│ │
|
||||
└──────┬──────┘
|
||||
▼
|
||||
Sortie : features browser_match_*
|
||||
→ Vecteur feature ML
|
||||
```
|
||||
|
||||
#### Traitement zone grise [0,45 ; seuil[
|
||||
|
||||
Lorsque le meilleur score est en zone grise (supérieur à 0,45 mais inférieur au seuil de confiance de la famille), le score ML final est atténué par :
|
||||
|
||||
```
|
||||
final_score = final_score × (1 − 0.5 × match_score)
|
||||
```
|
||||
|
||||
Cette atténuation ne neutralise pas le score ML mais signal une incertitude sur l'authenticité du navigateur déclaré. En dessous de 0,45 : aucun effet, la signature est trop faible pour être exploitée.
|
||||
|
||||
**Cas httpcloak illustratif** :
|
||||
- H2 SETTINGS Chrome : correct → +0,30
|
||||
- WINDOW_UPDATE Chrome : correct → +0,15
|
||||
- Pseudo-headers : `mpsa` (curl) vs `masp` (Chrome attendu) → 2/4 positions → 0,0
|
||||
- PRIORITY frames : absentes (correct pour Chrome) → +0,10
|
||||
- En-têtes HTTP : sec-fetch-* absents (httpcloak) → +0,075
|
||||
- TLS : JA4 Chrome-like → +0,08
|
||||
- JA4 dict : correspondance → +0,05
|
||||
- **Total ≈ 0,605** → zone grise [0,45 ; 0,72[ → atténuation active
|
||||
|
||||
#### Gestion CDN/reverse proxy (has_xff = 1)
|
||||
|
||||
Lorsqu'un CDN ou reverse proxy est détecté (`has_xff = 1`), le CDN rétablit sa propre connexion H2 vers le serveur Apache d'origine. Les paramètres H2 observés (SETTINGS, WINDOW_UPDATE, pseudo-headers, PRIORITY) reflètent alors le CDN, non le client final. Dans ce cas :
|
||||
|
||||
- Les dimensions H2 (poids total 0,70) sont **neutralisées à 0,50** (valeur neutre)
|
||||
- Le poids libéré est redistribué équitablement entre la dimension en-têtes HTTP (+0,35) et la dimension TLS (+0,35)
|
||||
- Seules les dimensions TLS et en-têtes HTTP subsistent, avec des pondérations ajustées
|
||||
|
||||
### 3.9.4 Nouvelles features dérivées
|
||||
|
||||
Le module `browser_matcher` génère les features suivantes dans le vecteur feature du pipeline :
|
||||
|
||||
| Feature | Type | Description | Statut |
|
||||
|---------|------|-------------|--------|
|
||||
| `browser_match_chrome` | Float32 | Score de correspondance Chrome (0–1) | `[impl.]` |
|
||||
| `browser_match_firefox` | Float32 | Score de correspondance Firefox (0–1) | `[impl.]` |
|
||||
| `browser_match_safari` | Float32 | Score de correspondance Safari (0–1) | `[impl.]` |
|
||||
| `browser_match_max` | Float32 | max(chrome, firefox, safari) | `[impl.]` |
|
||||
| `browser_family_detected` | String | Famille détectée ou chaîne vide | `[impl.]` |
|
||||
| `h2_window_update_value` | UInt32 | Valeur WINDOW_UPDATE observée | `[impl.]` (colonne `h2_window_update` dans `http_logs`) |
|
||||
| `h2_has_priority_frames` | UInt8 | 1 si frames PRIORITY présentes | `[impl.]` (colonne `h2_has_priority` dans `http_logs`) |
|
||||
| `h2_pseudo_order` | String | Ordre observé (ex. `m,a,s,p`) | `[impl.]` (colonne `h2_pseudo_order` dans `http_logs`) |
|
||||
| `tls_h2_family_mismatch` | UInt8 | 1 si JA4 dit Chrome mais H2 SETTINGS dit Firefox/outil | `[impl.]` (feature F4) |
|
||||
|
||||
Les features `h2_window_update_value`, `h2_has_priority_frames` et `h2_pseudo_order` sont désormais capturées par ja4ebpf via le parser HTTP/2 du flux SSL_read déchiffré et stockées dans des colonnes individuelles de `ja4_logs.http_logs`. De plus, chaque paramètre SETTINGS HTTP/2 dispose de sa propre colonne (`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. La feature `tls_h2_family_mismatch` est implémentée dans le vecteur feature global (famille F4 — TLS features) et se calcule à partir des données JA4 existantes et des colonnes H2 individuelles disponibles dans `ja4_logs.http_logs`. Les features `browser_match_*` sont calculées par le module `browser_matcher.py` (497 lignes) à chaque cycle d'analyse, avec les signatures statiques de `browser_signatures.py` (165 lignes) et un rechargement dynamique optionnel depuis ClickHouse toutes les 24 heures. En parallèle, le module `browser_matcher_dynamic.py` (387 lignes) scores les sessions contre les profils auto-appris de `auto_browser_profiles` (§3.9.6), fournissant une double couverture : signatures connues (statique) + signatures observées (dynamique).
|
||||
|
||||
### 3.9.5 Maintenance des signatures
|
||||
|
||||
#### Stockage et rechargement
|
||||
|
||||
Les signatures sont stockées dans la table ClickHouse `ja4_processing.browser_h2_signatures`, rechargée toutes les 24 heures par le module Python. Structure de la table :
|
||||
|
||||
```sql
|
||||
CREATE TABLE ja4_processing.browser_h2_signatures
|
||||
(
|
||||
family String,
|
||||
version_min String,
|
||||
version_max String,
|
||||
h2_settings_json String, -- JSON sérialisé du dict {key_id: value}
|
||||
h2_settings_forbidden String, -- JSON array des key_ids interdits
|
||||
h2_window_update UInt32,
|
||||
h2_window_update_tolerance UInt32,
|
||||
h2_priority_expected UInt8,
|
||||
pseudo_header_order String, -- "m,a,s,p"
|
||||
tls_json String, -- JSON des paramètres TLS
|
||||
headers_required String, -- JSON array
|
||||
headers_forbidden String, -- JSON array
|
||||
created_at DateTime DEFAULT now(),
|
||||
is_active UInt8 DEFAULT 1
|
||||
)
|
||||
ENGINE = ReplacingMergeTree(created_at)
|
||||
ORDER BY (family, version_min);
|
||||
```
|
||||
|
||||
#### File d'examen pour signatures inconnues
|
||||
|
||||
Les sessions présentant une empreinte H2 inconnue (aucune famille avec score > 0,45) mais un comportement de type navigateur (browser_confidence ≥ 0,55, présence de sec-fetch-*, TLS 1.3) sont enregistrées dans une **file d'examen** (`ja4_processing.unknown_h2_fingerprints`) pour mise à jour progressive de la base de signatures lors des nouvelles versions de navigateurs.
|
||||
|
||||
```sql
|
||||
INSERT INTO ja4_processing.unknown_h2_fingerprints
|
||||
SELECT
|
||||
now() AS observed_at,
|
||||
src_ip,
|
||||
ja4_fingerprint,
|
||||
h2_settings_json,
|
||||
h2_window_update_value,
|
||||
h2_pseudo_order,
|
||||
h2_has_priority_frames,
|
||||
browser_confidence_score
|
||||
FROM current_session_features
|
||||
WHERE browser_match_max < 0.45
|
||||
AND browser_confidence_score >= 0.55
|
||||
AND tls_version = 'TLS1.3';
|
||||
```
|
||||
|
||||
### 3.9.6 Profiling dynamique automatique des navigateurs
|
||||
|
||||
`[impl.]` **Module profile_builder.py (614 lignes) + browser_matcher_dynamic.py (387 lignes)**
|
||||
|
||||
Le système de signatures statiques (§3.9.2) est robuste mais fragile face aux mises à jour de navigateurs : chaque changement de version peut modifier les valeurs SETTINGS ou WINDOW_UPDATE, nécessitant une mise à jour manuelle du dictionnaire. Le **profiling dynamique automatique** résout ce problème en apprenant les signatures directement à partir du trafic observé, sans intervention humaine.
|
||||
|
||||
#### Architecture du pipeline
|
||||
|
||||
Le pipeline s'articule en deux phases :
|
||||
|
||||
1. **Phase hors-ligne** (`profile_builder.py`, exécuté quotidiennement par cron) :
|
||||
- Lit la vue `view_h2_profiling_raw` (sessions H2 filtrées des 7 derniers jours, dédupliquées par IP)
|
||||
- Clusterise les sessions similaires via HDBSCAN (`min_cluster_size=1000`) sur un vecteur mixte (variables continues normalisées par StandardScaler + variables catégorielles brutes)
|
||||
- Calcule les centroïdes par cluster : moyenne et tolérance (`mean + 3σ`) pour les continues, mode pour les catégorielles
|
||||
- Labelise les clusters par analyse des User-Agents → familles `Auto_Chrome`, `Auto_Firefox`, `Auto_Safari`, `Auto_Unknown`
|
||||
- Fusionne les clusters redondants (même famille + même `pseudo_order` + `window_update` à < 5% d'écart)
|
||||
- Persiste les profils dans `ja4_processing.auto_browser_profiles` (ReplacingMergeTree, TTL 14 jours)
|
||||
|
||||
2. **Phase temps réel** (`browser_matcher_dynamic.py`, appelé à chaque cycle de 300s) :
|
||||
- Charge les profils en mémoire au démarrage (rafraîchissement toutes les 24h)
|
||||
- Pour chaque session, calcule un score de similarité pondéré contre tous les profils :
|
||||
|
||||
```
|
||||
score = Σ (poids_i × similarité_i) × confiance_volumétrique
|
||||
|
||||
Pondération :
|
||||
h2_window_update : 0.40 (le plus discriminant entre familles)
|
||||
pseudo_order : 0.40 (invariant par version mineure)
|
||||
h2_initial_window_size : 0.10
|
||||
h2_has_priority : 0.10
|
||||
|
||||
Similarité continue : sim = max(0, 1 - |x - μ| / max(|μ|, 1))
|
||||
Similarité catégorielle : sim = 1.0 si match exact, 0.0 sinon
|
||||
Confiance volumétrique : conf = min(1.0, log10(count_ips + 1) / 4)
|
||||
→ count_ips=10000 → confiance≈1.0 ; count_ips=100 → confiance≈0.50
|
||||
```
|
||||
|
||||
#### Rejet rapide
|
||||
|
||||
Le scoring intègre deux mécanismes de rejet rapide pour éviter les calculs inutiles :
|
||||
|
||||
1. **Incompatibilité pseudo_order** : si la session a `pseudo_order = mpsa` (curl) mais le profil exige `masp` (Chrome), le score est immédiatement 0. Ce signal est binaire et invariant — il ne peut pas être contourné sans reproduire exactement l'ordre des pseudo-headers du navigateur cible.
|
||||
|
||||
2. **Dépassement de tolérance** : si `|h2_window_update_session - h2_window_update_profil| > tolérance`, le score est 0. La tolérance est calculée comme `|μ| + 3σ` lors du clustering, garantissant un intervalle de confiance à 99.7%.
|
||||
|
||||
#### Vue ClickHouse d'extraction
|
||||
|
||||
La vue `view_h2_profiling_raw` (créée dans `13_h2_profiling.sql`) filtre le trafic bots évident et encode les variables catégorielles :
|
||||
|
||||
```sql
|
||||
-- Encodage pseudo_order → UInt8
|
||||
multiIf(
|
||||
h2_pseudo_order = 'm,a,s,p', 1, -- Chrome/Safari
|
||||
h2_pseudo_order = 'm,p,a,s', 2, -- Firefox (ancien)
|
||||
h2_pseudo_order = 'm,s,p,a', 3,
|
||||
h2_pseudo_order = 'm,p,s,a', 4, -- curl/Firefox
|
||||
h2_pseudo_order = 'm,a,p,s', 5,
|
||||
0 -- inconnu
|
||||
) AS pseudo_order_id
|
||||
```
|
||||
|
||||
Filtres appliqués : `h2_pseudo_order != ''` (HTTP/2 uniquement), `h2_window_update > 0` (exclut curl), `h2_initial_window_size NOT IN (-1, 65535)` (exclut absents et valeur typique curl), `header_user_agent != ''` (exclut scanners).
|
||||
|
||||
#### Cycle de vie des profils
|
||||
|
||||
Le profilage dynamique gère automatiquement l'obsolescence :
|
||||
|
||||
- **Rafraîchissement** : `last_seen_date` est mis à jour quotidiennement pour les profils dont le vecteur H2 correspond à des sessions observées dans les dernières 24h.
|
||||
- **Purge** : les profils où `last_seen_date < today() - 14` sont supprimés (navigateurs dépréciés, versions obsolètes). Cette TTL garantit que seules les signatures récentes sont utilisées pour le scoring.
|
||||
|
||||
Le TTL ClickHouse de 14 jours sur la table `auto_browser_profiles` assure une purge physique des partitions expirées lors du merge.
|
||||
|
||||
#### Fusion des clusters
|
||||
|
||||
La fusion des clusters redondants est critique pour éviter la prolifération de profils quasi-identiques. Deux clusters sont fusionnés si **les trois** conditions sont réunies :
|
||||
|
||||
1. Même `detected_family` (ex: `Auto_Chrome`)
|
||||
2. Même `pseudo_order_mode` (même ordre des pseudo-headers)
|
||||
3. `|window_update_A - window_update_B| / max(A, B) < 5%` (valeurs proches)
|
||||
|
||||
La fusion calcule la **moyenne pondérée** par `count_ips` :
|
||||
```
|
||||
nouvelle_moyenne = (mean_A × count_A + mean_B × count_B) / (count_A + count_B)
|
||||
nouvelle_tolérance = max(tol_A, tol_B) # on élargit le seuil
|
||||
```
|
||||
|
||||
#### Avantage sur les signatures statiques
|
||||
|
||||
Le profiling dynamique présente trois avantages décisifs :
|
||||
|
||||
1. **Adaptabilité** : les nouvelles versions de navigateurs sont automatiquement apprises dès qu'elles atteignent un volume suffisant (`min_cluster_size=1000` IPs uniques), sans mise à jour manuelle du dictionnaire.
|
||||
2. **Robustesse** : la tolérance `mean + 3σ` s'adapte à la variance réelle observée dans la population, contrairement à la tolérance fixe de 1000 du système statique.
|
||||
3. **Couverture** : les familles `Auto_Unknown` capturent les clients H2 qui ne correspondent à aucune famille connue mais qui présentent un comportement de navigateur légitime (nouvelles implémentations, clients exotiques).
|
||||
|
||||
|
||||
688
docs/thesis/05_features.md
Normal file
688
docs/thesis/05_features.md
Normal file
@ -0,0 +1,688 @@
|
||||
[<< Sommaire](README.md) | [Suivant >>](06_techniques_avancees.md)
|
||||
|
||||
---
|
||||
|
||||
|
||||
## 4. Taxonomie des features de détection
|
||||
|
||||
Les 96 features sont organisées en 8 familles couvrant les couches L3→L7. Chaque feature est définie par sa formule de calcul, son signal discriminant, ses plages de valeurs typiques (humain vs. bot), et son statut d'implémentation.
|
||||
|
||||
**Légende des statuts** :
|
||||
- `[impl.]` Implémenté et validé en production
|
||||
- `[partiel]` Partiellement implémenté
|
||||
- `[todo]` Non encore implémenté
|
||||
|
||||
---
|
||||
|
||||
### Famille 1 : Volumétrie et vitesse (4 features)
|
||||
|
||||
La famille 1 capture les signaux de volume et de cadence bruts. Ces features sont les plus simples mais restent efficaces contre les bots non sophistiqués et constituent une dimension importante du vecteur ML.
|
||||
|
||||
#### hits `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(requêtes HTTP) par session dans la fenêtre de 300 s`
|
||||
|
||||
**Signal** : un volume de requêtes anormalement élevé en un court laps de temps est le signal de bot le plus basique. Les navigateurs humains sont limités par la vitesse de lecture et d'interaction.
|
||||
|
||||
| Type de client | Plage typique |
|
||||
|---------------|---------------|
|
||||
| Navigation humaine | 10–200 requêtes/5min |
|
||||
| Navigateur avec auto-refresh agressif | 200–500 |
|
||||
| Bot crawler lent | 200–1000 |
|
||||
| Bot scanner / scraper | 1000–50 000 |
|
||||
| DDoS HTTP flood | > 100 000 |
|
||||
|
||||
**Note** : hits seul n'est pas suffisant (un humain téléchargeant une page complexe peut générer 200+ requêtes pour les ressources). La combinaison hits × asset_ratio × hit_velocity est nécessaire.
|
||||
|
||||
#### hit_velocity `[impl.]`
|
||||
|
||||
**Calcul** : `hits / session_duration_seconds`
|
||||
|
||||
**Signal** : la vitesse de requêtes par seconde distingue les bots des humains indépendamment de la durée de session. Un humain lisant une page met plusieurs secondes entre deux clics.
|
||||
|
||||
| Type de client | Plage typique |
|
||||
|---------------|---------------|
|
||||
| Lecture humaine | 0.1–2 req/s |
|
||||
| Navigation rapide humaine | 2–5 req/s |
|
||||
| Bot crawler lent (poli) | 1–10 req/s |
|
||||
| Bot scraper agressif | 10–100 req/s |
|
||||
| DDoS / flood | > 100 req/s |
|
||||
|
||||
#### max_keepalives `[impl.]`
|
||||
|
||||
**Calcul** : `MAX(requêtes HTTP par connexion TCP) sur la session`
|
||||
|
||||
**Signal** : les navigateurs humains utilisent HTTP Keep-Alive de manière modérée (5–50 requêtes par connexion). Les bots mal configurés ouvrent une nouvelle connexion TCP par requête (max_keepalives = 1, ce qui est exceptionnel pour un navigateur). Certains bots pipeline au contraire entassent des milliers de requêtes dans une seule connexion.
|
||||
|
||||
| Type de client | Plage typique |
|
||||
|---------------|---------------|
|
||||
| Navigateur humain moderne | 10–50 par connexion |
|
||||
| Scraper simple (connexion par req) | 1 |
|
||||
| Bot pipeline agressif | > 100 |
|
||||
|
||||
**Implémentation** : ja4ebpf incrémente un compteur via le gestionnaire de corrélation in-memory par connexion TCP (`src_ip:src_port`) à chaque requête HTTP reçue. Le maximum sur la session est agrégé dans `view_ai_features_1h`.
|
||||
|
||||
#### count_login_post `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(requêtes POST vers /login, /signin, /auth, /wp-login.php, /account/login, /session/new)`
|
||||
|
||||
**Signal** : compteur direct des tentatives d'authentification. Un utilisateur humain se connecte 0–2 fois par session. Un outil de credential stuffing teste des centaines ou milliers de couples identifiant/mot de passe.
|
||||
|
||||
| Type de client | Plage typique |
|
||||
|---------------|---------------|
|
||||
| Utilisateur humain | 0–2 |
|
||||
| Test fonctionnel automatisé | 5–20 |
|
||||
| Credential stuffing | > 100 |
|
||||
|
||||
---
|
||||
|
||||
### Famille 2 : Diversité et exploration (7 features)
|
||||
|
||||
La famille 2 capture la diversité des patterns d'accès, distinguant les comportements systématiques des comportements organiques.
|
||||
|
||||
#### fuzzing_index `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(DISTINCT query_params) / COUNT(DISTINCT paths)`
|
||||
|
||||
**Signal** : un fuzzer d'API teste de nombreuses valeurs de paramètres sur un ensemble restreint d'endpoints (ratio élevé). La navigation normale génère environ autant de chemins distincts que de combinaisons de paramètres distinctes (ratio ≈ 1).
|
||||
|
||||
| Type de client | Plage typique |
|
||||
|---------------|---------------|
|
||||
| Navigation normale | 0.5–2 |
|
||||
| Crawler d'exploration | 1–5 |
|
||||
| Fuzzer d'API ciblé | 10–100 |
|
||||
| Scanner de vulnérabilités | > 50 |
|
||||
|
||||
#### path_diversity_ratio `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(DISTINCT paths) / hits`
|
||||
|
||||
**Signal** : un crawler systématique accède à une URL unique à chaque requête (ratio ≈ 1). Un bot répétitif frappe toujours le même endpoint (ratio ≈ 0.01). La navigation humaine est intermédiaire.
|
||||
|
||||
| Type de client | Plage typique |
|
||||
|---------------|---------------|
|
||||
| Navigation humaine | 0.3–0.8 |
|
||||
| Crawler exhaustif | ≈ 1.0 |
|
||||
| Bot ciblé (même URL) | < 0.05 |
|
||||
| Attaque flood sur un endpoint | ≈ 0.001 |
|
||||
|
||||
#### url_depth_variance `[impl.]`
|
||||
|
||||
**Calcul** : `VAR(COUNT('/' in path) per request)`
|
||||
|
||||
**Signal** : un crawler stratifié (par profondeur) maintient une profondeur d'URL uniforme dans chaque vague d'exploration, produisant une faible variance. La navigation humaine combine des accès superficiels (homepage, catégories) et profonds (pages produit, articles), produisant une variance naturellement élevée.
|
||||
|
||||
#### distinct_ja4_count `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(DISTINCT ja4_fingerprint) par session`
|
||||
|
||||
**Signal** : un humain utilisant un seul navigateur produit exactement 1 JA4 distinct. Une flotte de bots utilisant différentes stacks TLS ou un bot qui randomise sa configuration produit plusieurs JA4 distincts dans la même session.
|
||||
|
||||
| Type de client | Plage typique |
|
||||
|---------------|---------------|
|
||||
| Un seul navigateur | 1 |
|
||||
| Navigateur avec extension VPN (TLS différent) | 2 |
|
||||
| Flotte bot multi-outils | 5–20 |
|
||||
| Bot à randomisation intensive | > 10 |
|
||||
|
||||
#### distinct_header_orders `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(DISTINCT header_order_signature) par session`
|
||||
|
||||
**Signal** : chaque bibliothèque HTTP client envoie ses en-têtes dans un ordre fixe. Un navigateur humain produit toujours la même signature d'ordre. Un bot qui alterne entre plusieurs bibliothèques (requests, httpx, aiohttp) révèle cette rotation par la variabilité de l'ordre des en-têtes.
|
||||
|
||||
#### is_ua_rotating `[impl.]`
|
||||
|
||||
**Calcul** : `1 si COUNT(DISTINCT User-Agent) > 1 dans la session, sinon 0`
|
||||
|
||||
**Signal** : un navigateur humain a toujours le même User-Agent. Tout changement de User-Agent dans une session est un signal de rotation artificielle. Feature binaire haute précision.
|
||||
|
||||
#### ja4_drift_ratio `[impl.]` (§5.5)
|
||||
|
||||
*Voir définition complète dans la Famille 8 (Features comportementales avancées). Comptabilisée ici pour des raisons historiques d'agrégation.*
|
||||
|
||||
---
|
||||
|
||||
### Famille 3 : Authenticité protocolaire (12 features)
|
||||
|
||||
La famille 3 évalue la conformité du client aux standards des navigateurs modernes à travers les en-têtes HTTP et les signaux d'authenticité.
|
||||
|
||||
#### modern_browser_score `[impl.]`
|
||||
|
||||
**Calcul** : score composite sur la présence des marqueurs caractéristiques des navigateurs modernes : HTTPS, HTTP/2, Brotli, Fetch Metadata headers, Client Hints, Accept-Language, Cookie.
|
||||
|
||||
**Plage** : [0, 1] — 0 = aucun marqueur moderne, 1 = tous les marqueurs présents et cohérents.
|
||||
|
||||
#### ua_ch_mismatch `[impl.]`
|
||||
|
||||
**Calcul** : `1 si (Sec-CH-UA-Mobile: ?1 ET User-Agent contient "Windows"/"Linux"/"Macintosh") OU (User-Agent Chrome récent ET Sec-CH-UA absent)`
|
||||
|
||||
**Signal** : incohérence entre les Client Hints (réalité) et le User-Agent (déclaration).
|
||||
|
||||
#### has_accept_language `[impl.]`
|
||||
|
||||
**Calcul** : `1 si l'en-tête Accept-Language est présent dans la session (ratio > 0.8)`
|
||||
|
||||
**Signal** : en-tête toujours présent pour les navigateurs humains.
|
||||
|
||||
#### has_cookie `[impl.]`
|
||||
|
||||
**Calcul** : `1 si l'en-tête Cookie est présent dans au moins une requête de la session`
|
||||
|
||||
**Signal** : un navigateur humain maintient les cookies de session, les tokens CSRF, etc. Un bot stateless n'a aucun cookie.
|
||||
|
||||
#### has_referer `[impl.]`
|
||||
|
||||
**Calcul** : `ratio de requêtes avec Referer présent dans la session`
|
||||
|
||||
**Signal** : les humains naviguent en suivant des liens (Referer présent). Les bots accèdent directement aux URLs.
|
||||
|
||||
#### sec_fetch_absence_rate `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(requêtes sans aucun Sec-Fetch-*) / hits`
|
||||
|
||||
**Signal** : les Fetch Metadata headers (Sec-Fetch-Site, Mode, Dest) sont présents dans 100 % des requêtes des navigateurs Chrome/Firefox depuis 2019–2021. Leur absence sur une requête qui prétend provenir d'un navigateur moderne est un signal fort.
|
||||
|
||||
#### generic_accept_ratio `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(requêtes avec Accept: */*) / hits`
|
||||
|
||||
**Signal** : les navigateurs envoient des en-têtes Accept spécifiques selon le type de ressource (`text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8` pour les documents). Un Accept générique `*/*` est caractéristique des bibliothèques HTTP programmatiques.
|
||||
|
||||
#### missing_accept_enc_ratio `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(requêtes sans Accept-Encoding: br) / hits`
|
||||
|
||||
**Signal** : l'absence de Brotli dans Accept-Encoding signale un client pré-2016 ou non-navigateur.
|
||||
|
||||
#### header_count `[impl.]`
|
||||
|
||||
**Calcul** : `AVG(nombre d'en-têtes par requête) sur la session`
|
||||
|
||||
**Signal** : les navigateurs modernes envoient en moyenne 15–25 en-têtes par requête. Les bibliothèques HTTP minimalistes en envoient 3–7.
|
||||
|
||||
| Type de client | Plage typique |
|
||||
|---------------|---------------|
|
||||
| Chrome 119+ | 18–25 |
|
||||
| Firefox 120+ | 16–22 |
|
||||
| Python requests | 4–7 |
|
||||
| curl | 3–5 |
|
||||
| Scanner réseau | 1–3 |
|
||||
|
||||
#### header_order_confidence `[impl.]`
|
||||
|
||||
**Calcul** : score de similarité de l'ordre des en-têtes de la session avec les profils connus (Chrome, Firefox, Safari).
|
||||
|
||||
**Plage** : [0, 1] — 1 = correspondance exacte avec un profil connu.
|
||||
|
||||
#### sec_ch_mobile_mismatch `[impl.]`
|
||||
|
||||
**Calcul** : `1 si Sec-CH-UA-Platform indique "Android"/"iOS" mais User-Agent ou MSS TCP indique desktop`
|
||||
|
||||
**Signal** : incohérence cross-layer entre le signal réseau (TCP MSS caractéristique du carrier mobile) et le signal applicatif (CH-UA-Platform).
|
||||
|
||||
#### is_fake_navigation `[impl.]`
|
||||
|
||||
**Calcul** : feature composite — `1 si Sec-Fetch-Dest: document ET asset_ratio < 0.05 ET has_referer < 0.1`
|
||||
|
||||
**Signal** : session qui prétend naviguer (Sec-Fetch présent) mais qui ne charge aucune ressource (pas d'assets) et n'a aucun Referer — comportement impossible pour un vrai navigateur.
|
||||
|
||||
---
|
||||
|
||||
### Famille 4 : Cohérence cross-layer (14 features)
|
||||
|
||||
La famille 4 est le cœur de l'approche multi-couches : elle détecte les incohérences entre les signaux TCP (L4), TLS (L5) et HTTP (L7) qui trahissent les outils d'imitation partielle.
|
||||
|
||||
#### alpn_http_mismatch `[impl.]`
|
||||
|
||||
**Calcul** : `1 si ALPN négocié = h2 mais requêtes HTTP reçues en HTTP/1.1, OU ALPN = http/1.1 mais connexion HTTP/2`
|
||||
|
||||
**Signal** : le protocole applicatif réel ne correspond pas au protocole annoncé dans la négociation TLS.
|
||||
|
||||
#### is_alpn_missing `[impl.]`
|
||||
|
||||
**Calcul** : `1 si l'extension ALPN est absente du ClientHello TLS`
|
||||
|
||||
**Signal** : absence d'ALPN = client non-navigateur ou bibliothèque TLS minimale.
|
||||
|
||||
#### sni_host_mismatch `[impl.]`
|
||||
|
||||
**Calcul** : `1 si SNI TLS ≠ en-tête HTTP Host`
|
||||
|
||||
**Signal** : domain fronting ou proxy mal configuré.
|
||||
|
||||
#### mss_mobile_mismatch `[impl.]`
|
||||
|
||||
**Calcul** : `1 si MSS TCP ∈ {1340, 1392, 1350} (MSS carrier mobile) ET User-Agent Desktop`
|
||||
|
||||
**Signal** : trafic d'un appareil mobile routé via un bot se déclarant desktop.
|
||||
|
||||
#### tls12_ratio `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(connexions TLS 1.2) / COUNT(connexions TLS totales)`
|
||||
|
||||
**Signal** : Chrome et Firefox utilisent TLS 1.3 exclusivement depuis 2022 pour les connexions modernes. Un ratio TLS 1.2 élevé indique des bibliothèques ou configurations obsolètes.
|
||||
|
||||
#### http10_ratio `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(requêtes HTTP/1.0) / hits`
|
||||
|
||||
**Signal** : HTTP/1.0 est obsolète depuis HTTP/1.1 (1997). Sa présence signale un outil très ancien ou mal configuré.
|
||||
|
||||
#### tcp_jitter_variance `[impl.]`
|
||||
|
||||
**Calcul** : `VAR(delta_time entre connexions TCP successives)` (voir §3.3)
|
||||
|
||||
#### syn_timing_cv `[impl.]`
|
||||
|
||||
**Calcul** : `STDDEV(delay_SYN_to_ClientHello) / MEAN(delay_SYN_to_ClientHello)` (voir §3.3)
|
||||
|
||||
#### fingerprint_coherence_score `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(sessions avec JA4 dominant) / COUNT(sessions totales)` — ratio de sessions partageant le même JA4 dominant dans une plage de 5 minutes.
|
||||
|
||||
#### h2_settings_known `[impl.]`
|
||||
|
||||
**Calcul** : `1 si le vecteur SETTINGS HTTP/2 correspond à un navigateur connu (Chrome/Firefox/Safari/curl/Go)`
|
||||
|
||||
**Signal** : SETTINGS inconnu = implémentation HTTP/2 maison ou non répertoriée.
|
||||
|
||||
#### h2_pseudo_order_match `[impl.]`
|
||||
|
||||
**Calcul** : `1 si l'ordre des pseudo-headers (:method, :authority, :scheme, :path) correspond au navigateur déclaré dans le JA4`
|
||||
|
||||
**Signal** : incohérence = outil qui imite le JA4 TLS mais utilise une autre pile HTTP/2.
|
||||
|
||||
#### h2_ja4_coherence `[impl.]`
|
||||
|
||||
**Calcul** : score de cohérence entre la famille HTTP/2 (SETTINGS + WINDOW_UPDATE + pseudo-header order) et la famille JA4 (fingerprint TLS). Score élevé = même famille technologique pour les deux couches.
|
||||
|
||||
#### h2_settings_rare `[impl.]`
|
||||
|
||||
**Calcul** : `1 si le vecteur SETTINGS HTTP/2 n'est présent dans aucune base de fingerprints connue`
|
||||
|
||||
**Signal** : implémentation HTTP/2 non répertoriée, potentiellement un outil d'évasion personnalisé.
|
||||
|
||||
#### tls_h2_family_mismatch `[impl.]`
|
||||
|
||||
**Calcul** : feature composite — `1 si JA4 ∈ famille Chrome ET h2_pseudo_order ≠ masp (Chrome order), OU JA4 ∈ famille Firefox ET h2_window_update ≠ plage Firefox`
|
||||
|
||||
**Signal** : cette feature est le signal de haute précision central de la famille F4. Elle détecte les outils (httpcloak, BotBrowser partiel) qui imitent correctement la couche TLS mais ne reproduisent pas fidèlement la couche HTTP/2. Un navigateur Chrome légitime présente systématiquement JA4 Chrome + SETTINGS Chrome + WINDOW_UPDATE Chrome + pseudo-order `masp`. Toute discordance expose l'imitation.
|
||||
|
||||
---
|
||||
|
||||
### Famille 5 : Empreinte réseau (13 features)
|
||||
|
||||
La famille 5 agrège les signaux bruts de la couche réseau (L3/L4) qui complètent les signaux applicatifs.
|
||||
|
||||
#### ip_id_zero_ratio `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(paquets avec IP ID = 0) / total paquets`
|
||||
|
||||
**Signal** : scanners (Masscan, ZMap) génèrent des paquets avec IP ID = 0 systématiquement.
|
||||
|
||||
#### request_size_variance `[impl.]`
|
||||
|
||||
**Calcul** : `VAR(taille des requêtes HTTP en octets) par session`
|
||||
|
||||
**Signal** : les requêtes humaines varient en taille (saisie de formulaires, paramètres de recherche différents). Les bots automatisés envoient souvent des requêtes de taille identique ou très régulière.
|
||||
|
||||
#### anomalous_payload_ratio `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(requêtes avec payloads > 3σ de la moyenne de la session) / hits`
|
||||
|
||||
**Signal** : injection de charges utiles anormalement grandes (tentatives de buffer overflow, XXE, SQLi dans le body).
|
||||
|
||||
#### avg_ttl `[impl.]`
|
||||
|
||||
**Calcul** : `AVG(IP TTL observé) sur la session`
|
||||
|
||||
**Signal** : fingerprint OS résiduel (voir §3.2).
|
||||
|
||||
#### ttl_std `[impl.]`
|
||||
|
||||
**Calcul** : `STDDEV(IP TTL) sur la session`
|
||||
|
||||
**Signal** : un TTL constant indique un seul OS ou une manipulation. Une variance élevée indique des connexions depuis différents OS ou routes.
|
||||
|
||||
#### no_window_scale_ratio `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(connexions SYN sans option Window Scale) / total connexions`
|
||||
|
||||
**Signal** : absence de Window Scale = stack TCP minimal, OS obsolète, ou outil de scan.
|
||||
|
||||
#### ip_df_variance `[impl.]`
|
||||
|
||||
**Calcul** : `VAR(IP DF bit) sur la session`
|
||||
|
||||
**Signal** : instabilité du bit DF = stack réseau non standard ou tunnel changeant.
|
||||
|
||||
#### tcp_shared_count `[impl.]`
|
||||
|
||||
**Calcul** : `AVG(max_keepalives) sur la session`
|
||||
|
||||
**Signal** : voir §3.3.
|
||||
|
||||
#### port_exhaustion_ratio `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(connexions avec src_port > 60000) / total connexions`
|
||||
|
||||
**Signal** : un hôte émettant un très grand nombre de connexions rapides épuise sa plage de ports (16-bit, max 65535). Un ratio élevé de ports > 60000 indique une source à haute fréquence de connexions.
|
||||
|
||||
#### src_port_density `[impl.]`
|
||||
|
||||
**Calcul** : `(max_src_port - min_src_port) / total connexions`
|
||||
|
||||
**Signal** : un bot envoyant des connexions rapides depuis un seul processus utilise des ports séquentiels (densité faible). Un humain réel ouvre peu de connexions avec une plage de ports plus dispersée.
|
||||
|
||||
#### has_xff `[impl.]`
|
||||
|
||||
**Calcul** : `1 si l'en-tête X-Forwarded-For est présent dans au moins une requête de la session`
|
||||
|
||||
**Signal** : trafic via CDN ou proxy. Conséquence directe sur le pipeline : neutralisation des dimensions H2 du browser_matcher (voir §3.6).
|
||||
|
||||
#### true_window_size `[impl.]`
|
||||
|
||||
**Calcul** : `tcp_meta_window_size × 2^tcp_meta_window_scale`
|
||||
|
||||
**Signal** : taille effective de la fenêtre TCP après application du facteur d'échelle. Fingerprint OS/stack TCP.
|
||||
|
||||
#### window_mss_ratio `[impl.]`
|
||||
|
||||
**Calcul** : `true_window_size / tcp_meta_mss`
|
||||
|
||||
**Signal** : ratio diagnostique. Pour Linux standard : 64240 / 1460 ≈ 44. Des valeurs très différentes signalent des configurations non standard ou des manipulations.
|
||||
|
||||
---
|
||||
|
||||
### Famille 6 : Comportement de navigation (10 features)
|
||||
|
||||
La famille 6 capture le comportement de navigation macroscopique sur la fenêtre de 300 secondes.
|
||||
|
||||
#### asset_ratio `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(requêtes vers CSS/JS/images/fonts) / hits`
|
||||
|
||||
**Signal** : voir §2.3.2.
|
||||
|
||||
#### direct_access_ratio `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(requêtes sans Referer) / hits`
|
||||
|
||||
**Signal** : voir §2.3.2.
|
||||
|
||||
#### orphan_ratio `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(requêtes HTTP sans données L3/L4 associées) / hits`
|
||||
|
||||
**Signal** : ratio élevé = trafic majoritairement via proxy/CDN cassant la corrélation L4↔L7. Peut masquer des signaux TLS/TCP. Feature informative pour la fusion LR.
|
||||
|
||||
#### temporal_entropy `[impl.]`
|
||||
|
||||
**Calcul** : entropie de Shannon sur la distribution des requêtes par intervalle de temps de 30 s dans la fenêtre de 300 s : `H = -Σ pi × log2(pi)`
|
||||
|
||||
**Signal** : distribution uniforme (entropie maximale) = navigation naturelle. Distribution très concentrée (entropie faible) = burst automatisé.
|
||||
|
||||
#### post_ratio `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(requêtes POST) / hits`
|
||||
|
||||
**Signal** : voir §2.3.3.
|
||||
|
||||
#### head_ratio `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(requêtes HEAD) / hits`
|
||||
|
||||
**Signal** : les requêtes HEAD (sans corps de réponse) sont utilisées par des outils de check de disponibilité, des crawlers de métadonnées, ou des outils de reconnaissance. Rares dans la navigation humaine.
|
||||
|
||||
#### http_scheme_ratio `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(requêtes HTTP non-HTTPS) / hits`
|
||||
|
||||
**Signal** : les navigateurs modernes utilisent HTTPS exclusivement depuis 2018 environ. Des requêtes HTTP en clair signalent des outils anciens ou mal configurés.
|
||||
|
||||
#### login_post_concentration `[impl.]`
|
||||
|
||||
**Calcul** : `count_login_post / COUNT(requêtes POST totales)`
|
||||
|
||||
**Signal** : voir §2.3.3 — mesure si les POSTs se concentrent sur les endpoints d'authentification.
|
||||
|
||||
#### unusual_content_type_ratio `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(requêtes avec Content-Type absent ou non-standard) / hits`
|
||||
|
||||
**Signal** : les navigateurs envoient des Content-Type précis selon le contexte. Les outils automatisés omettent souvent ce header ou envoient des valeurs génériques.
|
||||
|
||||
#### non_standard_port_ratio `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(requêtes vers ports non-standards : pas 80/443/8080/8443) / hits`
|
||||
|
||||
**Signal** : les navigateurs humains n'accèdent que très rarement à des ports non-standards. Les bots ciblant des services d'administration ou des services exposés accèdent à des ports inhabituels.
|
||||
|
||||
---
|
||||
|
||||
### Famille 7 : Intelligence contextuelle (23 features)
|
||||
|
||||
La famille 7 combine la connaissance des bases de référence (JA4 connu, ASN, profils de navigateurs) avec les scores du browser_matcher.
|
||||
|
||||
#### ja4_asn_concentration `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(sessions avec même JA4 ET même ASN) / COUNT(sessions avec même JA4)`
|
||||
|
||||
**Signal** : une flotte de bots utilisant le même outil depuis le même datacenter produit une concentration JA4/ASN élevée, révélant une campagne coordonnée.
|
||||
|
||||
#### ja4_country_concentration `[impl.]`
|
||||
|
||||
**Calcul** : `entropy(distribution pays) pour les sessions partageant un JA4` — faible entropie = origines géographiques concentrées.
|
||||
|
||||
#### is_rare_ja4 `[impl.]`
|
||||
|
||||
**Calcul** : `1 si le JA4 de la session est absent de la base de fingerprints JA4 connus`
|
||||
|
||||
**Signal** : implémentation TLS non répertoriée — peut indiquer un nouvel outil d'évasion.
|
||||
|
||||
#### header_order_shared_count `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(sessions partageant exactement la même header_order_signature)` dans la fenêtre de 300 s.
|
||||
|
||||
**Signal** : de nombreuses sessions avec la même signature d'ordre d'en-têtes indique une flotte utilisant la même bibliothèque HTTP sans randomisation.
|
||||
|
||||
#### ja3_diversity_ratio `[impl.]`
|
||||
|
||||
**Calcul** : voir §2.2.3.
|
||||
|
||||
#### anubis_is_flagged `[impl.]`
|
||||
|
||||
**Calcul** : `1 si la session a une correspondance Anubis WEIGH ou DENY`
|
||||
|
||||
**Signal** : signal auxiliaire de la base Anubis, sans action bloquante dans le vecteur ML.
|
||||
|
||||
#### multiplexing_efficiency `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(streams HTTP/2 uniques) / COUNT(connexions TCP totales)`
|
||||
|
||||
**Signal** : un navigateur humain utilise efficacement le multiplexage HTTP/2 (plusieurs streams par connexion). Un bot ouvrant une connexion par requête a une efficacité = 1.
|
||||
|
||||
#### browser_confidence `[impl.]`
|
||||
|
||||
**Calcul** : score de confiance global du browser_matcher (voir §3.8).
|
||||
|
||||
#### browser_family `[impl.]`
|
||||
|
||||
**Calcul** : famille détectée : `chrome | firefox | safari | curl | python | go | unknown`
|
||||
|
||||
**Signal** : permet la décomposition des features par famille de clients.
|
||||
|
||||
#### is_known_browser `[impl.]`
|
||||
|
||||
**Calcul** : `1 si browser_confidence ≥ 0.70`
|
||||
|
||||
**Signal** : client correspondant à un profil de navigateur connu avec haute confiance.
|
||||
|
||||
#### browser_consistency_score `[impl.]`
|
||||
|
||||
**Calcul** : cohérence entre les features L5, L7, et H2 au regard du navigateur déclaré dans le User-Agent.
|
||||
|
||||
#### axis_ja4_known `[impl.]`
|
||||
|
||||
**Calcul** : dimension JA4 du browser_matcher — `1 si JA4 figure dans la base Chrome/Firefox/Safari connue`
|
||||
|
||||
#### axis_ja4_struct `[impl.]`
|
||||
|
||||
**Calcul** : dimension structurelle JA4 — score de conformité de la structure JA4 (nombre de ciphers, d'extensions, ALPN, SNI) aux navigateurs connus.
|
||||
|
||||
#### axis_http_modern `[impl.]`
|
||||
|
||||
**Calcul** : dimension modernité HTTP — composite de `sec_fetch_absence_rate`, `has_accept_language`, `modern_browser_score`, `ua_ch_mismatch`.
|
||||
|
||||
#### axis_nav_behavior `[impl.]`
|
||||
|
||||
**Calcul** : dimension comportement de navigation — composite de `asset_ratio`, `direct_access_ratio`, `orphan_ratio`, `post_ratio`.
|
||||
|
||||
#### axis_tls_coherence `[impl.]`
|
||||
|
||||
**Calcul** : dimension cohérence TLS — composite de `tls12_ratio`, `is_alpn_missing`, `sni_host_mismatch`, `fingerprint_coherence_score`.
|
||||
|
||||
#### axis_h2_coherence `[impl.]`
|
||||
|
||||
**Calcul** : dimension cohérence HTTP/2 — composite de `h2_settings_known`, `h2_pseudo_order_match`, `h2_ja4_coherence`.
|
||||
|
||||
#### browser_match_chrome `[impl.]`
|
||||
|
||||
**Calcul** : score de correspondance spécifique au profil Chrome (SETTINGS H2 + WINDOW_UPDATE + pseudo-order masp + JA4 Chrome).
|
||||
|
||||
#### browser_match_firefox `[impl.]`
|
||||
|
||||
**Calcul** : score de correspondance spécifique au profil Firefox.
|
||||
|
||||
#### browser_match_safari `[impl.]`
|
||||
|
||||
**Calcul** : score de correspondance spécifique au profil Safari.
|
||||
|
||||
#### browser_match_max `[impl.]`
|
||||
|
||||
**Calcul** : `MAX(browser_match_chrome, browser_match_firefox, browser_match_safari)`
|
||||
|
||||
**Signal** : score de correspondance au meilleur navigateur candidat.
|
||||
|
||||
#### h2_window_update_value `[impl.]`
|
||||
|
||||
**Calcul** : valeur brute de l'incrément WINDOW_UPDATE HTTP/2 (voir §2.5.3).
|
||||
|
||||
**Signal** : valeur distinctive par implémentation — voir table §2.5.3.
|
||||
|
||||
#### h2_has_priority_frames `[impl.]`
|
||||
|
||||
**Calcul** : `1 si des frames PRIORITY HTTP/2 sont reçues avant la première requête`
|
||||
|
||||
**Signal** : Firefox envoie des PRIORITY frames de démarrage. Chrome ≥ 119 n'en envoie plus. Absence ≠ non-Firefox ; présence = signal Firefox fort.
|
||||
|
||||
---
|
||||
|
||||
### Famille 8 : Features comportementales avancées (13 features)
|
||||
|
||||
La famille 8 regroupe les features originales développées spécifiquement pour cette architecture, capturant des signaux comportementaux avancés non présents dans la littérature standard.
|
||||
|
||||
#### path_transition_entropy `[impl.]`
|
||||
|
||||
**Calcul** : entropie de Shannon sur la matrice de transition entre chemins successifs. Pour chaque paire de chemins consécutifs (path_i → path_{i+1}), construire la distribution de probabilité des transitions ; calculer H = -Σ p(a→b) × log2(p(a→b)).
|
||||
|
||||
**Signal** : navigation humaine = entropie élevée (imprévisible). Crawler systématique = entropie faible (suivi d'un pattern fixe). Bot répétitif = entropie proche de 0 (même transition répétée).
|
||||
|
||||
#### cadence_cv `[impl.]`
|
||||
|
||||
**Calcul** : `STDDEV(inter_request_intervals_ms) / MEAN(inter_request_intervals_ms)`
|
||||
|
||||
**Signal** : coefficient de variation des intervalles inter-requêtes. Humain : CV ≈ 0.8–2.0 (variabilité naturelle). Bot à timer fixe : CV ≈ 0.01–0.05 (régularité mécanique). Bot avec jitter artificiel : CV ≈ 0.1–0.3 (moins régulier que timer fixe, moins variable qu'humain).
|
||||
|
||||
#### lag1_autocorrelation `[impl.]`
|
||||
|
||||
**Calcul** : corrélation de Pearson entre la série des intervalles inter-requêtes et la même série décalée de 1 : `corr(I[t], I[t-1])` où I[t] est l'intervalle entre la requête t et la requête t+1.
|
||||
|
||||
**Signal** : humain : autocorrélation faible (chaque intervalle est indépendant — dépend de la lecture, de l'interaction, etc.). Bot à burst-pause : autocorrélation positive forte (les longs intervalles suivent les longs intervalles, les courts suivent les courts). Timer avec bruit : autocorrélation proche de 0 mais variance très faible.
|
||||
|
||||
#### burst_ratio `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(inter-request intervals < percentile_10(intervals)) / hits`
|
||||
|
||||
**Signal** : ratio de requêtes arrivant en rafale (intervalles très courts). Les bots soumettent souvent des lots de requêtes suivis de pauses, alors que les humains ont une distribution plus continue.
|
||||
|
||||
#### pause_ratio `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(inter-request intervals > percentile_90(intervals)) / hits`
|
||||
|
||||
**Signal** : ratio de longues pauses. Complémentaire de burst_ratio. Un humain a un ratio de pauses naturellement élevé (lecture, réflexion). Un bot avec timer fixe a un ratio de pauses presque nul (timer constant).
|
||||
|
||||
#### benford_deviation `[impl.]`
|
||||
|
||||
**Calcul** : déviation par rapport à la loi de Benford (loi du premier chiffre significatif) sur les intervalles inter-requêtes en millisecondes.
|
||||
|
||||
**Loi de Benford** (aussi appelée loi du premier chiffre ou loi de Newcomb-Benford) : la probabilité que le premier chiffre significatif d'un nombre provenant de données naturelles soit d (d ∈ {1,...,9}) suit :
|
||||
|
||||
```
|
||||
P(d) = log₁₀(1 + 1/d)
|
||||
```
|
||||
|
||||
Soit : P(1) ≈ 30.1 %, P(2) ≈ 17.6 %, P(3) ≈ 12.5 %, ..., P(9) ≈ 4.6 %.
|
||||
|
||||
Les séries naturelles (intervalles de navigation humaine, données de population, transactions financières) suivent cette distribution. Les intervalles générés artificiellement (timers à période fixe, sleep() programmé) dévient significativement.
|
||||
|
||||
**Calcul de la déviation** :
|
||||
|
||||
```
|
||||
D_benford = Σ_{d=1}^{9} |f_observed(d) - P(d)|
|
||||
```
|
||||
|
||||
où f_observed(d) est la fréquence observée du premier chiffre d dans la série des intervalles inter-requêtes.
|
||||
|
||||
**Signal** : humain ≈ 0.05–0.20, bot à timer fixe > 0.40, bot avec jitter ≈ 0.20–0.35.
|
||||
|
||||
> **Caveat — Applicabilité de la loi de Benford** : l'application de la loi de Benford aux intervalles inter-requêtes est une hypothèse heuristique, non démontrée mathématiquement pour ce cas d'usage. La loi de Benford exige que les données couvrent plusieurs ordres de grandeur de manière approximativement log-normale ; les intervalles inter-requêtes (typiquement quelques ms à quelques secondes) ne couvrent pas nécessairement ces conditions. Les seuils de déviation cités (ex. 0.40) sont empiriques et nécessitent une validation sur les données de production. Cette feature est conservée comme signal auxiliaire, mais ne doit pas être utilisée comme critère de décision principal.
|
||||
|
||||
#### root_to_first_asset_delay `[impl.]`
|
||||
|
||||
**Calcul** : délai en ms entre la requête vers la page racine (`/` ou chemin HTML) et la première requête d'asset (CSS/JS/image) dans la même session.
|
||||
|
||||
**Signal** : un navigateur humain déclenche immédiatement le chargement des assets après avoir reçu le HTML (délai 50–200 ms, dépendant du temps de parsing). Un scraper qui charge le HTML sans le parser ne charge jamais les assets (valeur infinie, traitée comme NULL). Un bot qui simule les assets artificiellement peut avoir un délai non-physiologique (valeur théoriquement attendue très faible, nécessitant validation empirique sur l'infrastructure cible, ou > 5 000 ms).
|
||||
|
||||
#### asset_load_stddev `[impl.]`
|
||||
|
||||
**Calcul** : `STDDEV(délais entre requêtes d'assets successifs)` dans une cascade de chargement.
|
||||
|
||||
**Signal** : un navigateur réel charge les assets en parallèle avec des contraintes réseau naturelles, produisant une certaine variance. Un bot simulant des assets de manière séquentielle ou avec un délai fixe produit une variance anormalement faible.
|
||||
|
||||
#### ja4_drift_ratio `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(transitions du JA4 dominant entre segments temporels) / (nb_segments - 1)`
|
||||
|
||||
La session est divisée en segments de N requêtes. Le JA4 dominant de chaque segment est calculé (mode). `ja4_drift_ratio` compte les transitions entre JA4 dominants consécutifs, normalisé par le nombre de transitions possibles. La feature est calculée dans `view_thesis_features_1h` (fenêtres temporelles étendues d'une heure).
|
||||
|
||||
**Signal** : un humain avec un seul navigateur a un ratio de 0. Un bot qui change de configuration TLS entre différentes phases d'activité (reconnaissance → exploitation) produit un ratio proche de 1. Voir §5.5 pour la description complète et le contexte de détection des bots APT multi-phases.
|
||||
|
||||
**Plages typiques** : humain = 0 ; bot multi-phase = 0,5–1,0 ; bot TLS aléatoire = proche de 1,0.
|
||||
|
||||
#### host_diversity `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(DISTINCT en-têtes Host) par session`
|
||||
|
||||
**Signal** : un navigateur humain peut accéder à plusieurs domaines (portail, sous-domaines). Un bot de scan horizontal accède à de nombreux hosts distincts depuis la même IP. Feature critique pour la détection de bots qui parcourent plusieurs sites.
|
||||
|
||||
#### host_sweep_speed `[impl.]`
|
||||
|
||||
**Calcul** : `COUNT(DISTINCT hosts) / session_duration_minutes`
|
||||
|
||||
**Signal** : vitesse d'accès à de nouveaux hosts. Un scanner horizontal accède à un nouveau host par seconde ou plus vite. Un navigateur humain accède rarement à plus de 10 domaines distincts par minute.
|
||||
|
||||
#### host_coverage_uniformity `[impl.]`
|
||||
|
||||
**Calcul** : `1 - (STDDEV(requêtes par host) / MEAN(requêtes par host))`
|
||||
|
||||
**Signal** : un crawler systématique accède à chaque host avec le même nombre de requêtes (couverture uniforme). Un humain accède beaucoup plus à certains sites qu'à d'autres.
|
||||
|
||||
#### cross_domain_path_similarity `[impl.]`
|
||||
|
||||
**Calcul** : similarité de Jaccard entre les ensembles de chemins accédés sur différents hosts : `|paths(host_A) ∩ paths(host_B)| / |paths(host_A) ∪ paths(host_B)|`
|
||||
|
||||
**Signal** : un scanner accède aux mêmes chemins sur tous les hosts (similarité élevée : `/admin`, `/login`, `/config.php` sur chaque site). Un navigateur humain a des chemins spécifiques à chaque site (similarité faible).
|
||||
|
||||
|
||||
675
docs/thesis/06_techniques_avancees.md
Normal file
675
docs/thesis/06_techniques_avancees.md
Normal file
@ -0,0 +1,675 @@
|
||||
[<< Sommaire](README.md) | [Suivant >>](07_discussion_limites.md)
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
## 5. Techniques comportementales avancées
|
||||
|
||||
### Introduction
|
||||
|
||||
Cette section présente huit techniques originales développées pour adresser les **angles morts** des systèmes de détection existants. Ces angles morts ont été identifiés par analyse systématique des faux négatifs observés en production : sessions mal classifiées comme humaines malgré un comportement automatisé, ou sessions légitimes pénalisées par des features trop sensibles au contexte réseau.
|
||||
|
||||
Les huit techniques couvrent l'intégralité de la chaîne de détection : analyse des séquences de navigation (§5.1), détection de flottes distribuées (§5.2), fingerprinting temporel (§5.3), arbre de dépendances de ressources (§5.4), dérive intra-session de fingerprint (§5.5), analyse DNS passive (§5.6, non implémentée), invariant de compression (§5.7, non implémentée), et session multi-domaine (§5.8).
|
||||
|
||||
**Récapitulatif des statuts** :
|
||||
|
||||
| Section | Technique | Statut |
|
||||
|---------|-----------|--------|
|
||||
| 5.1 | Path Sequence Entropy | `[impl.]` |
|
||||
| 5.2 | Bipartite Bot Fleet Detection | `[impl.]` |
|
||||
| 5.3 | Request Cadence Fingerprint | `[impl.]` |
|
||||
| 5.4 | Resource Dependency Tree | `[impl.]` |
|
||||
| 5.5 | Intra-Session JA4 Drift | `[impl.]` |
|
||||
| 5.6 | DNS Shadow Analysis | `[todo]` |
|
||||
| 5.7 | Compression Ratio Invariant | `[todo]` |
|
||||
| 5.8 | Cross-Domain Session Linking | `[impl.]` |
|
||||
|
||||
---
|
||||
|
||||
### 5.1 Entropie de séquence de chemins (Path Sequence Entropy) `[impl.]`
|
||||
|
||||
#### Constat et motivation
|
||||
|
||||
La feature `path_diversity_ratio` mesure la **diversité** des chemins parcourus (nombre de chemins distincts / nombre total de requêtes), mais non leur **ordre**. Cette distinction est fondamentale : un crawler sophistiqué peut randomiser l'ordre de visite des pages pour augmenter la diversité apparente, sans reproduire les transitions naturelles d'un comportement humain.
|
||||
|
||||
La navigation humaine suit des patterns séquentiels prévisibles déterminés par l'architecture du site : page d'accueil → catégorie → produit → panier d'achat. Ces transitions ont une structure probabiliste stable que les crawlers systématiques ne reproduisent pas, même avec randomisation.
|
||||
|
||||
#### Modèle mathématique : chaîne de Markov du premier ordre
|
||||
|
||||
Une **chaîne de Markov du premier ordre** modélise un système dans lequel l'état suivant dépend uniquement de l'état courant (propriété d'absence de mémoire, aussi appelée propriété markovienne). Pour les séquences de chemins, chaque chemin est un état, et la probabilité de transition est :
|
||||
|
||||
```
|
||||
P(chemin_j | chemin_i) = count(i → j) / count(i)
|
||||
```
|
||||
|
||||
où `count(i → j)` est le nombre de fois que le chemin `i` est immédiatement suivi du chemin `j`, et `count(i)` est le nombre total de départs depuis `i`.
|
||||
|
||||
L'**entropie de la matrice de transition** mesure l'imprévisibilité des transitions :
|
||||
|
||||
```
|
||||
H_transition = -Σ_{i,j} P(p_i → p_j) × log₂(P(p_i → p_j))
|
||||
```
|
||||
|
||||
Interprétation :
|
||||
- **Entropie élevée** : transitions imprévisibles — caractéristique d'une navigation humaine organique avec des chemins variés
|
||||
- **Entropie faible** : transitions prévisibles — caractéristique d'un crawler systématique (lexicographique, profondeur d'abord)
|
||||
- **Entropie nulle** : transition unique constante — bot répétant exactement le même chemin
|
||||
|
||||
#### Résistance à la rotation de diversité
|
||||
|
||||
Un bot qui randomise aléatoirement l'ordre de ses chemins augmente `path_diversity_ratio` mais produit une matrice de transition **quasi-uniforme** (entropie maximale). Or, la navigation humaine réelle possède des transitions **structurées** : les transitions produit → panier sont fréquentes, les transitions panier → page d'accueil sont rares. L'entropie maximale (uniforme) est donc elle-même un signal anormal, distinguable de l'entropie modérée mais structurée de la navigation humaine.
|
||||
|
||||
La combinaison `(path_diversity_ratio, path_transition_entropy)` forme un espace bidimensionnel où les clusters bots et humains sont mieux séparés qu'avec une feature seule.
|
||||
|
||||
#### Implémentation
|
||||
|
||||
**Table ClickHouse** : `agg_path_sequences_1h`
|
||||
- Colonnes : `(src_ip, ja4, session_id, groupArray(path) AS path_sequence)`
|
||||
- Agrégation par session sur fenêtre glissante d'une heure
|
||||
|
||||
**Vue** : `view_thesis_features_1h`
|
||||
- Colonne : `path_transition_entropy`
|
||||
|
||||
**SQL de calcul** :
|
||||
|
||||
```sql
|
||||
-- Normalisation des préfixes à profondeur 2
|
||||
-- ex. /shop/product/123 → /shop/product
|
||||
WITH normalized_paths AS (
|
||||
SELECT
|
||||
session_id,
|
||||
arrayMap(p -> arrayStringConcat(
|
||||
arraySlice(splitByChar('/', p), 1, 3), '/'), path_sequence
|
||||
) AS norm_seq
|
||||
FROM agg_path_sequences_1h
|
||||
),
|
||||
-- Calcul des transitions
|
||||
transitions AS (
|
||||
SELECT
|
||||
session_id,
|
||||
arrayZip(
|
||||
arraySlice(norm_seq, 1, length(norm_seq) - 1),
|
||||
arraySlice(norm_seq, 2)
|
||||
) AS transition_pairs
|
||||
FROM normalized_paths
|
||||
)
|
||||
-- L'entropie finale est calculée via UDF Python (scipy.stats.entropy)
|
||||
-- ou approximée par la formule SQL suivante :
|
||||
SELECT
|
||||
session_id,
|
||||
-sum(p * log2(p)) AS path_transition_entropy
|
||||
FROM (
|
||||
SELECT
|
||||
session_id,
|
||||
count() / sum(count()) OVER (PARTITION BY session_id) AS p
|
||||
FROM transitions
|
||||
ARRAY JOIN transition_pairs AS tp
|
||||
GROUP BY session_id, tp
|
||||
)
|
||||
GROUP BY session_id;
|
||||
```
|
||||
|
||||
La feature `path_transition_entropy` est intégrée dans le vecteur feature de `bot_detector` (famille F7 — Comportement navigation).
|
||||
|
||||
---
|
||||
|
||||
### 5.2 Graphe de co-occurrence JA4×ASN (Bipartite Bot Fleet Detection) `[impl.]`
|
||||
|
||||
#### Constat et motivation
|
||||
|
||||
La feature `ja4_asn_concentration` mesure la concentration d'une paire (JA4, ASN) donnée — une valeur élevée signale un usage anormal d'une combinaison spécifique. Cependant, les botnets sophistiqués utilisent des **dizaines de JA4 et d'ASN en rotation** : chaque paire (JA4, ASN) individuelle est statistiquement anodine, mais le **pattern global de co-occurrence** révèle la coordination.
|
||||
|
||||
Un botnet distribuant ses requêtes sur 20 ASN différents, avec 5 JA4 distincts par ASN, produira 100 paires (JA4, ASN) différentes, chacune avec une concentration faible — indétectable par `ja4_asn_concentration` seul. Pourtant, ces 5 JA4 co-apparaissent systématiquement dans les mêmes ASN, formant une signature de flotte détectable par analyse de graphe.
|
||||
|
||||
#### Modèle mathématique : graphe bipartite et détection de communautés
|
||||
|
||||
Un **graphe bipartite** G = (U ∪ V, E) est un graphe dont les sommets se répartissent en deux ensembles disjoints (ici : JA4 fingerprints **U** et ASNs **V**) où les arêtes ne connectent que des sommets d'ensembles différents. Une arête (u, v) ∈ E existe si ≥ N IPs utilisent le JA4 u depuis l'ASN v dans la fenêtre temporelle considérée.
|
||||
|
||||
**Projection sur l'espace JA4** : le graphe projeté G_JA4 = (U, E') est défini par (u₁, u₂) ∈ E' si ∃ v ∈ V tel que (u₁, v) ∈ E et (u₂, v) ∈ E. Deux JA4 partagent une arête si et seulement si ils co-apparaissent dans le même ASN.
|
||||
|
||||
**Détection de communautés** : une communauté est un sous-ensemble de sommets plus densément connectés entre eux qu'avec le reste du graphe. Dans le graphe projeté G_JA4, une communauté dense de JA4 indique un botnet utilisant plusieurs empreintes JA4 mais se coordonnant via une infrastructure ASN partagée.
|
||||
|
||||
L'implémentation utilise **Louvain** ([Blondel et al., 2008](https://arxiv.org/abs/0803.0476)) via la bibliothèque `python-louvain` pour la détection de communautés sur le graphe projeté pondéré. L'algorithme de Louvain optimise la modularité du graphe par agrégation hiérarchique itérative, identifiant les regroupements de JA4 partageant un profil de co-occurrence ASN similaire. En l'absence de `python-louvain`, un fallback vers les **composantes connexes** de NetworkX est utilisé.
|
||||
|
||||
**Formule du score de flotte** :
|
||||
|
||||
```
|
||||
fleet_score = community_size × edge_density / log2(n_asn + 2)
|
||||
```
|
||||
|
||||
où :
|
||||
- `community_size` : nombre de JA4 dans la communauté détectée
|
||||
- `edge_density` : densité du sous-graphe induit (arêtes observées / arêtes possibles)
|
||||
- `log(nb_ASN)` : terme de normalisation pour pénaliser les grandes flottes trop dispersées géographiquement (signal d'infrastructure légitime CDN)
|
||||
|
||||
Une valeur de `fleet_score` élevée (> seuil empirique déterminé en production) déclenche un malus sur le score d'anomalie de toutes les IPs des communautés concernées.
|
||||
|
||||
#### Implémentation
|
||||
|
||||
**Module** : `fleet.py` dans `bot_detector`
|
||||
|
||||
```python
|
||||
import networkx as nx
|
||||
from networkx.algorithms import bipartite
|
||||
|
||||
def build_fleet_graph(df: pd.DataFrame, min_ips: int = 3):
|
||||
"""Construit le graphe bipartite JA4 × ASN."""
|
||||
G = nx.Graph()
|
||||
edge_weights = (
|
||||
df.groupby(['ja4', 'asn_number'])['src_ip'].nunique()
|
||||
.reset_index(name='n_ips')
|
||||
)
|
||||
edge_weights = edge_weights[edge_weights['n_ips'] >= min_ips]
|
||||
for _, row in edge_weights.iterrows():
|
||||
G.add_edge(f"ja4:{row['ja4']}", f"asn:{row['asn_number']}",
|
||||
weight=int(row['n_ips']))
|
||||
return G, ja4_nodes, asn_nodes
|
||||
|
||||
def detect_fleet_communities(df: pd.DataFrame) -> dict:
|
||||
"""Analyse le graphe et retourne {src_ip: fleet_score}."""
|
||||
G, ja4_nodes, asn_nodes = build_fleet_graph(df)
|
||||
# Projection bipartite : graphe des JA4 partageant des ASN
|
||||
G_ja4 = bipartite.weighted_projected_graph(G, ja4_nodes)
|
||||
# Détection de communautés (Louvain ou composantes connexes en fallback)
|
||||
try:
|
||||
from community import best_partition as louvain_partition
|
||||
partition = louvain_partition(G_ja4, weight='weight', random_state=42)
|
||||
except ImportError:
|
||||
communities = {i: set(c) for i, c in enumerate(nx.connected_components(G_ja4))}
|
||||
# fleet_score = taille × densité / log2(n_asn + 2)
|
||||
for cid, members in communities.items():
|
||||
score = len(members) * density / max(np.log2(n_asn + 2), 0.1)
|
||||
for ja4_node in members:
|
||||
fleet_scores[ja4_node.replace('ja4:', '')] = score
|
||||
return ip_scores
|
||||
matrix = np.zeros((len(ja4_nodes), len(asn_nodes)))
|
||||
for i, ja4 in enumerate(ja4_nodes):
|
||||
for j, asn in enumerate(asn_nodes):
|
||||
if G.has_edge(ja4, asn):
|
||||
matrix[i, j] = G[ja4][asn]['weight']
|
||||
return ja4_nodes, matrix
|
||||
|
||||
def detect_fleets(sessions: pd.DataFrame) -> pd.DataFrame:
|
||||
G = build_bipartite_graph(sessions)
|
||||
ja4_nodes, matrix = project_ja4(G)
|
||||
if len(ja4_nodes) < 5:
|
||||
return pd.DataFrame()
|
||||
clusterer = HDBSCAN(min_cluster_size=3, metric='jaccard')
|
||||
labels = clusterer.fit_predict(matrix > 0)
|
||||
# Calcul fleet_score par cluster...
|
||||
```
|
||||
|
||||
**Sortie** : les résultats de la détection de flotte sont intégrés directement dans le DataFrame du cycle courant via deux colonnes additionnelles :
|
||||
- `fleet_score` : score de la communauté à laquelle appartient l'IP (0 si aucune flotte détectée)
|
||||
- `fleet_campaign_flag` : 1 si `fleet_score ≥ FLEET_SCORE_THRESHOLD` (défaut : 2.0)
|
||||
|
||||
Les IPs appartenant à des communautés avec `fleet_score` élevé reçoivent un **malus de score d'anomalie** dans le pipeline final (ajout d'une constante au score brut EIF avant application de la fusion LR).
|
||||
|
||||
---
|
||||
|
||||
### 5.3 Fingerprinting par timing inter-requêtes (Request Cadence Fingerprint) `[impl.]`
|
||||
|
||||
#### Constat et motivation
|
||||
|
||||
Les features temporelles existantes — `hit_velocity` (vitesse moyenne de requêtes) et `temporal_entropy` (distribution horaire) — capturent la fréquence et la régularité macro-temporelle des requêtes. Elles manquent cependant le **rythme microscopique** : la distribution des intervalles entre requêtes consécutives (inter-request intervals, **Δt**) contient un signal discriminant riche.
|
||||
|
||||
Les humains produisent des intervalles irréguliers avec des rafales de chargement de page suivies de pauses de lecture. Les bots produisent des intervalles réguliers (basés sur `sleep(N)`) ou des intervalles exponentiels (backoff). Ces différences de structure statistique sont exploitées via quatre signaux complémentaires.
|
||||
|
||||
#### Signal 1 : Coefficient de variation (CV)
|
||||
|
||||
Le **coefficient de variation** est le rapport entre l'écart-type et la moyenne de la distribution des intervalles :
|
||||
|
||||
```
|
||||
CV(Δt) = σ(Δt) / μ(Δt)
|
||||
```
|
||||
|
||||
Valeurs caractéristiques :
|
||||
- **Navigation humaine** : CV ≈ 1,5–3,0 (haute variabilité naturelle due aux temps de lecture variables)
|
||||
- **Bot régulier** (sleep fixe N ms) : CV ≈ 0,01–0,3 (faible variabilité, intervalles quasi-constants)
|
||||
- **Bot avec jitter gaussien** : CV ≈ 0,3–0,7 (variabilité modérée mais inférieure à l'humain)
|
||||
|
||||
#### Signal 2 : Autocorrélation de décalage 1 (Lag-1)
|
||||
|
||||
L'**autocorrélation de décalage 1** mesure la corrélation entre des intervalles consécutifs :
|
||||
|
||||
```
|
||||
ρ₁ = corr(Δt_n, Δt_{n+1}) = Cov(Δt_n, Δt_{n+1}) / (σ_n × σ_{n+1})
|
||||
```
|
||||
|
||||
Valeurs caractéristiques :
|
||||
- **Navigation humaine** : ρ₁ ≈ 0 (intervalles indépendants — la durée de lecture d'une page n'est pas corrélée à celle de la suivante)
|
||||
- **Bot avec jitter proportionnel** : ρ₁ ≈ 0,8+ (corrélé — le jitter est généré proportionnellement à l'intervalle de base, créant une dépendance entre intervalles consécutifs)
|
||||
|
||||
Ce signal distingue spécifiquement les bots utilisant un jitter proportionnel (ex. `sleep(N + random() × N)`) des bots avec jitter additif indépendant.
|
||||
|
||||
#### Signal 3 : Ratio rafale/pause
|
||||
|
||||
Proportion des intervalles Δt classifiés comme :
|
||||
- **Rafale** : Δt < 100 ms (chargement parallèle de ressources dans le même rendu de page)
|
||||
- **Pause** : Δt > 5 000 ms (temps de lecture d'une page)
|
||||
- **Intermédiaire** : 100 ms ≤ Δt ≤ 5 000 ms (navigation entre pages)
|
||||
|
||||
Patterns caractéristiques :
|
||||
- **Navigateur réel** : alternance rafale (chargement de page) → pause (lecture) → rafale, avec ratio rafale/pause ≈ 0,3–0,6
|
||||
- **Scraper séquentiel** : exclusivement intermédiaire (requêtes espacées uniformément, ni rafale ni pause)
|
||||
- **Playwright/Puppeteer** : rafales très courtes (ordre de grandeur < 50 ms, à valider empiriquement) mais pauses quasi-absentes
|
||||
|
||||
#### Signal 4 : Déviation par rapport à la loi de Benford
|
||||
|
||||
La **loi de Benford** (aussi appelée loi du premier chiffre, [Benford, 1938](https://en.wikipedia.org/wiki/Benford%27s_law)) stipule que dans de nombreux jeux de données naturels, la probabilité que le premier chiffre significatif d'un nombre soit d vaut :
|
||||
|
||||
```
|
||||
P(premier chiffre = d) = log₁₀(1 + 1/d)
|
||||
```
|
||||
|
||||
**Table de la loi de Benford** :
|
||||
|
||||
| Chiffre d | Probabilité attendue |
|
||||
|-----------|---------------------|
|
||||
| 1 | 30,1 % |
|
||||
| 2 | 17,6 % |
|
||||
| 3 | 12,5 % |
|
||||
| 4 | 9,7 % |
|
||||
| 5 | 7,9 % |
|
||||
| 6 | 6,7 % |
|
||||
| 7 | 5,8 % |
|
||||
| 8 | 5,1 % |
|
||||
| 9 | 4,6 % |
|
||||
|
||||
Les jeux de données naturels (durées de visite, revenus fiscaux, séquences physiques mesurées) suivent cette distribution ; les jeux de données artificiels (timers `sleep(1000ms)`) ne la suivent pas — ils concentrent les premiers chiffres sur des valeurs spécifiques.
|
||||
|
||||
Par exemple, un bot utilisant `sleep(1000ms)` génère exclusivement des intervalles Δt autour de 1000 ms, avec premier chiffre 1 en quasi-totalité. Un bot utilisant `sleep(2500ms + jitter±200ms)` surreprésente les chiffres 2 et 3.
|
||||
|
||||
**Métrique de déviation** :
|
||||
|
||||
```
|
||||
D_benford = Σ_{d=1}^{9} |P_observée(d) - P_benford(d)|
|
||||
= mean absolute deviation
|
||||
```
|
||||
|
||||
Valeurs caractéristiques :
|
||||
- Navigation humaine : D_benford ≈ 0,02–0,08 (faible déviation)
|
||||
- Bot sleep(N) : D_benford ≈ 0,30–0,80 (forte concentration sur un chiffre)
|
||||
|
||||
> **Caveat** : l'applicabilité de la loi de Benford aux intervalles inter-requêtes est une hypothèse heuristique, non démontrée mathématiquement pour ce cas d'usage. Les données d'intervalles (quelques ms à quelques secondes) ne couvrent pas nécessairement les ordres de grandeur requis par la loi. Les seuils ci-dessus sont empiriques et nécessitent une validation sur les données de production.
|
||||
|
||||
#### Implémentation
|
||||
|
||||
**Table ClickHouse** : `agg_request_timing_1h`
|
||||
- Colonnes : `(src_ip, ja4, session_id, groupArray(timestamp_ns) AS ts_array)`
|
||||
|
||||
**Vue** : `view_thesis_features_1h`
|
||||
- Colonnes : `cadence_cv`, `lag1_autocorrelation`, `burst_ratio`, `benford_deviation`, `pause_ratio`
|
||||
|
||||
**SQL** :
|
||||
|
||||
```sql
|
||||
WITH deltas AS (
|
||||
SELECT
|
||||
session_id,
|
||||
arrayMap(
|
||||
(t1, t2) -> (t2 - t1) / 1e6, -- nanoseconds → milliseconds
|
||||
arraySlice(ts_array, 1, length(ts_array) - 1),
|
||||
arraySlice(ts_array, 2)
|
||||
) AS delta_ms
|
||||
FROM agg_request_timing_1h
|
||||
WHERE length(ts_array) >= 3
|
||||
),
|
||||
stats AS (
|
||||
SELECT
|
||||
session_id,
|
||||
arrayReduce('avg', delta_ms) AS mu,
|
||||
arrayReduce('stddevPop', delta_ms) AS sigma,
|
||||
countIf(d -> d < 100, delta_ms) AS burst_count,
|
||||
countIf(d -> d > 5000, delta_ms) AS pause_count,
|
||||
length(delta_ms) AS n
|
||||
FROM deltas
|
||||
ARRAY JOIN delta_ms AS d
|
||||
GROUP BY session_id
|
||||
)
|
||||
SELECT
|
||||
session_id,
|
||||
sigma / nullIf(mu, 0) AS cadence_cv,
|
||||
burst_count / n AS burst_ratio,
|
||||
pause_count / n AS pause_ratio
|
||||
-- lag1_autocorrelation et benford_deviation calculés en Python post-processing
|
||||
FROM stats;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.4 Détection de navigation synthétique par arbre de dépendances (Resource Dependency Tree) `[impl.]`
|
||||
|
||||
#### Constat et motivation
|
||||
|
||||
La feature `asset_ratio` détecte les bots qui ne chargent pas les ressources statiques (CSS, JS, images). Les frameworks de scraping modernes — Playwright, Puppeteer, Selenium — **chargent toutes les ressources** pour paraître identiques à un navigateur réel. Le signal n'est plus la présence du chargement de ressources, mais son **ordre temporel**.
|
||||
|
||||
Un navigateur réel parse le HTML, construit le Document Object Model (DOM), puis déclenche le chargement des ressources selon leur type et priorité. Ce processus crée des **cascades waterfall** caractéristiques. Playwright simule ce processus mais avec des différences temporelles mesurables.
|
||||
|
||||
#### Concept : cascade de dépendances de ressources
|
||||
|
||||
Le processus de chargement d'une page par un vrai navigateur suit cette séquence déterministe :
|
||||
|
||||
1. **Requête HTML** : t = 0 ms — le navigateur demande le document HTML
|
||||
2. **Parsing HTML** : t = 0–50 ms — construction du DOM, identification des ressources
|
||||
3. **CSS bloquant** : t = 50–100 ms — les feuilles de style CSS référencées dans `<link rel="stylesheet">` sont chargées en priorité absolue (bloquant le rendu)
|
||||
4. **Construction CSSOM** : t = 100–150 ms — le CSS Object Model est construit à partir des CSS téléchargés
|
||||
5. **JS différé et images** : t = 150–300 ms — après la construction du CSSOM, les scripts différés (`defer`, `async`) et les images sont chargés en parallèle
|
||||
|
||||
Cette structure produit deux signaux mesurables :
|
||||
|
||||
#### Signal 1 : root_to_first_asset_delay
|
||||
|
||||
Le délai entre la requête HTML et la première requête de ressource (CSS ou JS) est caractéristique :
|
||||
- **Navigateur réel** : 50–200 ms (temps de parsing HTML réel)
|
||||
- **Playwright headless** : délai attendu faible (< 50 ms, hypothèse nécessitant validation empirique sur notre infrastructure)
|
||||
- **Scraper avec chargement d'assets** : 0 ms (requêtes séquentielles immédiates)
|
||||
|
||||
#### Signal 2 : asset_load_stddev
|
||||
|
||||
L'écart-type des timestamps de chargement au sein d'une cascade (batch de ressources déclenchées en parallèle) :
|
||||
- **Navigateur réel** : faible dans le batch (chargement effectivement parallèle sur HTTP/2), mais les batches successifs sont séparés par des gaps caractéristiques
|
||||
- **Playwright** : quasi-nul (toutes les ressources déclenchées simultanément par le moteur JS simulé)
|
||||
- **Scraper séquentiel** : élevé (les ressources sont chargées une par une)
|
||||
|
||||
#### Implémentation
|
||||
|
||||
**Tables ClickHouse** :
|
||||
|
||||
```sql
|
||||
CREATE TABLE agg_resource_cascade_1h
|
||||
(
|
||||
session_id String,
|
||||
src_ip IPv4,
|
||||
resource_type Enum('html', 'css', 'js', 'image', 'font', 'other'),
|
||||
timestamp_ns UInt64,
|
||||
path String
|
||||
)
|
||||
ENGINE = MergeTree()
|
||||
ORDER BY (session_id, timestamp_ns)
|
||||
TTL toDateTime(timestamp_ns / 1e9) + INTERVAL 2 HOUR;
|
||||
```
|
||||
|
||||
**Vue** : `view_resource_cascade_1h`
|
||||
|
||||
**Fichier SQL** : `12_thesis_features.sql`
|
||||
|
||||
**Colonnes** : `root_to_first_asset_delay` (ms), `asset_load_stddev` (ms)
|
||||
|
||||
**Identification des ressources** : double-source pour robustesse :
|
||||
1. **En-tête Accept** : `Accept: text/css` → CSS ; `Accept: */*` avec `Sec-Fetch-Dest: script` → JS
|
||||
2. **Extension de chemin** : regex `\.(css|js|png|jpg|webp|woff2|svg)$`
|
||||
|
||||
```sql
|
||||
WITH html_times AS (
|
||||
SELECT session_id, min(timestamp_ns) AS html_ts
|
||||
FROM agg_resource_cascade_1h
|
||||
WHERE resource_type = 'html'
|
||||
GROUP BY session_id
|
||||
),
|
||||
first_asset AS (
|
||||
SELECT session_id, min(timestamp_ns) AS first_asset_ts
|
||||
FROM agg_resource_cascade_1h
|
||||
WHERE resource_type IN ('css', 'js')
|
||||
GROUP BY session_id
|
||||
),
|
||||
asset_stats AS (
|
||||
SELECT session_id,
|
||||
stddevPop(timestamp_ns / 1e6) AS asset_load_stddev_ms
|
||||
FROM agg_resource_cascade_1h
|
||||
WHERE resource_type != 'html'
|
||||
GROUP BY session_id
|
||||
)
|
||||
SELECT
|
||||
h.session_id,
|
||||
(f.first_asset_ts - h.html_ts) / 1e6 AS root_to_first_asset_delay_ms,
|
||||
a.asset_load_stddev_ms
|
||||
FROM html_times h
|
||||
LEFT JOIN first_asset f USING (session_id)
|
||||
LEFT JOIN asset_stats a USING (session_id);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.5 Analyse de dérive de fingerprint TLS intra-session (Intra-Session JA4 Drift) `[impl.]`
|
||||
|
||||
#### Constat et motivation
|
||||
|
||||
La feature `distinct_ja4_count` mesure la diversité globale des JA4 par IP sur une fenêtre temporelle. Elle détecte les bots qui **changent fréquemment de JA4**. Cependant, un attaquant sophistiqué peut maintenir un JA4 stable pendant la majeure partie de la session et ne le changer qu'à la **transition de phase d'attaque** — une stratégie de discrétion qui contourne `distinct_ja4_count`.
|
||||
|
||||
Cette technique analyse la **dynamique temporelle** de la dérive JA4 plutôt que sa magnitude globale.
|
||||
|
||||
#### Technique : analyse de dérive temporelle
|
||||
|
||||
1. **Segmentation** : la session est découpée en fenêtres de 10 minutes (W₁, W₂, …, W_k)
|
||||
2. **JA4 dominant par segment** : pour chaque fenêtre, le JA4 le plus fréquent (mode) est retenu comme JA4 représentatif du segment
|
||||
3. **Comptage des transitions** : une transition est comptée quand le JA4 dominant change entre deux segments consécutifs
|
||||
4. **Calcul du ratio** :
|
||||
|
||||
```
|
||||
drift_ratio = nb_transitions / (nb_segments - 1) ∈ [0, 1]
|
||||
```
|
||||
|
||||
Valeurs caractéristiques :
|
||||
- **Navigation humaine** : drift_ratio = 0 (même navigateur = même JA4 tout au long de la session)
|
||||
- **Bot en rotation simple** : drift_ratio ≈ 1 (JA4 changeant à chaque segment)
|
||||
- **Bot APT multi-phase** : drift_ratio faible (0,1–0,3) avec une transition unique corrélée à un changement comportemental
|
||||
|
||||
#### Détection APT (Advanced Persistent Threat)
|
||||
|
||||
La combinaison `drift_ratio` + `post_ratio` + changement de `path_prefix` permet de détecter les transitions **reconnaissance → exploitation** :
|
||||
- Phase 1 (reconnaissance) : JA4_A, requêtes GET, chemins `/`, `/robots.txt`, `/sitemap.xml`
|
||||
- Transition : changement de JA4 (JA4_A → JA4_B)
|
||||
- Phase 2 (exploitation) : JA4_B, requêtes POST, chemins `/admin/login`, `/wp-json/wp/v2/users`
|
||||
|
||||
Ce pattern — faible drift_ratio global avec une transition ponctuelle coïncidant avec un changement de `post_ratio` et de `path_prefix` dominant — est un indicateur fort d'activité APT coordonnée.
|
||||
|
||||
#### Implémentation
|
||||
|
||||
**Vue ClickHouse** : `view_thesis_features_1h`
|
||||
- Colonne : `ja4_drift_ratio` (Float32)
|
||||
|
||||
**SQL** :
|
||||
|
||||
```sql
|
||||
WITH segmented AS (
|
||||
SELECT
|
||||
src_ip,
|
||||
ja4_fingerprint,
|
||||
-- Segment de 10 minutes
|
||||
toStartOfInterval(event_time, INTERVAL 10 MINUTE) AS segment_ts,
|
||||
count() AS hits_in_segment
|
||||
FROM ja4_processing.sessions
|
||||
WHERE event_time >= now() - INTERVAL 1 HOUR
|
||||
GROUP BY src_ip, ja4_fingerprint, segment_ts
|
||||
),
|
||||
dominant_ja4 AS (
|
||||
SELECT
|
||||
src_ip,
|
||||
segment_ts,
|
||||
argMax(ja4_fingerprint, hits_in_segment) AS dominant_ja4
|
||||
FROM segmented
|
||||
GROUP BY src_ip, segment_ts
|
||||
),
|
||||
ordered AS (
|
||||
SELECT
|
||||
src_ip,
|
||||
segment_ts,
|
||||
dominant_ja4,
|
||||
lagInFrame(dominant_ja4) OVER (
|
||||
PARTITION BY src_ip
|
||||
ORDER BY segment_ts
|
||||
) AS prev_ja4,
|
||||
count() OVER (PARTITION BY src_ip) AS nb_segments
|
||||
FROM dominant_ja4
|
||||
)
|
||||
SELECT
|
||||
src_ip,
|
||||
sum(dominant_ja4 != prev_ja4 AND prev_ja4 IS NOT NULL) /
|
||||
nullIf(max(nb_segments) - 1, 0) AS ja4_drift_ratio
|
||||
FROM ordered
|
||||
GROUP BY src_ip
|
||||
HAVING max(nb_segments) >= 3; -- Minimum 3 segments (30+ minutes)
|
||||
```
|
||||
|
||||
La condition `HAVING max(nb_segments) >= 3` exclut les sessions trop courtes pour un calcul fiable de drift_ratio (voir §6.5 pour les limites). Les sessions exclues reçoivent la valeur `NULL` dans le vecteur feature, traitée par imputation avec la médiane de la population.
|
||||
|
||||
---
|
||||
|
||||
### 5.6 Corrélation DNS passive × flux HTTP (DNS Shadow Analysis) `[todo]`
|
||||
|
||||
#### Constat et motivation
|
||||
|
||||
L'architecture actuelle ne capture pas les requêtes DNS. Or, chaque première visite d'un navigateur vers un domaine est précédée d'une résolution DNS — le client interroge un résolveur récursif pour obtenir l'adresse IP du serveur. Les bots qui ciblent directement des IPs, utilisent un fichier `/etc/hosts` personnalisé, ou recourent au **DNS-over-HTTPS (DoH)** ne génèrent aucune requête DNS observable sur le réseau local.
|
||||
|
||||
Cette asymétrie constitue un signal exploitable : un flux HTTP sans résolution DNS préalable observable est suspect.
|
||||
|
||||
#### Technique : ratio DNS shadow
|
||||
|
||||
Capture DNS passive via `ja4ebpf` étendu au port UDP/53, puis corrélation avec les flux HTTP :
|
||||
|
||||
```
|
||||
dns_shadow_ratio = requêtes HTTP vers hôte X / résolutions DNS de l'hôte X observées
|
||||
```
|
||||
|
||||
Valeurs caractéristiques :
|
||||
- **Navigateur réel** : ratio ≈ 1 (une résolution DNS → de multiples requêtes HTTP via connexions Keep-Alive réutilisées)
|
||||
- **Bot utilisant /etc/hosts ou DoH** : ratio → ∞ (requêtes HTTP sans résolution DNS observable)
|
||||
- **CDN/proxy** : ratio variable mais cohérent dans le temps
|
||||
|
||||
#### DNS-over-HTTPS (DoH)
|
||||
|
||||
**DNS-over-HTTPS** ([RFC 8484](https://www.rfc-editor.org/rfc/rfc8484)) est un protocole qui envoie les requêtes DNS encapsulées dans des connexions HTTPS plutôt qu'en clair sur UDP/53. Ceci prévient l'observation passive des résolutions DNS. Fournisseurs courants : Cloudflare 1.1.1.1, Google 8.8.8.8.
|
||||
|
||||
Les bots utilisant DoH contournent l'analyse DNS shadow. Cependant, les connexions HTTPS vers 1.1.1.1 ou 8.8.8.8 depuis le réseau serveur peuvent elles-mêmes être corrélées comme indicateur d'utilisation de DoH par le client (si observable au niveau réseau) — cette corrélation indirecte est une atténuation partielle.
|
||||
|
||||
#### Extension JA4D
|
||||
|
||||
Le fingerprinting **JA4D/JA4D6** des requêtes DHCP/DHCPv6 pourrait compléter l'analyse en identifiant les dispositifs derrière NAT, ajoutant une couche d'identification au-delà de l'adresse IP. Cette extension est envisagée comme travail futur conjoint à DNS Shadow Analysis.
|
||||
|
||||
**`[todo]` Non implémenté** : nécessite l'extension de `ja4ebpf` pour la capture UDP/53. Travail futur priorité 1 (voir §6.6).
|
||||
|
||||
---
|
||||
|
||||
### 5.7 Détection par invariant de ratio de compression (Compression Ratio Invariant) `[todo]`
|
||||
|
||||
#### Constat et motivation
|
||||
|
||||
La feature `missing_accept_enc_ratio` détecte l'absence de l'en-tête `Accept-Encoding` dans les requêtes HTTP. Certains bots incluent cet en-tête (`Accept-Encoding: gzip, deflate, br`) sans pour autant **traiter effectivement** la compression dans les réponses reçues. L'en-tête est présent pour paraître légitime, mais le bot traite les données brutes sans décompresser.
|
||||
|
||||
#### Technique : invariant de ratio de compression côté serveur
|
||||
|
||||
Instrumentation Apache (module custom) :
|
||||
|
||||
1. Le serveur compare la taille de la réponse compressée envoyée avec la taille `Content-Length` non compressée
|
||||
2. Le **ratio de compression effectif** par session est calculé : `ratio = taille_compressée / taille_non_compressée`
|
||||
3. Ce ratio est corrélé avec les requêtes suivantes du client : un client qui décompresse correctement peut traiter des réponses de taille variable sans délai anormal ; un bot qui ne décompresse pas lit des données plus petites (la réponse compressée) mais échoue à les interpréter correctement
|
||||
|
||||
#### Signal subtil : timing différentiel Brotli vs gzip
|
||||
|
||||
**Brotli** ([RFC 7932](https://www.rfc-editor.org/rfc/rfc7932)) est un algorithme de compression développé par Google, offrant 20 à 26 % de meilleure compression que gzip sur du contenu web, au prix d'un coût computationnel de décompression plus élevé. Il est activé via `Accept-Encoding: br`.
|
||||
|
||||
**gzip** ([RFC 1952](https://www.rfc-editor.org/rfc/rfc1952)) est le format DEFLATE-based largement supporté depuis HTTP/1.1, avec une décompression rapide et faible consommation CPU.
|
||||
|
||||
Un client **réel** est légèrement plus lent à traiter une réponse Brotli (décompression coûteuse) qu'une réponse gzip de même contenu. Ce délai différentiel est mesurable en quelques dizaines de millisecondes sur les connexions à faible latence. Un bot qui n'effectue pas de décompression répond à vitesse constante indépendamment de l'encodage — l'absence de différentiel est le signal.
|
||||
|
||||
**`[todo]` Non implémenté** : nécessite une instrumentation Apache pour le suivi de compression. Interférence avec la compression CDN pour les sessions `has_xff=1`. Travail futur priorité 2 (voir §6.6).
|
||||
|
||||
---
|
||||
|
||||
### 5.8 Empreinte comportementale de session multi-domaine (Cross-Domain Session Linking) `[impl.]`
|
||||
|
||||
#### Constat et motivation
|
||||
|
||||
Dans un environnement multi-hôtes (plusieurs Apache VirtualHosts sur le même serveur), les features sont calculées indépendamment par triplet `(src_ip, ja4, host)`. Un attaquant scannant plusieurs domaines depuis la même IP présente un pattern **inter-domaine** caractéristique invisible dans l'analyse mono-domaine.
|
||||
|
||||
Cette technique fusionne les informations de sessions multi-domaines pour détecter les comportements de **scan d'infrastructure** systématique.
|
||||
|
||||
#### Métrique 1 : host_diversity
|
||||
|
||||
```sql
|
||||
host_diversity = uniqExact(host) OVER (PARTITION BY src_ip)
|
||||
```
|
||||
|
||||
Valeurs caractéristiques :
|
||||
- **Navigation humaine** : 1–3 hôtes (un utilisateur visite rarement plus de 3 domaines hébergés sur la même infrastructure)
|
||||
- **Scanner d'infrastructure** : 10+ hôtes (balayage systématique de tous les VirtualHosts)
|
||||
|
||||
#### Métrique 2 : host_sweep_speed
|
||||
|
||||
```
|
||||
host_sweep_speed = uniqExact(host) / session_duration_seconds
|
||||
```
|
||||
|
||||
Un scanner parcourt rapidement de nombreux hôtes différents. Un utilisateur humain reste longtemps sur un même hôte. La dimension temporelle distingue un utilisateur occasionnel multi-domaine d'un scanner actif.
|
||||
|
||||
#### Métrique 3 : host_coverage_uniformity
|
||||
|
||||
```
|
||||
host_coverage_uniformity = 1 − stddev(hits_per_host) / avg(hits_per_host)
|
||||
```
|
||||
|
||||
Un scanner distribue ses requêtes **uniformément** entre les hôtes (chaque hôte reçoit approximativement le même nombre de requêtes de reconnaissance). Un utilisateur humain se concentre sur 1–2 hôtes avec une distribution très inégale.
|
||||
|
||||
Valeurs caractéristiques :
|
||||
- Scanner : host_coverage_uniformity ≈ 0,8–1,0 (distribution uniforme)
|
||||
- Utilisateur humain : host_coverage_uniformity ≈ 0,1–0,4 (très inégal)
|
||||
|
||||
#### Métrique 4 : cross_domain_path_similarity (coefficient de Jaccard)
|
||||
|
||||
Le **coefficient de Jaccard** mesure la similarité entre deux ensembles :
|
||||
|
||||
```
|
||||
Jaccard(A, B) = |A ∩ B| / |A ∪ B|
|
||||
```
|
||||
|
||||
où :
|
||||
- |A ∩ B| = nombre de chemins apparaissant sur **les deux** hôtes A et B
|
||||
- |A ∪ B| = nombre total de chemins distincts sur les deux hôtes réunis
|
||||
- La valeur est dans [0, 1] : 0 = aucun chemin commun, 1 = ensembles identiques
|
||||
|
||||
Un scanner utilisant une wordlist commune (`/admin`, `/wp-login.php`, `/.env`, `/phpinfo.php`, `/.git/config`) sur tous les hôtes produit un Jaccard élevé (0,7–1,0). Une navigation humaine sur des sites distincts produit un Jaccard proche de 0.
|
||||
|
||||
**Implémentation SQL** :
|
||||
|
||||
```sql
|
||||
WITH paths_by_host AS (
|
||||
SELECT
|
||||
src_ip,
|
||||
host,
|
||||
groupArray(DISTINCT path) AS paths
|
||||
FROM ja4_processing.sessions
|
||||
WHERE event_time >= now() - INTERVAL 1 HOUR
|
||||
GROUP BY src_ip, host
|
||||
),
|
||||
host_pairs AS (
|
||||
SELECT
|
||||
a.src_ip,
|
||||
a.host AS host_a,
|
||||
b.host AS host_b,
|
||||
arrayIntersect(a.paths, b.paths) AS common_paths,
|
||||
arrayDistinct(arrayConcat(a.paths, b.paths)) AS union_paths
|
||||
FROM paths_by_host a
|
||||
JOIN paths_by_host b
|
||||
ON a.src_ip = b.src_ip AND a.host < b.host
|
||||
)
|
||||
SELECT
|
||||
src_ip,
|
||||
avg(length(common_paths) / nullIf(length(union_paths), 0))
|
||||
AS cross_domain_path_similarity
|
||||
FROM host_pairs
|
||||
GROUP BY src_ip;
|
||||
```
|
||||
|
||||
**Table récapitulative des features Cross-Domain** :
|
||||
|
||||
| Feature | Type | Description | Statut |
|
||||
|---------|------|-------------|--------|
|
||||
| `host_diversity` | UInt16 | Nombre d'hôtes distincts par IP | `[impl.]` |
|
||||
| `host_sweep_speed` | Float32 | Hôtes distincts / durée session (hôtes/s) | `[impl.]` |
|
||||
| `host_coverage_uniformity` | Float32 | 1 − stddev/avg des hits par hôte | `[impl.]` |
|
||||
| `cross_domain_path_similarity` | Float32 | Jaccard moyen entre paires d'hôtes | `[impl.]` |
|
||||
|
||||
Ces quatre features sont intégrées dans le vecteur feature du Modèle Complet (famille F8 — Multi-domaine).
|
||||
|
||||
225
docs/thesis/07_discussion_limites.md
Normal file
225
docs/thesis/07_discussion_limites.md
Normal file
@ -0,0 +1,225 @@
|
||||
[<< Sommaire](README.md) | [Suivant >>](08_conclusion_references.md)
|
||||
|
||||
---
|
||||
|
||||
|
||||
## 6. Discussion et limites
|
||||
|
||||
### 6.1 Course aux armements (Arms Race)
|
||||
|
||||
#### Dynamique attaquant/défenseur
|
||||
|
||||
La détection de bots s'inscrit dans une dynamique de course aux armements où chaque technique de détection génère une contre-mesure correspondante. Cette dynamique est bien documentée dans la littérature sur la sécurité réseau ([Fifield et al., 2012](https://arxiv.org/abs/1210.0921)) : l'avantage attaquant décroît quand le nombre de signaux de détection augmente, car le coût d'imitation de plusieurs signaux simultanément croît de manière super-linéaire.
|
||||
|
||||
**Tableau des contre-mesures connues** :
|
||||
|
||||
| Technique de détection | Contre-mesure connue | Évaluation de la contre-mesure |
|
||||
|-----------------------|---------------------|-------------------------------|
|
||||
| JA4 TLS fingerprinting | BotBrowser, curl_cffi, httpcloak | Efficace sur la couche TLS, mais laisse des traces sur H2 |
|
||||
| Analyse des en-têtes HTTP | Frameworks reproduisant l'ordre exact des en-têtes Chrome | Efficace mais ne reproduit pas les en-têtes conditionnel (Sec-Fetch-*, If-None-Match) |
|
||||
| Analyse temporelle (hit_velocity) | Jitter artificiel randomisé | Augmente CV mais laisse une signature d'autocorrélation (§5.3) |
|
||||
| asset_ratio | Playwright/Puppeteer chargeant toutes ressources | Détectable via resource dependency tree (§5.4) |
|
||||
| IP reputation | Proxies résidentiels (Bright Data, Oxylabs) | Contournement partiel mais coût élevé par requête |
|
||||
| Comportement navigation | Scripts imitant les patterns de clic humain | Détectable via cadence fingerprint et entropy de séquence |
|
||||
|
||||
#### Architecture multi-couches comme contre-mesure structurelle
|
||||
|
||||
La mitigation fondamentale est l'**architecture multi-couches** : imiter simultanément 85+ features réparties sur 8 familles et 5 couches réseau (L3 à L7) implique un coût de mise en œuvre croissant de manière exponentielle avec le nombre de signaux. Un attaquant qui maîtrise la couche TLS (L5) peut encore être trahi par le timing TCP (L4) ou les patterns de navigation (L7).
|
||||
|
||||
#### Analyse des outils d'évasion spécifiques
|
||||
|
||||
**BotBrowser** : modifie le code source de Chromium pour supprimer les artefacts d'automatisation. Passe tous les tests de détection JS-based. Détectable via :
|
||||
- Signatures temporelles (le patching de Chromium introduit des artefacts comportementaux subtils dans les timings de rendu)
|
||||
- Frames PRIORITY H2 : BotBrowser utilise l'architecture Chrome → pas de PRIORITY frames (cohérent), mais ne corrige pas les subtilités de `INITIAL_WINDOW_SIZE` sur certaines versions
|
||||
- JA4 TLS identique au vrai Chrome → non discriminant seul
|
||||
|
||||
**curl_cffi** : bibliothèque Python qui utilise libcurl avec un stack TLS patché pour imiter les JA4 Chrome/Firefox. Détectable via :
|
||||
- Ordre pseudo-headers : `curl_cffi` conserve l'ordre curl (`mpsa`), pas Chrome (`masp`)
|
||||
- WINDOW_UPDATE absent (= 0) — non reproduit par curl_cffi
|
||||
- Comportement navigation : absence de cookies de session, absence de `Referer`, absence de `Sec-Fetch-*`
|
||||
|
||||
**httpcloak** : similaire à `curl_cffi`. Reproduit correctement les SETTINGS H2 de Chrome et la valeur WINDOW_UPDATE, mais échoue sur l'ordre pseudo-headers (conserve `mpsa`). Score browser_matcher résultant ≈ 0,60 → zone grise → atténuation active.
|
||||
|
||||
**Playwright/Puppeteer headless** : charge toutes les ressources, reproduit les en-têtes Chrome. Détectable via :
|
||||
- `root_to_first_asset_delay` < 10 ms (§5.4)
|
||||
- `asset_load_stddev` quasi-nul (§5.4)
|
||||
- Absence de variabilité naturelle dans les timings inter-requêtes (§5.3, CV faible)
|
||||
|
||||
### 6.2 Faux positifs et contamination de baseline
|
||||
|
||||
#### Hypothèse semi-supervisée et sa fragilité
|
||||
|
||||
Le modèle EIF repose sur l'hypothèse que le trafic avec `asn_label = 'human'` est effectivement humain, constituant la distribution de référence. Cette hypothèse est fragilisée par deux phénomènes :
|
||||
|
||||
**Proxies résidentiels** (Bright Data, Oxylabs, IPRoyal) : ces services opèrent depuis des ASN étiquetés "résidentiel/human" par les bases de données géo-IP, mais transportent du trafic automatisé pour leurs clients. Leur trafic contaminerait la baseline si inclus.
|
||||
|
||||
**Botnets IoT** : appareils compromis dans des réseaux résidentiels, générant du trafic automatisé depuis des ASN labellisés humains.
|
||||
|
||||
#### Quantification statistique de la contamination
|
||||
|
||||
Avec `contamination = 0.001` (paramètre EIF), au maximum 0,1 % des sessions d'entraînement sont supposées anormales. Sur ~34 000 sessions par cycle, cela représente au maximum **34 sessions contaminées** — insuffisant pour déplacer matériellement la frontière d'isolation EIF.
|
||||
|
||||
Cependant, des proxies résidentiels persistants apparaissant dans **chaque cycle** pourraient accumuler un impact. Soit C_cycle sessions contaminées par cycle, et N cycles d'entraînement accumulés. Si C_cycle ≈ 50 et N = 10, 500 sessions contaminées sur 340 000 (0,15 %) — encore marginal mais non négligeable.
|
||||
|
||||
**Seuil critique** : au-delà de 1 % de contamination de la baseline, la sensibilité EIF chute de manière non linéaire — les sessions contaminées sont suffisamment nombreuses pour constituer leur propre "cluster normal" dans l'espace d'isolation.
|
||||
|
||||
#### Système de mitigation multi-niveau
|
||||
|
||||
| Niveau | Mécanisme | Efficacité estimée |
|
||||
|--------|-----------|-------------------|
|
||||
| 1 | `contamination = 0.001` + déduplication Anubis | Supprime les bots connus avant entraînement |
|
||||
| 2 | Signaux orthogonaux (§5.2, §5.3) résistants à contamination | Détecte bots résistants à l'EIF par des axes indépendants |
|
||||
| 3 | Validation : `anomaly_rate > 20%` → rejet du modèle | Détecte les cycles d'entraînement pathologiques |
|
||||
| 4 | Feedback SOC : FP → reclassification "human" ; TP → exclusion baseline | Correction manuelle des erreurs systématiques |
|
||||
| 5 | Triple ensemble : XGBoost corrige les erreurs systématiques EIF | Supervisé corrige les biais de l'non-supervisé |
|
||||
|
||||
#### Impact du feedback SOC
|
||||
|
||||
Le mécanisme de feedback (niveau 4) est particulièrement important : chaque faux positif identifié par l'équipe SOC reclassifie l'IP en "human" dans la baseline, empêchant sa réinfluence future sur l'EIF. Chaque vrai positif confirmé exclut l'IP de la baseline, purifiant le jeu d'entraînement de manière progressive.
|
||||
|
||||
### 6.3 Vie privée et conformité RGPD
|
||||
|
||||
#### Caractérisation du traitement
|
||||
|
||||
Le fingerprinting réseau opère sans déchiffrement TLS (les métadonnées TLS sont accessibles avant déchiffrement du payload) et sans exécution de code côté client (pas d'injection de JavaScript de tracking, contrairement à FingerprintJS [BotD](https://fingerprint.com/blog/botd/)). Cette caractéristique architecturale a des implications directes sur la conformité RGPD.
|
||||
|
||||
**Analyse de conformité** :
|
||||
|
||||
| Dimension RGPD | Analyse |
|
||||
|----------------|---------|
|
||||
| **Base légale** (Art. 6(1)(f)) | Intérêt légitime : protection de l'infrastructure contre les attaques automatisées — nécessité établie, proportionnalité maintenue par les mécanismes de minimisation |
|
||||
| **Données traitées** | Adresse IP source (nécessaire techniquement pour la communication réseau), fingerprints JA4 (agrégats cryptographiques sans identifiant personnel direct), métriques comportementales agrégées |
|
||||
| **JA4H_d** | Explicitement conçu pour le non-SPII tracking : les suites de chiffrement sont expurgées des informations personnellement identifiantes |
|
||||
| **Minimisation** (Art. 5(1)(e)) | TTL 2h pour les données brutes, 7j pour les agrégats → volume constant, données individuelles non conservées longtemps |
|
||||
| **Tracking inter-sites** | Absent : l'analyse est limitée au trafic propre du serveur, sans corrélation avec d'autres sites |
|
||||
|
||||
**Comparaison avec les alternatives** : les solutions de détection de bots côté client (JavaScript fingerprinting, CAPTCHA) impliquent l'exécution de code sur l'appareil de l'utilisateur et la collecte de données de dispositif (canvas fingerprint, WebGL, résolution d'écran) — données personnelles au sens du RGPD selon plusieurs avis de la CNIL et du WP29. L'approche passive réseau est structurellement plus favorable à la protection de la vie privée.
|
||||
|
||||
### 6.4 Scalabilité et performances
|
||||
|
||||
#### Performances ClickHouse
|
||||
|
||||
[ClickHouse](https://clickhouse.com/docs/en/intro) avec moteur **AggregatingMergeTree** permet :
|
||||
- Ingestion en temps réel : ~100 000 requêtes/seconde sur matériel standard (serveur 16 cœurs, 64 Go RAM)
|
||||
- TTL 2h brut / 7j agrégé : volume de données constant, pas de croissance non bornée
|
||||
- Opérations ARRAY (`arrayIntersect`, `arrayUnion`, `groupArray`, `arrayMap`) : nativement optimisées dans ClickHouse via SIMD et vectorisation
|
||||
|
||||
#### Performances du pipeline ML
|
||||
|
||||
| Composant | Temps d'exécution | Conditions |
|
||||
|-----------|------------------|------------|
|
||||
| EIF training | < 2 secondes | ~34 000 sessions, 96 features |
|
||||
| AE inference | ~50 ms | Batch de 34 000 sessions |
|
||||
| XGBoost inference | ~30 ms | Batch de 34 000 sessions |
|
||||
| HDBSCAN (anomalies) | ~100 ms | ~34 000 sessions, espace latent AE |
|
||||
| HDBSCAN (profiling) | ~2–5 s | Quotidien, ~200k sessions H2 dédupliquées, min_cluster=1000 |
|
||||
| Dynamic matcher scoring | < 1 ms | Par session, lookup en mémoire contre ~5–10 profils |
|
||||
| Louvain (fleet.py) | ~50 ms | Graphe JA4×ASN, communautés |
|
||||
| Fusion LR LR | < 10 ms | Régression logistique, négligeable |
|
||||
| **Cycle complet** | **~300 secondes** | EIF + AE + XGBoost + HDBSCAN + Louvain |
|
||||
|
||||
La durée du cycle (300 s = 5 minutes) est contrainte principalement par la **fenêtre d'agrégation ClickHouse** (1 heure glissante avec recalcul toutes les 5 minutes), non par les temps d'exécution ML.
|
||||
|
||||
#### Limites de scalabilité
|
||||
|
||||
**fleet.py — graphe NetworkX** :
|
||||
- Complexité mémoire : O(V + E) où V = nombre de nœuds JA4 + ASN, E = nombre d'arêtes
|
||||
- À 100 000 JA4/ASN distincts par cycle, la mémoire devient contraignante (~4 Go pour un graphe dense)
|
||||
- **Mitigation** : représentation en matrice sparse (scipy.sparse), réinitialisation du graphe à chaque cycle, seuil minimum d'arêtes (min_ips = 3)
|
||||
|
||||
**HDBSCAN sur l'espace latent AE** :
|
||||
- Complexité temporelle : O(n log n) pour n sessions
|
||||
- À 34 000 sessions/cycle : ~100 ms — acceptable
|
||||
- À 500 000 sessions/cycle (scaling ×15) : ~2 s — encore tolérable
|
||||
|
||||
**Fusion LR logistic regression** : O(n × d) entraînement, d = 3 features d'entrée (scores EIF, AE, XGBoost). Temps négligeable quelle que soit la taille.
|
||||
|
||||
**Limite architecturale principale** : le modèle XGBoost hebdomadaire nécessite un jeu de labels accumulés via le feedback SOC. À faible volume de labels (< 500 sessions étiquetées par semaine), XGBoost ne converge pas correctement. Ce problème est partiellement atténué par le filtrage Cleanlab qui élimine les labels douteux (détail §3.8), mais reste identifié comme axe d'amélioration futur (§6.6 — online learning).
|
||||
|
||||
**Overhead de l'uprobe SSL_read** : un uprobe attaché à `SSL_read` se déclenche à *chaque* appel de lecture TLS, y compris pour les gros transferts de fichiers (images, vidéos, scripts JS volumineux), où une seule requête peut générer des dizaines d'appels `SSL_read` successifs transportant des frames HTTP/2 DATA sans intérêt pour le fingerprinting. Sous forte charge (> 10 000 connexions TLS actives simultanées), cet overhead peut dégrader les performances du serveur web de manière mesurable. Les mitigations recommandées sont : (1) filtrer côté eBPF les invocations dont le buffer ne contient pas les magic bytes HTTP/2 ou HTTP/1.x (`GET `, `POST `, etc.) avant de soumettre au ring buffer ; (2) ignorer les frames HTTP/2 de type DATA de grande taille (longueur payload > 16 384 octets) qui ne contiennent pas d'en-têtes de requête ; (3) appliquer du sampling probabiliste (ex. 1 appel sur 10) pour les connexions déjà identifiées par leur JA4 comme des navigateurs légitimes connus.
|
||||
|
||||
### 6.5 Limites des techniques proposées
|
||||
|
||||
Analyse systématique des limites de chaque technique proposée, avec quantification et mesures d'atténuation :
|
||||
|
||||
| Technique | Limite principale | Condition de déclenchement | Mesure d'atténuation |
|
||||
|-----------|-------------------|---------------------------|---------------------|
|
||||
| **5.1** Path Sequence Entropy | Nécessite une session longue (> 5 requêtes) pour une entropie fiable | Sessions de courte durée, bots single-request | Fallback vers `path_diversity_ratio` pour sessions < 5 requêtes |
|
||||
| **5.2** Bipartite Fleet Graph | Contrainte mémoire O(V+E) à l'échelle | > 100k JA4/ASN distincts par cycle | Représentation sparse, seuil `min_ips`, réinitialisation par cycle |
|
||||
| **5.3** Cadence Fingerprint | Insuffisant pour les sessions single-request | Bots qui font exactement 1 requête par cycle | Agrégation des sessions multi-cycles par IP sur 24h |
|
||||
| **5.4** Resource Dependency Tree | Requiert une classification correcte Content-Type/extension | Chemins sans extension, Content-Type ambigu (`*/*`) | Double-source : en-tête Accept + regex extension chemin |
|
||||
| **5.5** JA4 Drift | Nécessite ≥ 3 segments de 10 min (session ≥ 30 min) | Sessions courtes, reconnaissance rapide | Flag `ja4_drift_insufficient_data` + NULL imputation |
|
||||
| **5.6** DNS Shadow Analysis (`[todo]`) | DoH rend l'observation DNS impossible pour ~20 % des clients modernes | Clients Chrome avec DoH activé par défaut, Firefox DNS-over-HTTPS | Cross-référence IPs de fournisseurs DoH connus (1.1.1.1, 8.8.8.8) |
|
||||
| **5.7** Compression Ratio Invariant (`[todo]`) | Interférence avec la compression CDN | Sessions `has_xff = 1` (CDN recompresse) | Désactivation pour sessions CDN ; instrumentation avant CDN si architecture le permet |
|
||||
| **5.8** Cross-Domain Session Linking | Scénarios NAT / IP partagée (multiples utilisateurs derrière la même IP) | Réseaux d'entreprise, CGNAT opérateurs mobiles | Feature `src_port_diversity` comme discriminant (NAT montre haute diversité de ports source) |
|
||||
|
||||
#### Scénario NAT détaillé (§5.8)
|
||||
|
||||
Dans un réseau d'entreprise sous NAT, 200 utilisateurs partagent une seule IP publique. Leurs navigations sur des intranet/extranet hébergés sur le même serveur Apache produiraient une `host_diversity` artificielle de 5–15 hôtes — fausse alarme. La feature discriminante est `src_port_diversity` :
|
||||
|
||||
```sql
|
||||
src_port_diversity = uniqExact(src_port) OVER (PARTITION BY src_ip, time_window)
|
||||
```
|
||||
|
||||
Un seul utilisateur réel alterne quelques connexions (2–6 ports source actifs). 200 utilisateurs NAT partagent l'IP mais utilisent des milliers de ports source différents (src_port_diversity élevée). Cette feature est présente dans la famille F1 (réseau/transport) et constitue le discriminant naturel contre les faux positifs NAT de §5.8.
|
||||
|
||||
### 6.6 Absence d'évaluation quantitative
|
||||
|
||||
Ce document présente un système opérationnel déployé en production, mais souffre d'une lacune majeure : **il ne fournit pas de métriques d'évaluation standardisées** permettant de mesurer objectivement la performance de détection.
|
||||
|
||||
**Métriques absentes** :
|
||||
- F1-score, Precision, Recall, ROC-AUC, PR-AUC
|
||||
- Matrice de confusion sur un jeu de test labellisé
|
||||
- Taux de faux positifs et faux négatifs par seuil de décision
|
||||
- Comparaison avec des baselines publiques (datasets CICIDS2017, CTU-13, ou benchmarks récents)
|
||||
|
||||
**Le chiffre de "777 anomalies par cycle (≈ 2,3 %)"** est un compteur opérationnel brut : il mesure le nombre de sessions dépassant le seuil d'anomalie configuré, mais ne distingue pas les vrais positifs des faux positifs. En l'absence de ground truth systématique, ce chiffre ne constitue pas un indicateur de performance de détection.
|
||||
|
||||
**Conséquence** : les choix architecturaux (EIF bifurqué, ensemble triple-voix, poids de la fusion LR) sont motivés par des arguments qualitatifs et l'expérience opérationnelle, mais ne sont pas validés par une évaluation quantitative contrôlée. La priorité immédiate pour les travaux futurs est l'établissement d'un protocole d'évaluation sur un dataset labellisé, avec comparaison contre des baselines (Isolation Forest seul, XGBoost seul, LOF, One-Class SVM).
|
||||
|
||||
### 6.7 Travaux futurs et roadmap
|
||||
|
||||
#### Priorité 1 — Extension du vecteur de features
|
||||
|
||||
**browser_matcher maintenance (§3.9)** :
|
||||
- État actuel : `[impl.]` — logique de score complète à 7 dimensions, base de signatures Chrome/Firefox/Safari complète
|
||||
- Données H2 brutes : `[impl.]` — capture des 7 paramètres SETTINGS individuels, WINDOW_UPDATE, flag PRIORITY et ordre pseudo-headers par ja4ebpf via le parser HTTP/2 du flux SSL_read déchiffré. Colonnes individuelles dans `ja4_logs.http_logs` (`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`, `h2_window_update`, `h2_has_priority`, `h2_pseudo_order`)
|
||||
- Profiling dynamique : `[impl.]` — moteur HDBSCAN hors-ligne (`profile_builder.py`, 614 lignes) + scorer temps réel (`browser_matcher_dynamic.py`, 387 lignes). Les profils auto-appris dans `auto_browser_profiles` s'adaptent automatiquement aux nouvelles versions de navigateurs, éliminant la maintenance manuelle du dictionnaire statique.
|
||||
- Travail restant : monitoring de la convergence des clusters dynamiques, validation croisée entre scores statique et dynamique
|
||||
|
||||
**DNS Shadow Analysis (§5.6)** :
|
||||
- État actuel : `[todo]` non implémenté
|
||||
- Travail requis : extension de `ja4ebpf` pour capture UDP/53 + TCP/53, table ClickHouse `dns_resolutions`, corrélation avec table `sessions`
|
||||
- Contraintes techniques : nécessite accès privilégié au trafic réseau niveau interface (pcap ou eBPF)
|
||||
- Estimation : 6–8 semaines de développement + tests de charge
|
||||
|
||||
**Compression Ratio Invariant (§5.7)** :
|
||||
- État actuel : `[todo]` non implémenté
|
||||
- Travail requis : module Apache custom pour logging des tailles pré/post-compression, table ClickHouse dédiée
|
||||
- Estimation : 2–3 semaines de développement
|
||||
|
||||
#### Priorité 2 — Améliorations des modèles ML
|
||||
|
||||
**β-VAE en remplacement de l'AE standard** :
|
||||
Un Variational Autoencoder bêta ([β-VAE, Higgins et al., 2017](https://openreview.net/forum?id=Sy2fchgwl)) structure l'espace latent en imposant une contrainte KL-divergence pondérée par β sur la distribution latente. Cette structure améliore la qualité du score d'anomalie via la divergence KL par rapport à N(0,1), permettant une détection plus fine des anomalies "lentes" (bots qui imitent le comportement humain mais dérivent progressivement).
|
||||
|
||||
**Modélisation des phases d'attaque** :
|
||||
Des modèles d'état-espace pour la modélisation des phases d'attaque — permet de détecter explicitement les transitions de phase (reconnaissance → exploitation) au lieu de seulement scorer chaque session isolément. Complémentaire au signal JA4 Drift (§5.5).
|
||||
|
||||
**t-digest pour la dérive conceptuelle** :
|
||||
Remplacement de l'approximation à 5 quantiles actuellement utilisée pour la détection de drift ([quantile_drift_score]) par la structure **t-digest** , qui supporte les distributions bimodales et les queues longues avec précision adaptative. Critique pour les features à distribution bimodale comme `hit_velocity` (distribution séparée bots/humains).
|
||||
|
||||
**XGBoost → online learning** :
|
||||
Remplacement du ré-entraînement hebdomadaire XGBoost par un apprentissage incrémental (gradient boosting online, par exemple [XGBoost Federated](https://xgboost.readthedocs.io/en/stable/tutorials/federated_learning.html) ou RIVER framework) permettant des mises à jour par cycle au lieu d'attendre l'accumulation d'une semaine de labels.
|
||||
|
||||
#### Priorité 3 — Infrastructure et protocoles émergents
|
||||
|
||||
**HTTP/3 (QUIC)** :
|
||||
HTTP/3 ([RFC 9114](https://www.rfc-editor.org/rfc/rfc9114)) transporte HTTP sur **QUIC** plutôt que TCP. QUIC est un protocole de transport multiplexé et chiffré (UDP-based, TLS 1.3 intégré). Le fingerprinting passif HTTP/3 nécessite une capture différente (UDP au lieu de TCP) et un décodage QUIC. Actuellement hors périmètre — à intégrer lorsque la part de trafic HTTP/3 dépasse 20 % de la baseline.
|
||||
|
||||
**Fingerprinting IPv6** :
|
||||
Les champs spécifiques à IPv6 (extension headers, Hop Limit ≠ TTL IPv4, adresses link-local) constituent des signatures passives supplémentaires. L'analyse du Hop Limit IPv6 est analogue à l'analyse TTL IPv4 mais avec des valeurs initiales différentes.
|
||||
|
||||
**TLS 1.3 0-RTT** :
|
||||
La reprise de session **0-RTT** ([RFC 8446 §2.3](https://www.rfc-editor.org/rfc/rfc8446)) permet d'envoyer des données applicatives dans le premier paquet TLS, avant la completion du handshake. Ces sessions ne contiennent pas de ClientHello complet → fingerprinting JA4 impossible. Traitement spécial requis dans l'EIF (feature `is_0rtt_session` = 1, dimensions TLS neutralisées).
|
||||
|
||||
261
docs/thesis/08_conclusion_references.md
Normal file
261
docs/thesis/08_conclusion_references.md
Normal file
@ -0,0 +1,261 @@
|
||||
[<< Sommaire](README.md) | [Suivant >>]()
|
||||
|
||||
---
|
||||
|
||||
|
||||
## 7. Conclusion
|
||||
|
||||
### Synthèse des composants
|
||||
|
||||
Cette thèse présente une architecture de détection de bots HTTP opérationnelle, validée en production sur des serveurs Apache exposés à un trafic réel. Les contributions principales sont les suivantes.
|
||||
|
||||
#### Composant 1 : Architecture multi-couches L3–L7 avec 96 features
|
||||
|
||||
Un système de détection à couverture complète couvrant cinq couches réseau (L3 réseau : TTL, IP flags ; L4 transport : TCP window, seq patterns ; L5 session TLS : JA4 fingerprint, GREASE, ALPN ; L6 application HTTP : en-têtes, méthodes, User-Agent ; L7 comportemental : navigation, timing, séquences) avec 96 features réparties en 8 familles. Ce système est prouvé à l'échelle (3 M+ logs analysés, ~34 000 sessions/cycle, 5-minute cycle time).
|
||||
|
||||
**La bifurcation Modèle Complet / Modèle Applicatif** est une contribution architecturale spécifique : les sessions sans corrélation entre JA4 et comportement (trafic à travers CDN ou reverse proxy) sont traitées par un modèle séparé évitant les biais d'imputation de features manquantes.
|
||||
|
||||
#### Composant 2 : Ensemble triple-voix avec semi-supervision
|
||||
|
||||
Un pipeline ML combinant :
|
||||
- **Isolation Forest Étendu (EIF)** ([Hariri et al., 2021](https://ieeexplore.ieee.org/document/8888179)) : modèle non-supervisé fondé sur l'isolation aléatoire d'instances anormales dans des espaces de features basse-dimension
|
||||
- **Autoencodeur variationnel (AE)** ([Mirsky et al., NDSS 2018](https://www.ndss-symposium.org/ndss-paper/kitsune-an-ensemble-of-autoencoders-for-online-network-intrusion-detection/)) : détection d'anomalies par reconstruction, capturant les corrélations entre features
|
||||
- **XGBoost supervisé** : correction des erreurs systématiques des modèles non-supervisés via labels SOC accumulés
|
||||
- **Fusion par régression logistique** : fusion des trois scores en un score final calibré
|
||||
|
||||
Le pipeline intègre un mécanisme de **détection de dérive conceptuelle** (basé sur le percentile 5 des scores négatifs) distinguant la dérive organique (évolution naturelle du trafic) de la dérive adversariale (manipulation intentionnelle de la distribution).
|
||||
|
||||
L'**explainabilité** est assurée par l'importance des features par profondeur d'isolation (approche de type ExIFFI) pour l'EIF et SHAP ([Lundberg & Lee, 2017](https://shap.readthedocs.io/)) pour XGBoost, permettant l'audit des décisions de blocage par l'équipe SOC.
|
||||
|
||||
#### Composant 3 : Fingerprinting HTTP/2 passif structuré (browser_matcher)
|
||||
|
||||
Extension du système `browser_confidence` à 6 axes vers une correspondance structurée par famille de navigateur, fondée sur l'analyse passive de 7 dimensions H2 : frame SETTINGS (7 paramètres), WINDOW_UPDATE, ordre des pseudo-headers, frames PRIORITY, cohérence des en-têtes HTTP, structure TLS, et lookup JA4 par dictionnaire.
|
||||
|
||||
La capture passive est réalisée par ja4ebpf via un uprobe sur `SSL_read` (OpenSSL/BoringSSL). Le Go Magic Bytes dispatcher identifie le preface HTTP/2 dans le flux déchiffré, et le parser H2 extrait les frames SETTINGS, WINDOW_UPDATE et HEADERS sans instrumentation serveur. Chaque paramètre SETTINGS est stocké dans une colonne ClickHouse individuelle (`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 -1 pour les paramètres absents, complétés par `h2_window_update`, `h2_has_priority` et `h2_pseudo_order`.
|
||||
|
||||
Cette technique permet de détecter des outils d'évasion qui reproduisent correctement la couche TLS (curl_cffi, httpcloak) mais échouent à reproduire les subtilités H2 — notamment l'ordre des pseudo-headers et la valeur WINDOW_UPDATE. Le module de scoring est entièrement implémenté (`browser_matcher.py`, 497 lignes) avec les signatures des trois familles majeures (Chrome, Firefox, Safari) et trois signatures non-navigateur (curl, python-httpx, Go net/http) dans `browser_signatures.py` (165 lignes).
|
||||
|
||||
Le **profiling dynamique automatique** (`profile_builder.py`, 614 lignes + `browser_matcher_dynamic.py`, 387 lignes) étend cette approche en apprenant les signatures H2 directement à partir du trafic observé via HDBSCAN, sans nécessiter de mise à jour manuelle du dictionnaire. Les centroïdes auto-appris (moyenne + tolérance 3σ pour les continues, mode pour les catégorielles) sont stockés dans `auto_browser_profiles` et scorés en temps réel par distance normalisée pondérée avec confiance volumétrique. Ce mécanisme élimine la fragilité du système statique face aux mises à jour de navigateurs et couvre les implémentations H2 non répertoriées via les familles `Auto_Unknown`.
|
||||
|
||||
La stabilité des empreintes H2 de Chrome sur 2+ ans (novembre 2023 – 2026) contraste favorablement avec JA4 TLS (instable à chaque mise à jour de ciphersuites), justifiant l'investissement dans cette dimension additionnelle.
|
||||
|
||||
#### Composant 4 : Huit techniques originales de détection
|
||||
|
||||
Six techniques entièrement implémentées adressant des angles morts spécifiques :
|
||||
|
||||
1. **Path Sequence Entropy (§5.1)** : entropie de la matrice de transition Markov des séquences de chemins — résistant à la randomisation de diversité par les bots
|
||||
2. **Bipartite Bot Fleet Detection (§5.2)** : détection de flottes distribuées via graphe bipartite JA4×ASN et Louvain sur le graphe projeté pondéré — détecte les botnets dont aucune paire (JA4, ASN) individuelle n'est anormale
|
||||
3. **Request Cadence Fingerprint (§5.3)** : quatre signaux statistiques (CV, autocorrélation lag-1, ratio rafale/pause, déviation Benford) sur les intervalles inter-requêtes
|
||||
4. **Resource Dependency Tree (§5.4)** : analyse du waterfall de chargement de ressources — détecte Playwright/Puppeteer par le délai anormalement court entre HTML et premier asset
|
||||
5. **Intra-Session JA4 Drift (§5.5)** : ratio de transitions de JA4 dominant par fenêtre de 10 minutes — détecte les bots APT multi-phases
|
||||
6. **Cross-Domain Session Linking (§5.8)** : quatre métriques d'analyse inter-domaines incluant le coefficient de Jaccard sur les ensembles de chemins
|
||||
|
||||
Deux techniques identifiées comme travaux futurs (§5.6 DNS Shadow Analysis, §5.7 Compression Ratio Invariant).
|
||||
|
||||
#### Composant 5 : Pipeline ClickHouse scalable
|
||||
|
||||
Architecture de données fondée sur ClickHouse avec **AggregatingMergeTree views** pour le calcul en temps réel de features complexes (entropie, corrélations, graphes), **TTL management** (2h brut, 7j agrégé), et opérations ARRAY natives (arrayIntersect, groupArray, arrayMap). Performances validées : 100 000 requêtes/s en ingestion, cycle complet 300 secondes.
|
||||
|
||||
### État final d'implémentation
|
||||
|
||||
**Tableau récapitulatif des 96 features par famille** (taxonomie sémantique Section 4) :
|
||||
|
||||
| Famille | Description | Features (exemples) | `[impl.]` | `[partiel]` | `[todo]` |
|
||||
|---------|-------------|---------------------|-----------|-------------|---------|
|
||||
| Famille 1 — Volumétrie et vitesse | Signaux de volume et cadence bruts | hits, hit_velocity, max_keepalives, count_login_post (4 features) | 4 | 0 | 0 |
|
||||
| Famille 2 — Diversité et exploration | Diversité des ressources et des signatures réseau | fuzzing_index, path_diversity_ratio, url_depth_variance, distinct_ja4_count, distinct_header_orders, is_ua_rotating, ja4_drift_ratio (7 features) | 7 | 0 | 0 |
|
||||
| Famille 3 — Authenticité protocolaire | Conformité aux standards des navigateurs modernes | modern_browser_score, ua_ch_mismatch, has_accept_language, has_cookie, has_referer, sec_fetch_absence_rate, et 6 autres (12 features) | 12 | 0 | 0 |
|
||||
| Famille 4 — Cohérence cross-layer | Cohérence entre les couches TCP, TLS et HTTP | alpn_http_mismatch, is_alpn_missing, sni_host_mismatch, tls_h2_family_mismatch, tls12_ratio, http10_ratio, et 8 autres (14 features) | 14 | 0 | 0 |
|
||||
| Famille 5 — Empreinte réseau | Caractéristiques de la pile réseau du client | ip_id_zero_ratio, avg_ttl, ttl_std, no_window_scale_ratio, ip_asn, ip_country, ja4_asn_rarity, et 6 autres (13 features) | 13 | 0 | 0 |
|
||||
| Famille 6 — Comportement de navigation | Patterns de navigation et structure des requêtes | asset_ratio, direct_access_ratio, orphan_ratio, temporal_entropy, post_ratio, head_ratio, http_scheme_ratio, et 3 autres (10 features) | 10 | 0 | 0 |
|
||||
| Famille 7 — Intelligence contextuelle | Enrichissements contextuels et browser scoring | ja4_asn_concentration, browser_confidence, browser_match_chrome, browser_match_firefox, browser_match_safari, browser_match_max, browser_family_detected, et 16 autres (23 features) | 23 | 0 | 0 |
|
||||
| Famille 8 — Features comportementales avancées | Features originales de recherche (§5.1–§5.8) | path_transition_entropy, cadence_cv, lag1_autocorrelation, burst_ratio, benford_deviation, root_to_first_asset_delay, ja4_drift_ratio, host_diversity, et 5 autres (13 features) | 13 | 0 | 0 |
|
||||
| **Total** | | | **96** | **0** | **0** |
|
||||
|
||||
**Résumé quantitatif** : les 96 features documentées dans la Section 4 sont **toutes entièrement implémentées** (`[impl.]`), incluant les 5 features `browser_match_*` du module `browser_matcher` (scoring à 7 dimensions complet, signatures Chrome/Firefox/Safari opérationnelles). Les features des techniques §5.6 (DNS Shadow Analysis) et §5.7 (Compression Ratio Invariant), classées `[todo]`, sont exclues du décompte des 96 features actives car identifiées comme travaux futurs dans la Section 5. Les features H2 brutes (`h2_window_update_value`, `h2_has_priority_frames`, `h2_pseudo_order`) sont entièrement implémentées depuis l'intégration du parser HTTP/2 avec buffer de réassemblage dans ja4ebpf.
|
||||
|
||||
### Perspective
|
||||
|
||||
Le système atteint ses objectifs opérationnels actuels. La capture HTTP/2 passive est intégrée avec 12 colonnes individuelles dans `ja4_logs.http_logs`, et le module `browser_matcher` est opérationnel avec ses 7 dimensions de scoring statique. Le moteur de profiling dynamique automatique (§3.9.6) complète le système statique en apprenant les signatures H2 à partir du trafic réel, éliminant la dépendance aux signatures codées en dur. Les axes d'amélioration prioritaires sont le monitoring de la convergence des clusters dynamiques, l'extension DNS Shadow Analysis pour la couverture DNS (`[todo]` → `[partiel]`), et le passage à l'apprentissage en ligne pour XGBoost. À plus long terme, le support HTTP/3 (QUIC) deviendra nécessaire à mesure que la proportion de trafic HTTP/3 augmente dans la baseline.
|
||||
|
||||
La modélisation des phases d'attaque séquentielles par des modèles d'état-espace constitue une piste de recherche prometteuse, qui permettrait de modéliser explicitement les phases d'attaque séquentielles — comblant la lacune actuelle entre la détection de sessions individuelles et la détection de campagnes d'attaque coordonnées multi-phases.
|
||||
|
||||
---
|
||||
|
||||
## 8. Références
|
||||
|
||||
### Standards et RFC
|
||||
|
||||
[1] **RFC 7540 — HTTP/2**
|
||||
Internet Engineering Task Force. "Hypertext Transfer Protocol Version 2 (HTTP/2)." May 2015.
|
||||
[https://www.rfc-editor.org/rfc/rfc7540](https://www.rfc-editor.org/rfc/rfc7540)
|
||||
|
||||
[2] **RFC 9113 — HTTP/2 (révision)**
|
||||
Internet Engineering Task Force. "HTTP/2." June 2022.
|
||||
[https://www.rfc-editor.org/rfc/rfc9113](https://www.rfc-editor.org/rfc/rfc9113)
|
||||
|
||||
[3] **RFC 7541 — HPACK (compression d'en-têtes HTTP/2)**
|
||||
Internet Engineering Task Force. "HPACK: Header Compression for HTTP/2." May 2015.
|
||||
[https://www.rfc-editor.org/rfc/rfc7541](https://www.rfc-editor.org/rfc/rfc7541)
|
||||
|
||||
[4] **RFC 9218 — Priorités HTTP extensibles**
|
||||
Internet Engineering Task Force. "Extensible Prioritization Scheme for HTTP." June 2022.
|
||||
[https://www.rfc-editor.org/rfc/rfc9218](https://www.rfc-editor.org/rfc/rfc9218)
|
||||
|
||||
[5] **RFC 8701 — GREASE (Generating Random Extensions And Sustaining Extensibility)**
|
||||
Internet Engineering Task Force. "Applying Generate Random Extensions And Sustain Extensibility (GREASE) to TLS Extensibility." January 2020.
|
||||
[https://www.rfc-editor.org/rfc/rfc8701](https://www.rfc-editor.org/rfc/rfc8701)
|
||||
|
||||
[6] **RFC 7301 — ALPN (Application-Layer Protocol Negotiation)**
|
||||
Internet Engineering Task Force. "Transport Layer Security (TLS) Application-Layer Protocol Negotiation Extension." July 2014.
|
||||
[https://www.rfc-editor.org/rfc/rfc7301](https://www.rfc-editor.org/rfc/rfc7301)
|
||||
|
||||
[7] **RFC 6066 — SNI (Server Name Indication)**
|
||||
Internet Engineering Task Force. "Transport Layer Security (TLS) Extensions: Extension Definitions." January 2011.
|
||||
[https://www.rfc-editor.org/rfc/rfc6066](https://www.rfc-editor.org/rfc/rfc6066)
|
||||
|
||||
[8] **RFC 7932 — Brotli**
|
||||
Internet Engineering Task Force. "Brotli Compressed Data Format." July 2016.
|
||||
[https://www.rfc-editor.org/rfc/rfc7932](https://www.rfc-editor.org/rfc/rfc7932)
|
||||
|
||||
[9] **RFC 1952 — gzip**
|
||||
Internet Engineering Task Force. "GZIP file format specification version 4.3." May 1996.
|
||||
[https://www.rfc-editor.org/rfc/rfc1952](https://www.rfc-editor.org/rfc/rfc1952)
|
||||
|
||||
[10] **RFC 8484 — DNS-over-HTTPS (DoH)**
|
||||
Internet Engineering Task Force. "DNS Queries over HTTPS (DoH)." October 2018.
|
||||
[https://www.rfc-editor.org/rfc/rfc8484](https://www.rfc-editor.org/rfc/rfc8484)
|
||||
|
||||
[11] **RFC 7323 — TCP Window Scale**
|
||||
Internet Engineering Task Force. "TCP Extensions for High Performance." September 2014.
|
||||
[https://www.rfc-editor.org/rfc/rfc7323](https://www.rfc-editor.org/rfc/rfc7323)
|
||||
|
||||
[12] **RFC 8446 — TLS 1.3**
|
||||
Internet Engineering Task Force. "The Transport Layer Security (TLS) Protocol Version 1.3." August 2018.
|
||||
[https://www.rfc-editor.org/rfc/rfc8446](https://www.rfc-editor.org/rfc/rfc8446)
|
||||
|
||||
[13] **RFC 9114 — HTTP/3**
|
||||
Internet Engineering Task Force. "HTTP/3." June 2022.
|
||||
[https://www.rfc-editor.org/rfc/rfc9114](https://www.rfc-editor.org/rfc/rfc9114)
|
||||
|
||||
---
|
||||
|
||||
### Algorithmes et modèles de machine learning
|
||||
|
||||
[14] **Liu, F. T., Ting, K. M., & Zhou, Z. H. (2008)**
|
||||
"Isolation Forest."
|
||||
In Proceedings of the 8th IEEE International Conference on Data Mining (ICDM), pp. 413–422.
|
||||
[https://cs.nju.edu.cn/zhouzh/zhouzh.files/publication/icdm08b.pdf](https://cs.nju.edu.cn/zhouzh/zhouzh.files/publication/icdm08b.pdf)
|
||||
|
||||
[15] **Hariri, S., Kind, M. C., & Brunner, R. J. (2021)**
|
||||
"Extended Isolation Forest."
|
||||
IEEE Transactions on Knowledge and Data Engineering, 33(4), 1479–1489.
|
||||
[https://ieeexplore.ieee.org/document/8888179](https://ieeexplore.ieee.org/document/8888179)
|
||||
|
||||
[16] **isotree — bibliothèque Extended Isolation Forest**
|
||||
Córtes, D. isotree: Isolation-based Outlier Detection in Python/R.
|
||||
[https://github.com/david-cortes/isotree](https://github.com/david-cortes/isotree)
|
||||
|
||||
[17] **Mirsky, Y., Doitshman, T., Elovici, Y., & Shabtai, A. (2018)**
|
||||
"Kitsune: An Ensemble of Autoencoders for Online Network Intrusion Detection."
|
||||
In Proceedings of the Network and Distributed System Security Symposium (NDSS).
|
||||
[https://www.ndss-symposium.org/ndss-paper/kitsune-an-ensemble-of-autoencoders-for-online-network-intrusion-detection/](https://www.ndss-symposium.org/ndss-paper/kitsune-an-ensemble-of-autoencoders-for-online-network-intrusion-detection/)
|
||||
|
||||
[18] **Campello, R. J. G. B., Moulavi, D., & Sander, J. (2013)**
|
||||
"Density-Based Clustering Based on Hierarchical Density Estimates."
|
||||
In Pacific-Asia Conference on Knowledge Discovery and Data Mining (PAKDD), Lecture Notes in Computer Science, vol 7819, pp. 160–172.
|
||||
[https://link.springer.com/chapter/10.1007/978-3-642-37456-2_14](https://link.springer.com/chapter/10.1007/978-3-642-37456-2_14)
|
||||
|
||||
[19] **Lundberg, S. M., & Lee, S. I. (2017)**
|
||||
"A Unified Approach to Interpreting Model Predictions."
|
||||
In Advances in Neural Information Processing Systems (NeurIPS), 30.
|
||||
Documentation officielle SHAP : [https://shap.readthedocs.io/](https://shap.readthedocs.io/)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[24] **Higgins, I., Matthey, L., Pal, A., Burgess, C., Glorot, X., Botvinick, M., Mohamed, S., & Lerchner, A. (2017)**
|
||||
"β-VAE: Learning Basic Visual Concepts with a Constrained Variational Framework."
|
||||
In International Conference on Learning Representations (ICLR).
|
||||
[https://openreview.net/forum?id=Sy2fchgwl](https://openreview.net/forum?id=Sy2fchgwl)
|
||||
|
||||
---
|
||||
|
||||
### Fingerprinting réseau et détection de bots
|
||||
|
||||
[25] **Althouse, J., Atkinson, J., & Dorsey, J. (2017)**
|
||||
"JA3 — A Method For Profiling SSL/TLS Client Hello Messages."
|
||||
Salesforce Engineering Blog. GitHub repository.
|
||||
[https://github.com/salesforce/ja3](https://github.com/salesforce/ja3)
|
||||
|
||||
[26] **Althouse, J. (2023)**
|
||||
"JA4+ — A Suite of Network Fingerprints."
|
||||
FoxIO LLC. GitHub repository.
|
||||
[https://github.com/FoxIO-LLC/ja4](https://github.com/FoxIO-LLC/ja4)
|
||||
|
||||
[27] **Bartlett, J. (2023)**
|
||||
"HTTP/2 Fingerprinting for Bot Detection."
|
||||
Cloudflare Blog.
|
||||
[https://blog.cloudflare.com/cloudflare-bot-management-machine-learning-and-more/](https://blog.cloudflare.com/cloudflare-bot-management-machine-learning-and-more/)
|
||||
|
||||
[28] **Imperva (2024)**
|
||||
"Bad Bot Report 2024."
|
||||
Imperva Threat Research.
|
||||
[https://www.imperva.com/resources/resource-library/reports/bad-bot-report/](https://www.imperva.com/resources/resource-library/reports/bad-bot-report/)
|
||||
|
||||
[29] **Osama, A., Adi, E., & Baig, Z. (2025)**
|
||||
"Augmenting WAF Payloads via Large Language Models."
|
||||
arXiv preprint arXiv:2512.23610.
|
||||
[https://arxiv.org/abs/2512.23610](https://arxiv.org/abs/2512.23610)
|
||||
|
||||
[30] **Fifield, D., & Egelman, S. (2012)**
|
||||
"Fingerprintability of WebRTC."
|
||||
arXiv preprint arXiv:1210.0921.
|
||||
[https://arxiv.org/abs/1210.0921](https://arxiv.org/abs/1210.0921)
|
||||
|
||||
---
|
||||
|
||||
### Outils et bibliothèques
|
||||
|
||||
[31] **TecharoHQ Anubis** — Protection anti-bot basée sur des challenges Web PoW (Proof of Work) pour Apache/Nginx.
|
||||
[https://github.com/TecharoHQ/anubis](https://github.com/TecharoHQ/anubis)
|
||||
|
||||
[32] **FingerprintJS BotD** — Détection de bots côté client (JavaScript) par fingerprinting de dispositif.
|
||||
[https://fingerprint.com/blog/botd/](https://fingerprint.com/blog/botd/)
|
||||
|
||||
[33] **ClickHouse** — Système de gestion de bases de données analytiques orienté colonnes, haute performance.
|
||||
[https://clickhouse.com/docs/en/intro](https://clickhouse.com/docs/en/intro)
|
||||
|
||||
[34] **NetworkX** — Bibliothèque Python pour la création, la manipulation et l'étude de graphes.
|
||||
[https://networkx.org/documentation/stable/](https://networkx.org/documentation/stable/)
|
||||
|
||||
[35] **HDBSCAN Python library** — Implémentation performante de l'algorithme HDBSCAN.
|
||||
[https://hdbscan.readthedocs.io/en/latest/](https://hdbscan.readthedocs.io/en/latest/)
|
||||
|
||||
[36] **XGBoost** — Bibliothèque de gradient boosting optimisée.
|
||||
[https://xgboost.readthedocs.io/en/stable/](https://xgboost.readthedocs.io/en/stable/)
|
||||
|
||||
---
|
||||
|
||||
### Statistiques et mathématiques
|
||||
|
||||
[37] **Benford, F. (1938)**
|
||||
"The Law of Anomalous Numbers."
|
||||
Proceedings of the American Philosophical Society, 78(4), 551–572.
|
||||
Article encyclopédique de référence : [https://en.wikipedia.org/wiki/Benford%27s_law](https://en.wikipedia.org/wiki/Benford%27s_law)
|
||||
|
||||
[38] **Jaccard, P. (1912)**
|
||||
"The Distribution of the Flora in the Alpine Zone."
|
||||
New Phytologist, 11(2), 37–50.
|
||||
Article encyclopédique : [https://en.wikipedia.org/wiki/Jaccard_index](https://en.wikipedia.org/wiki/Jaccard_index)
|
||||
|
||||
[39] **Markov, A. A. (1906)**
|
||||
"Extension of the Limit Theorems of Probability Theory to a Sum of Variables Connected in a Chain."
|
||||
Notes of the Imperial Academy of Sciences of St. Petersburg.
|
||||
Article encyclopédique : [https://en.wikipedia.org/wiki/Markov_chain](https://en.wikipedia.org/wiki/Markov_chain)
|
||||
|
||||
---
|
||||
|
||||
### Benchmarks et datasets de référence
|
||||
|
||||
17
docs/thesis/README.md
Normal file
17
docs/thesis/README.md
Normal file
@ -0,0 +1,17 @@
|
||||
# Détection et Classification du Trafic HTTP Malveillant
|
||||
|
||||
**Document technique — Avril 2026 — Version 4.0**
|
||||
|
||||
Ce document est divisé en 9 parties :
|
||||
|
||||
| Fichier | Contenu | Lignes |
|
||||
|---------|---------|--------|
|
||||
| [00_resume.md](00_resume.md) | Titre, résumé, table des matières | 75 |
|
||||
| [01_introduction.md](01_introduction.md) | Section 1 — Introduction, contexte, générations de défenses | 50 |
|
||||
| [02_etat_de_lart.md](02_etat_de_lart.md) | Section 2 — État de l'art (règles statiques, fingerprinting, ML) | 208 |
|
||||
| [03_architecture.md](03_architecture.md) | Section 3.1–3.8 — Architecture multi-couches, pipeline ML | 767 |
|
||||
| [04_browser_matcher.md](04_browser_matcher.md) | Section 3.9 — Browser Signature Detection (browser_matcher) | 481 |
|
||||
| [05_features.md](05_features.md) | Section 4 — Taxonomie des 96 features (8 familles) | 682 |
|
||||
| [06_techniques_avancees.md](06_techniques_avancees.md) | Section 5 — Techniques comportementales avancées (§5.1–5.8) | 669 |
|
||||
| [07_discussion_limites.md](07_discussion_limites.md) | Section 6 — Discussion, limites, scalabilité, RGPD | 207 |
|
||||
| [08_conclusion_references.md](08_conclusion_references.md) | Sections 7–8 — Conclusion et références | 277 |
|
||||
Reference in New Issue
Block a user