Partager via


Pratiques recommandées pour la requête de repérage avancé

S’applique à :

  • Microsoft Defender XDR

Appliquez ces recommandations pour obtenir des résultats plus rapidement et éviter les délais d’expiration lors de l’exécution de requêtes complexes. Si vous souhaitez en savoir plus sur l’amélioration des performances de requête, veuillez consulter Meilleures pratiques de requête Kusto.

Comprendre les quotas de ressources processeur

En fonction de sa taille, chaque locataire a accès à une quantité définie de ressources processeur allouées pour l’exécution de requêtes de repérage avancées. Pour plus d’informations sur les différents paramètres d’utilisation, consultez quotas de chasse avancés et paramètres d’utilisation.

Après avoir exécuté votre requête, vous pouvez voir le temps d’exécution et son utilisation des ressources (faible, moyen, élevé). Une valeur élevée indique que l’exécution de la requête a pris plus de ressources et qu’elle pourrait être améliorée pour retourner les résultats plus efficacement.

Détails de la requête sous l’onglet **Résultats** dans le portail Microsoft Defender

Les clients qui exécutent régulièrement plusieurs requêtes doivent suivre la consommation et appliquer les conseils d’optimisation de cet article pour réduire les interruptions résultant du dépassement des quotas ou des paramètres d’utilisation.

Regardez Optimisation des requêtes KQL pour voir certaines des façons les plus courantes d’améliorer vos requêtes.

Conseils d’optimisation généraux

  • Dimensionner les nouvelles requêtes : si vous pensez qu’une requête retourne un jeu de résultats volumineux, évaluez-le d’abord à l’aide de l’opérateur count. Utilisez limit ou son synonyme take pour éviter les jeux de résultats volumineux.

  • Appliquer les filtres tôt : appliquez des filtres de temps et d’autres filtres pour réduire le jeu de données, en particulier avant d’utiliser des fonctions de transformation et d’analyse, telles que substring(),replace(), trim(), toupper() ou parse_json() . Dans l’exemple ci-dessous, la fonction d’analyse extractjson() est utilisée après que les opérateurs de filtrage ont réduit le nombre d’enregistrements.

    DeviceEvents
    | where Timestamp > ago(1d)
    | where ActionType == "UsbDriveMount"
    | where DeviceName == "user-desktop.domain.com"
    | extend DriveLetter = extractjson("$.DriveLetter", AdditionalFields)
    
  • Has beats contains : pour éviter de rechercher inutilement des sous-chaînes dans des mots, utilisez l’opérateur has au lieu de contains. En savoir plus sur les opérateurs de chaîne

  • Rechercher dans des colonnes spécifiques : recherchez dans une colonne spécifique plutôt que d’exécuter des recherches en texte intégral sur toutes les colonnes. N’utilisez * pas pour case activée toutes les colonnes.

  • Respect de la casse pour la vitesse : les recherches respectant la casse sont plus spécifiques et généralement plus performantes. Les noms des opérateurs de chaîne respectant la casse, tels que has_cs et contains_cs, se terminent généralement par _cs. Vous pouvez également utiliser l’opérateur == equals respectant la casse au lieu de =~.

  • Analyser, ne pas extraire : dans la mesure du possible, utilisez l’opérateur d’analyse ou une fonction d’analyse comme parse_json(). Évitez l’opérateur matches regex de chaîne ou la fonction extract(), qui utilisent toutes deux une expression régulière. Réservez l’utilisation de l’expression régulière pour des scénarios plus complexes. En savoir plus sur les fonctions d’analyse

  • Filtrer les tables et non les expressions : ne filtrez pas sur une colonne calculée si vous pouvez filtrer sur une colonne de table.

  • Pas de termes à trois caractères : évitez de comparer ou de filtrer des termes contenant trois caractères ou moins. Ces termes ne sont pas indexés et leur correspondance nécessite plus de ressources.

  • Projeter de manière sélective : facilitez la compréhension de vos résultats en projetant uniquement les colonnes dont vous avez besoin. La projection de colonnes spécifiques avant l’exécution d’opérations de jointure ou similaires permet également d’améliorer les performances.

Optimiser l’opérateur join

