ℹ️ À 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

DeviceId relie les tables Endpoint ; l'UPN relie aux identités ; AlertEvidence relie les alertes aux entités
DeviceId relie les tables Endpoint ; l'UPN relie aux identités ; AlertEvidence relie les alertes aux entités
TableContenuColonnes utiles
DeviceInfoInventaire des appareils (mis à jour régulièrement)DeviceId, DeviceName, OSPlatform, MachineGroup
DeviceProcessEventsCréations de processusFileName, ProcessCommandLine, InitiatingProcessFileName, AccountName
DeviceNetworkEventsConnexions réseauRemoteIP, RemotePort, RemoteUrl, ActionType
DeviceLogonEventsOuvertures de session sur les appareilsLogonType, AccountName, RemoteIP, ActionType
DeviceFileEventsCréations, modifications de fichiersFileName, FolderPath, SHA256
IdentityLogonEventsAuthentifications AD et Entra (Defender for Identity)AccountUpn, Application, LogonType, FailureReason
EmailEventsMessages reçus et envoyésSenderFromAddress, Subject, ThreatTypes, DeliveryAction
AlertInfo / AlertEvidenceAlertes et entités associéesAlertId, Title, Severity, DeviceId, AccountUpn

KQL en 10 opérateurs

Une requête est un pipeline : on filtre tôt, on réduit les colonnes, on agrège
Une requête est un pipeline : on filtre tôt, on réduit les colonnes, on agrège
OpérateurRôleExemple
whereFiltrer les ligneswhere Timestamp > ago(7d)
projectGarder / renommer des colonnesproject Timestamp, DeviceName, ProcessCommandLine
extendAjouter une colonne calculéeextend Heure = hourofday(Timestamp)
summarizeAgrégersummarize Nb = count() by DeviceName
top / takeLimiter les résultatstop 20 by Nb desc
joinRelier deux tablesjoin kind=inner (DeviceInfo) on DeviceId
has / containsRecherche de textehas cherche un mot entier (rapide), contains une sous-chaîne (lent)
in~ / =~Comparaison insensible à la cassewhere FileName in~ ("powershell.exe","pwsh.exe")
letVariable ou sous-requêtelet admins = dynamic(["adm-alice"]);
renderGraphiquerender 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ètreValeur
Profondeur30 jours pour les données natives Defender XDR (plus avec les tables Sentinel, selon leur rétention)
Résultats100 000 lignes et 64 Mo maximum par requête
Durée10 minutes maximum par requête
RessourcesQuota 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, ReportId et 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ômeCauseParade
Requête trop lente ou bloquéePas de filtre de temps, contains sur de gros volumesFiltre Timestamp en premier, has, colonnes réduites
Résultats tronquésPlus de 100 000 lignesAgréger (summarize) ou affiner les filtres
Join qui exploseTable 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 serveurAppareil non intégré ou mauvais nom (FQDN vs nom court)Vérifier DeviceInfo, utiliser startswith ou has
Règle de détection refuséeColonnes obligatoires absentesRenvoyer 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 ; has plutôt que contains.
  • 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…).

Pour aller plus loin