ℹ️ À savoir · En bref
L'advanced hunting interroge 30 jours de données brutes de Defender (postes, identités, messagerie, applications cloud) avec KQL, le langage de Sentinel et de Log Analytics.
Une requête qui trouve quelque chose d'utile peut devenir une règle de détection personnalisée : elle tourne seule et crée des alertes.
Objectifs de la fiche
- Connaître les tables principales et comment les relier.
- Écrire des requêtes KQL lisibles et efficaces.
- Disposer de requêtes types prêtes à adapter.
- Transformer une requête en règle de détection.
Les tables et leurs clés

| Table | Contenu | Colonnes utiles |
|---|---|---|
DeviceInfo | Inventaire des appareils (mis à jour régulièrement) | DeviceId, DeviceName, OSPlatform, MachineGroup |
DeviceProcessEvents | Créations de processus | FileName, ProcessCommandLine, InitiatingProcessFileName, AccountName |
DeviceNetworkEvents | Connexions réseau | RemoteIP, RemotePort, RemoteUrl, ActionType |
DeviceLogonEvents | Ouvertures de session sur les appareils | LogonType, AccountName, RemoteIP, ActionType |
DeviceFileEvents | Créations, modifications de fichiers | FileName, FolderPath, SHA256 |
IdentityLogonEvents | Authentifications AD et Entra (Defender for Identity) | AccountUpn, Application, LogonType, FailureReason |
EmailEvents | Messages reçus et envoyés | SenderFromAddress, Subject, ThreatTypes, DeliveryAction |
AlertInfo / AlertEvidence | Alertes et entités associées | AlertId, Title, Severity, DeviceId, AccountUpn |
KQL en 10 opérateurs