L’opérateur de jointure fusionne les lignes de deux tables en faisant correspondre les valeurs dans les colonnes spécifiées. Appliquez ces conseils pour optimiser les requêtes qui utilisent cet opérateur.

  • Table plus petite à gauche : l’opérateur join établit une correspondance entre les enregistrements de la table située à gauche de votre instruction de jointure et les enregistrements à droite. En ayant la table plus petite à gauche, moins d’enregistrements devront être mis en correspondance, ce qui accélère la requête.

    Dans le tableau ci-dessous, nous réduisons le tableau DeviceLogonEvents de gauche pour ne couvrir que trois appareils spécifiques avant de le joindre à par IdentityLogonEvents SID de compte.

    DeviceLogonEvents
    | where DeviceName in ("device-1.domain.com", "device-2.domain.com", "device-3.domain.com")
    | where ActionType == "LogonFailed"
    | join
        (IdentityLogonEvents
        | where ActionType == "LogonFailed"
        | where Protocol == "Kerberos")
    on AccountSid
    
  • Utiliser la saveur de jointure interne : la saveur de jointure par défaut ou la jointure innerunique déduplique les lignes de la table de gauche par la clé de jointure avant de retourner une ligne pour chaque correspondance à la table de droite. Si la table de gauche a plusieurs lignes avec la même valeur pour la join clé, ces lignes sont dédupliquées pour laisser une seule ligne aléatoire pour chaque valeur unique.

    Ce comportement par défaut peut exclure des informations importantes de la table de gauche qui peuvent fournir des informations utiles. Par exemple, la requête ci-dessous n’affiche qu’un seul e-mail contenant une pièce jointe particulière, même si cette même pièce jointe a été envoyée à l’aide de plusieurs messages électroniques :

    EmailAttachmentInfo
    | where Timestamp > ago(1h)
    | where Subject == "Document Attachment" and FileName == "Document.pdf"
    | join (DeviceFileEvents | where Timestamp > ago(1h)) on SHA256
    

    Pour résoudre cette limitation, nous appliquons la saveur de jointure interne en spécifiant kind=inner d’afficher toutes les lignes de la table de gauche avec les valeurs correspondantes à droite :

    EmailAttachmentInfo
    | where Timestamp > ago(1h)
    | where Subject == "Document Attachment" and FileName == "Document.pdf"
    | join kind=inner (DeviceFileEvents | where Timestamp > ago(1h)) on SHA256
    
  • Joindre des enregistrements à partir d’une fenêtre de temps : lors de l’examen des événements de sécurité, les analystes recherchent les événements connexes qui se produisent autour de la même période. L’application de la même approche lors de l’utilisation join améliore également les performances en réduisant le nombre d’enregistrements à case activée.

    La requête ci-dessous recherche les événements de connexion dans les 30 minutes suivant la réception d’un fichier malveillant :

    EmailEvents
    | where Timestamp > ago(7d)
    | where ThreatTypes has "Malware"
    | project EmailReceivedTime = Timestamp, Subject, SenderFromAddress, AccountName = tostring(split(RecipientEmailAddress, "@")[0])
    | join (
    DeviceLogonEvents
    | where Timestamp > ago(7d)
    | project LogonTime = Timestamp, AccountName, DeviceName
    ) on AccountName
    | where (LogonTime - EmailReceivedTime) between (0min .. 30min)
    
  • Appliquer des filtres de temps des deux côtés : même si vous n’examinez pas une fenêtre de temps spécifique, l’application de filtres de temps sur les tables de gauche et de droite peut réduire le nombre d’enregistrements à case activée et améliorer les join performances. La requête ci-dessous s’applique Timestamp > ago(1h) aux deux tables afin qu’elle joint uniquement les enregistrements de la dernière heure :

    EmailAttachmentInfo
    | where Timestamp > ago(1h)
    | where Subject == "Document Attachment" and FileName == "Document.pdf"
    | join kind=inner (DeviceFileEvents | where Timestamp > ago(1h)) on SHA256
    
  • Utiliser des indicateurs pour les performances : utilisez des indicateurs avec l’opérateur join pour indiquer au back-end de répartir la charge lors de l’exécution d’opérations gourmandes en ressources. En savoir plus sur les indicateurs de jointure

    Par exemple, l’indicateur de lecture aléatoire permet d’améliorer les performances des requêtes lors de la jointure de tables à l’aide d’une clé à cardinalité élevée, une clé avec de nombreuses valeurs uniques, comme dans AccountObjectId la requête ci-dessous :

    IdentityInfo
    | where JobTitle == "CONSULTANT"
    | join hint.shufflekey = AccountObjectId
    (IdentityDirectoryEvents
        | where Application == "Active Directory"
        | where ActionType == "Private data retrieval")
    on AccountObjectId
    

    L’indicateur de diffusion est utile lorsque la table de gauche est petite (jusqu’à 100 000 enregistrements) et que la table de droite est extrêmement volumineuse. Par exemple, la requête ci-dessous tente de joindre quelques e-mails qui ont des sujets spécifiques avec tous les messages contenant des liens dans le EmailUrlInfo tableau :

    EmailEvents
    | where Subject in ("Warning: Update your credentials now", "Action required: Update your credentials now")
    | join hint.strategy = broadcast EmailUrlInfo on NetworkMessageId
    

Optimiser l’opérateur summarize

L’opérateur summarize agrège le contenu d’une table. Appliquez ces conseils pour optimiser les requêtes qui utilisent cet opérateur.

  • Rechercher des valeurs distinctes : en général, utilisez summarize pour rechercher des valeurs distinctes qui peuvent être répétitives. Il peut être inutile de l’utiliser pour agréger des colonnes qui n’ont pas de valeurs répétitives.

    Bien qu’un seul e-mail puisse faire partie de plusieurs événements, l’exemple ci-dessous n’est pas une utilisation efficace de , car un ID de summarize message réseau pour un e-mail individuel est toujours fourni avec une adresse d’expéditeur unique.

    EmailEvents
    | where Timestamp > ago(1h)
    | summarize by NetworkMessageId, SenderFromAddress
    

    L’opérateur summarize peut être facilement remplacé par project, produisant potentiellement les mêmes résultats tout en consommant moins de ressources :

    EmailEvents
    | where Timestamp > ago(1h)
    | project NetworkMessageId, SenderFromAddress
    

    L’exemple suivant est une utilisation plus efficace de , summarize car il peut y avoir plusieurs instances distinctes d’une adresse d’expéditeur envoyant un e-mail à la même adresse de destinataire. Ces combinaisons sont moins distinctes et sont susceptibles d’avoir des doublons.

    EmailEvents
    | where Timestamp > ago(1h)
    | summarize by SenderFromAddress, RecipientEmailAddress
    
  • Lecture aléatoire de la requête : bien que summarize soit mieux utilisé dans les colonnes avec des valeurs répétitives, les mêmes colonnes peuvent également avoir une cardinalité élevée ou un grand nombre de valeurs uniques. À l’instar de l’opérateur join , vous pouvez également appliquer l’indicateur de lecture aléatoire avec summarize pour répartir la charge de traitement et améliorer potentiellement les performances lors de l’utilisation de colonnes avec une cardinalité élevée.

    La requête ci-dessous utilise summarize pour compter l’adresse e-mail de destinataire distincte, qui peut s’exécuter dans des centaines de milliers dans les grandes organisations. Pour améliorer les performances, il incorpore hint.shufflekey:

    EmailEvents
    | where Timestamp > ago(1h)
    | summarize hint.shufflekey = RecipientEmailAddress count() by Subject, RecipientEmailAddress
    

Scénarios de requête

Identifier des processus uniques avec des ID de processus

Les ID de processus (PID) sont recyclés dans Windows et réutilisés pour les nouveaux processus. Ils ne peuvent pas être utilisés comme identificateurs uniques pour des processus spécifiques.

Pour obtenir un identificateur unique pour un processus sur un ordinateur spécifique, utilisez l’ID de processus conjointement avec l’heure de création du processus. Lorsque vous rejoignez ou résumez des données autour des processus, incluez des colonnes pour l’identificateur de l’ordinateur (DeviceId ou DeviceName), l’ID de processus (ProcessId ou InitiatingProcessId) et l’heure de création du processus (ProcessCreationTime ou InitiatingProcessCreationTime).

L’exemple de requête suivant trouve les processus qui accèdent à plus de 10 adresses IP via le port 445 (SMB), qui peut rechercher des partages de fichiers.

Exemples de requête :

DeviceNetworkEvents
| where RemotePort == 445 and Timestamp > ago(12h) and InitiatingProcessId !in (0, 4)
| summarize RemoteIPCount=dcount(RemoteIP) by DeviceName, InitiatingProcessId, InitiatingProcessCreationTime, InitiatingProcessFileName
| where RemoteIPCount > 10

La requête se résume par InitiatingProcessId et InitiatingProcessCreationTime de sorte qu’elle examine un seul processus, sans mélanger plusieurs processus ayant le même ID de processus.

Lignes de commande de requête

Plusieurs méthodes s’offrent à vous pour créer une ligne de commande pour accomplir une tâche. Par exemple, un attaquant peut référencer un fichier image sans chemin d’accès, sans extension de fichier, à l’aide de variables d’environnement ou avec des guillemets. L’attaquant peut également modifier l’ordre des paramètres ou ajouter plusieurs guillemets et espaces.