| Opérateur | Rôle | Exemple |
|---|---|---|
where | Filtrer les lignes | where Timestamp > ago(7d) |
project | Garder / renommer des colonnes | project Timestamp, DeviceName, ProcessCommandLine |
extend | Ajouter une colonne calculée | extend Heure = hourofday(Timestamp) |
summarize | Agréger | summarize Nb = count() by DeviceName |
top / take | Limiter les résultats | top 20 by Nb desc |
join | Relier deux tables | join kind=inner (DeviceInfo) on DeviceId |
has / contains | Recherche de texte | has cherche un mot entier (rapide), contains une sous-chaîne (lent) |
in~ / =~ | Comparaison insensible à la casse | where FileName in~ ("powershell.exe","pwsh.exe") |
let | Variable ou sous-requête | let admins = dynamic(["adm-alice"]); |
render | Graphique | render timechart |
💡 Astuce
Filtre d'abord sur le temps et la table la plus sélective, puis seulement le texte. Préfère has à contains, et == à =~ quand la casse est connue.
Dans le portail, le bouton Afficher le schéma et l'aide à la saisie évitent les fautes de noms de colonnes.
Requêtes types
processus suspects
// PowerShell lancé avec une commande encodée (souvent malveillant)
DeviceProcessEvents
| where Timestamp > ago(7d)
| where FileName in~ ("powershell.exe", "pwsh.exe")
| where ProcessCommandLine matches regex @"(?i)\s-(e|en|enc|encodedcommand)\s"
| project Timestamp, DeviceName, AccountName, ProcessCommandLine,
Parent = InitiatingProcessFileName
| order by Timestamp desc
identités
// Échecs d'authentification par compte (force brute, mot de passe expiré…)
IdentityLogonEvents
| where Timestamp > ago(1d) and ActionType == "LogonFailed"
| summarize Echecs = count(), Sources = dcount(IPAddress) by AccountUpn, FailureReason
| where Echecs > 20
| order by Echecs desc
réseau
// Poste qui contacte un domaine donné, avec l'utilisateur
DeviceNetworkEvents
| where Timestamp > ago(3d) and RemoteUrl has "exemple-suspect.com"
| join kind=inner (DeviceInfo | summarize arg_max(Timestamp, OSPlatform) by DeviceId) on DeviceId
| project Timestamp, DeviceName, OSPlatform, InitiatingProcessAccountName, RemoteUrl, RemoteIP
contexte Citrix
// Citrix : processus lancés dans les sessions des VDA (hors système)
DeviceProcessEvents
| where Timestamp > ago(1d)
| where DeviceName startswith "vda-"
| where AccountName !in~ ("system", "local service", "network service")
| summarize Lancements = count(), Utilisateurs = dcount(AccountName) by FileName
| top 25 by Lancements desc
Limites à connaître
| Paramètre | Valeur |
|---|---|
| Profondeur | 30 jours pour les données natives Defender XDR (plus avec les tables Sentinel, selon leur rétention) |
| Résultats | 100 000 lignes et 64 Mo maximum par requête |
| Durée | 10 minutes maximum par requête |
| Ressources | Quota CPU par tenant, recalculé toutes les 15 minutes ; requêtes bloquées si dépassé |
Des requêtes aux détections
- Depuis l'éditeur : Créer une règle de détection ; la requête doit renvoyer
Timestamp,ReportIdet une colonne d'entité (DeviceId,AccountUpn…). - Fréquences : en continu (quasi temps réel, pour les requêtes simples), ou toutes les 1, 3, 12 ou 24 h.
- Actions automatiques possibles : isoler l'appareil, lancer une analyse antivirus, désactiver l'utilisateur, mettre un fichier en quarantaine.
- Les règles ont leur propre quota : garde-les ciblées et testées sur 30 jours avant activation.
ℹ️ À savoir
Sentinel est désormais exploité dans le portail Defender (retrait de Sentinel du portail Azure annoncé pour le 31 mars 2027) : advanced hunting peut interroger dans la même requête les tables Defender et les journaux Sentinel (pare-feu, NetScaler, serveurs…).
Pièges classiques
| Symptôme | Cause | Parade |
|---|---|---|
| Requête trop lente ou bloquée | Pas de filtre de temps, contains sur de gros volumes | Filtre Timestamp en premier, has, colonnes réduites |
| Résultats tronqués | Plus de 100 000 lignes | Agréger (summarize) ou affiner les filtres |
| Join qui explose | Table de droite non dédupliquée (DeviceInfo a plusieurs lignes par appareil) | summarize arg_max(Timestamp, *) by DeviceId avant le join |
| Rien trouvé sur un serveur | Appareil non intégré ou mauvais nom (FQDN vs nom court) | Vérifier DeviceInfo, utiliser startswith ou has |
| Règle de détection refusée | Colonnes obligatoires absentes | Renvoyer Timestamp, ReportId et une entité |
🔷 En contexte Citrix
Sur des VDA non persistants intégrés sans l'option « une entrée par appareil », un même poste peut apparaître sous plusieurs DeviceId : raisonne par DeviceName ou par groupe d'appareils pour les requêtes Citrix (voir M365-DEF-01).
Cherche les sorties d'environnement : processus inattendus lancés dans les sessions (cmd, PowerShell, outils d'accès distant) sur les VDA publiés.
À retenir
- Tables Device* reliées par DeviceId ; identités par AccountUpn.
- Filtrer le temps d'abord ;
hasplutôt quecontains. - summarize + by pour agréger, join avec une table dédupliquée.
- Limites : 30 jours, 100 000 lignes, 10 min, quota CPU.
- Une bonne requête devient une règle de détection personnalisée.
Auto-évaluation
Q1. Quelle différence entre has et contains ?
→ has cherche un terme entier indexé (rapide) ; contains cherche une sous-chaîne n'importe où (plus lent).
Q2. Comment compter les échecs de connexion par compte sur 24 h ?
→ IdentityLogonEvents | where Timestamp > ago(1d) and ActionType == "LogonFailed" | summarize count() by AccountUpn
Q3. Pourquoi dédupliquer DeviceInfo avant un join ?
→ La table contient plusieurs lignes par appareil ; sans arg_max, chaque événement est multiplié.
Q4. Quelles colonnes une règle de détection personnalisée doit-elle renvoyer ?
→ Timestamp, ReportId et au moins une colonne d'entité (DeviceId, AccountUpn…).