Pour créer des requêtes plus durables autour des lignes de commande, appliquez les pratiques suivantes :

  • Identifiez les processus connus (tels que net.exe ou psexec.exe) en mettant en correspondance les champs de nom de fichier, au lieu de filtrer sur la ligne de commande elle-même.
  • Analyser les sections de ligne de commande à l’aide de la fonction parse_command_line()
  • Lors de l’interrogation des arguments de la ligne de commande, ne recherchez pas une correspondance exacte de plusieurs arguments non liés dans un certain ordre. Au lieu de cela, vous devez utiliser des expressions régulières ou utiliser plusieurs opérateurs de contenu séparés..
  • Utilisez des correspondances non respectées de casse. Par exemple, utilisez =~, in~et contains au lieu de ==, inet contains_cs.
  • Pour atténuer les techniques d’obfuscation de ligne de commande, envisagez de supprimer les guillemets, de remplacer les virgules par des espaces et de remplacer plusieurs espaces consécutifs par un seul espace. Il existe des techniques d’obfuscation plus complexes qui nécessitent d’autres approches, mais ces ajustements peuvent aider à résoudre les problèmes courants.

Les exemples suivants montrent différentes façons de construire une requête qui recherche le fichier net.exe pour arrêter le service de pare-feu « MpsSvc » :

// Non-durable query - do not use
DeviceProcessEvents
| where ProcessCommandLine == "net stop MpsSvc"
| limit 10

// Better query - filters on file name, does case-insensitive matches
DeviceProcessEvents
| where Timestamp > ago(7d) and FileName in~ ("net.exe", "net1.exe") and ProcessCommandLine contains "stop" and ProcessCommandLine contains "MpsSvc"

// Best query also ignores quotes
DeviceProcessEvents
| where Timestamp > ago(7d) and FileName in~ ("net.exe", "net1.exe")
| extend CanonicalCommandLine=replace("\"", "", ProcessCommandLine)
| where CanonicalCommandLine contains "stop" and CanonicalCommandLine contains "MpsSvc"

Ingérer des données à partir de sources externes

Pour incorporer des listes longues ou des tables volumineuses dans votre requête, utilisez l’opérateur externaldata pour ingérer des données à partir d’un URI spécifié. Vous pouvez obtenir des données à partir de fichiers dans TXT, CSV, JSON ou d’autres formats. L’exemple ci-dessous montre comment vous pouvez utiliser la liste complète des hachages SHA-256 fournis par MalwareBazaar (abuse.ch) pour case activée pièces jointes sur les e-mails :

let abuse_sha256 = (externaldata(sha256_hash: string)
[@"https://bazaar.abuse.ch/export/txt/sha256/recent/"]
with (format="txt"))
| where sha256_hash !startswith "#"
| project sha256_hash;
abuse_sha256
| join (EmailAttachmentInfo
| where Timestamp > ago(1d)
) on $left.sha256_hash == $right.SHA256
| project Timestamp,SenderFromAddress,RecipientEmailAddress,FileName,FileType,
SHA256,ThreatTypes,DetectionMethods

Analyser les chaînes

Il existe différentes fonctions que vous pouvez utiliser pour gérer efficacement les chaînes qui nécessitent une analyse ou une conversion.

String Fonction Exemple d'utilisation
Lignes de commande parse_command_line() Extrayez la commande et tous les arguments.
Paths parse_path() Extrayez les sections d’un chemin d’accès de fichier ou de dossier.
Numéros de version parse_version() Déconstruire un numéro de version avec jusqu’à quatre sections et jusqu’à huit caractères par section. Utilisez les données analysées pour comparer l’ancienneté des versions.
Adresses IPv4 parse_ipv4() Convertir une adresse IPv4 en entier long. Pour comparer des adresses IPv4 sans les convertir, utilisez ipv4_compare().
Adresses IPv6 parse_ipv6() Convertissez une adresse IPv4 ou IPv6 en notation IPv6 canonique. Pour comparer les adresses IPv6, utilisez ipv6_compare().

Pour en savoir plus sur toutes les fonctions d’analyse prises en charge, consultez Fonctions de chaîne Kusto.

Remarque

Certaines tables de cet article peuvent ne pas être disponibles dans Microsoft Defender pour point de terminaison. Activez Microsoft Defender XDR pour rechercher les menaces à l’aide de sources de données supplémentaires. Vous pouvez déplacer vos flux de travail de chasse avancés de Microsoft Defender pour point de terminaison vers Microsoft Defender XDR en suivant les étapes décrites dans Migrer des requêtes de chasse avancées à partir de Microsoft Defender pour point de terminaison.

Conseil

Voulez-vous en savoir plus ? Collaborez avec la communauté Sécurité Microsoft dans notre communauté technique : Communauté technique Microsoft Defender XDR.