Geavanceerde best practices voor opsporingsquery's

Van toepassing op:

  • Microsoft Defender XDR

Pas deze aanbevelingen toe om sneller resultaten te krijgen en time-outs te voorkomen tijdens het uitvoeren van complexe query's. Lees Best practices voor Kusto-query's voor meer hulp bij het verbeteren van queryprestaties.

Inzicht in CPU-resourcequota

Afhankelijk van de grootte heeft elke tenant toegang tot een bepaalde hoeveelheid CPU-resources die zijn toegewezen voor het uitvoeren van geavanceerde opsporingsquery's. Lees geavanceerde opsporingsquota en gebruiksparameters voor gedetailleerde informatie over verschillende gebruiksparameters.

Nadat u de query hebt uitgevoerd, ziet u de uitvoeringstijd en het resourcegebruik (Laag, Gemiddeld, Hoog). Hoog geeft aan dat de query meer resources nodig heeft om uit te voeren en kan worden verbeterd om de resultaten efficiënter te retourneren.

De querydetails op het tabblad **Resultaten** in de Microsoft Defender-portal

Klanten die regelmatig meerdere query's uitvoeren, moeten het verbruik bijhouden en de optimalisatierichtlijnen in dit artikel toepassen om onderbrekingen als gevolg van het overschrijden van quota of gebruiksparameters te minimaliseren.

Bekijk KQL-query's optimaliseren om enkele van de meest voorkomende manieren te zien om uw query's te verbeteren.

Algemene optimalisatietips

  • Grootte van nieuwe query's: als u vermoedt dat een query een grote resultatenset retourneert, beoordeelt u deze eerst met behulp van de operator count. Gebruik limiet of het synoniem take om grote resultatensets te voorkomen.

  • Filters vroeg toepassen: pas tijdfilters en andere filters toe om de gegevensset te verminderen, met name voordat u transformatie- en parseringsfuncties gebruikt, zoals substring(), replace(), trim(), toupper() of parse_json(). In het onderstaande voorbeeld wordt de parseringsfunctie extractjson() gebruikt nadat filteroperatoren het aantal records hebben verminderd.

    DeviceEvents
    | where Timestamp > ago(1d)
    | where ActionType == "UsbDriveMount"
    | where DeviceName == "user-desktop.domain.com"
    | extend DriveLetter = extractjson("$.DriveLetter", AdditionalFields)
    
  • Bevat beats: als u wilt voorkomen dat subtekenreeksen in woorden onnodig worden gezocht, gebruikt u de has operator in plaats van contains. Meer informatie over tekenreeksoperatoren

  • Zoeken in specifieke kolommen: zoek in een specifieke kolom in plaats van zoekopdrachten in volledige tekst uit te voeren in alle kolommen. Gebruik niet * om alle kolommen te controleren.

  • Hoofdlettergevoelig voor snelheid: hoofdlettergevoelige zoekopdrachten zijn specifieker en presteren doorgaans beter. Namen van hoofdlettergevoelige tekenreeksoperatoren, zoals has_cs en contains_cs, eindigen meestal op _cs. U kunt ook de operator == hoofdlettergevoelige equals gebruiken in plaats van =~.

  • Parseren, niet extraheren. Gebruik indien mogelijk de parseringsoperator of een parseringsfunctie zoals parse_json(). Vermijd de matches regex tekenreeksoperator of de functie extract(), die beide een reguliere expressie gebruiken. Reserveer het gebruik van de reguliere expressie voor complexere scenario's. Meer informatie over parseerfuncties

  • Filtertabellen, geen expressies: filter niet op een berekende kolom als u kunt filteren op een tabelkolom.

  • Geen termen van drie tekens: vermijd het vergelijken of filteren van termen met drie tekens of minder. Deze termen worden niet geïndexeerd en er zijn meer resources voor nodig.

  • Selectief projecteren: maak uw resultaten gemakkelijker te begrijpen door alleen de kolommen te projecteren die u nodig hebt. Het projecteren van specifieke kolommen voorafgaand aan het uitvoeren van join - of vergelijkbare bewerkingen helpt ook bij het verbeteren van de prestaties.

join De operator optimaliseren

De join-operator voegt rijen uit twee tabellen samen door waarden in opgegeven kolommen te vergelijken. Pas deze tips toe om query's te optimaliseren die gebruikmaken van deze operator.

  • Kleinere tabel aan de linkerkant: de join operator koppelt records in de tabel aan de linkerkant van de join-instructie aan records aan de rechterkant. Door de kleinere tabel aan de linkerkant te hebben, hoeven er minder records te worden vergeleken, waardoor de query wordt versneld.

    In de onderstaande tabel beperken we de linkertabel DeviceLogonEvents tot slechts drie specifieke apparaten voordat ze worden samengevoegd met IdentityLogonEvents per account-SID's.

    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
    
  • Gebruik de inner-join-smaak: de standaard join-smaak of de innerunique-join ontdubbelt rijen in de linkertabel met de joinsleutel voordat een rij wordt geretourneerd voor elke overeenkomst naar de rechtertabel. Als de linkertabel meerdere rijen bevat met dezelfde waarde voor de join sleutel, worden deze rijen ontdubbeld zodat er één willekeurige rij overblijft voor elke unieke waarde.

    Dit standaardgedrag kan belangrijke informatie uit de linkertabel weglaten die nuttig inzicht kan bieden. In de onderstaande query wordt bijvoorbeeld slechts één e-mailbericht met een bepaalde bijlage weergegeven, zelfs als dezelfde bijlage is verzonden met meerdere e-mailberichten:

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

    Om deze beperking aan te pakken, passen we de inner-join-smaak toe door op te geven dat alle rijen in de linkertabel worden weergegeven met overeenkomende kind=inner waarden aan de rechterkant:

    EmailAttachmentInfo
    | where Timestamp > ago(1h)
    | where Subject == "Document Attachment" and FileName == "Document.pdf"
    | join kind=inner (DeviceFileEvents | where Timestamp > ago(1h)) on SHA256
    
  • Records toevoegen vanuit een tijdvenster: bij het onderzoeken van beveiligingsevenementen zoeken analisten naar gerelateerde gebeurtenissen die zich rond dezelfde periode voordoen. Het toepassen van dezelfde benadering bij het gebruik join van ook voordelen voor de prestaties door het aantal te controleren records te verminderen.

    Met de onderstaande query wordt gecontroleerd op aanmeldingsevenementen binnen 30 minuten na ontvangst van een schadelijk bestand:

    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)
    
  • Tijdfilters aan beide zijden toepassen: zelfs als u een bepaald tijdvenster niet onderzoekt, kan het toepassen van tijdfilters op zowel de linker- als rechtertabel het aantal records verminderen om de prestaties te controleren en te verbeteren join . De onderstaande query is van toepassing op Timestamp > ago(1h) beide tabellen, zodat alleen records van het afgelopen uur worden samengevoegd:

    EmailAttachmentInfo
    | where Timestamp > ago(1h)
    | where Subject == "Document Attachment" and FileName == "Document.pdf"
    | join kind=inner (DeviceFileEvents | where Timestamp > ago(1h)) on SHA256
    
  • Hints gebruiken voor prestaties: gebruik hints met de join operator om de back-end te instrueren om de belasting te verdelen bij het uitvoeren van resource-intensieve bewerkingen. Meer informatie over hints voor deelname

    De hint voor willekeurige volgorde helpt bijvoorbeeld bij het verbeteren van queryprestaties bij het samenvoegen van tabellen met behulp van een sleutel met een hoge kardinaliteit, een sleutel met veel unieke waarden, zoals de AccountObjectId in de onderstaande query:

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

    De broadcasthint helpt wanneer de linkertabel klein is (maximaal 100.000 records) en de rechtertabel extreem groot is. De onderstaande query probeert bijvoorbeeld een paar e-mailberichten met specifieke onderwerpen samen te voegen met alle berichten die koppelingen in de EmailUrlInfo tabel bevatten:

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

summarize De operator optimaliseren

De samenvattingsoperator voegt de inhoud van een tabel samen. Pas deze tips toe om query's te optimaliseren die gebruikmaken van deze operator.

  • Unieke waarden zoeken: gebruik in het algemeen summarize om unieke waarden te vinden die herhaaldelijk kunnen worden gebruikt. Het kan onnodig zijn om deze te gebruiken om kolommen te aggregeren die geen terugkerende waarden hebben.

    Hoewel één e-mailbericht deel kan uitmaken van meerdere gebeurtenissen, is het onderstaande voorbeeld geen efficiënt gebruik van summarize omdat een netwerkbericht-id voor een afzonderlijke e-mail altijd een uniek afzenderadres heeft.

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

    De summarize operator kan eenvoudig worden vervangen projectdoor , wat mogelijk dezelfde resultaten oplevert en minder resources verbruikt:

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

    Het volgende voorbeeld is een efficiënter gebruik van summarize , omdat er meerdere afzonderlijke exemplaren van een afzenderadres kunnen zijn dat e-mail naar hetzelfde adres van de geadresseerde verzendt. Dergelijke combinaties zijn minder verschillend en hebben waarschijnlijk dubbele waarden.

    EmailEvents
    | where Timestamp > ago(1h)
    | summarize by SenderFromAddress, RecipientEmailAddress
    
  • De query wijzigen: hoewel summarize het het beste wordt gebruikt in kolommen met terugkerende waarden, kunnen dezelfde kolommen ook een hoge kardinaliteit of grote aantallen unieke waarden hebben. Net als de join operator kunt u de shuffle-hint ook toepassen om summarize de verwerkingsbelasting te verdelen en mogelijk de prestaties te verbeteren wanneer u werkt op kolommen met een hoge kardinaliteit.

    In de onderstaande query wordt gebruikgemaakt summarize van het tellen van het afzonderlijke e-mailadres van de geadresseerde, dat kan worden uitgevoerd in de honderdduizenden in grote organisaties. Om de prestaties te verbeteren, bevat hint.shufflekeyhet :

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

Queryscenario's

Unieke processen identificeren met proces-id's

Proces-id's (PID's) worden gerecycled in Windows en hergebruikt voor nieuwe processen. Op zichzelf kunnen ze niet fungeren als unieke id's voor specifieke processen.

Als u een unieke id voor een proces op een specifieke computer wilt ophalen, gebruikt u de proces-id samen met de aanmaaktijd van het proces. Wanneer u gegevens rond processen samenvoegt of samenvat, neemt u kolommen op voor de machine-id (of DeviceIdDeviceName), de proces-id (ProcessId of InitiatingProcessId), en de aanmaaktijd van het proces (ProcessCreationTime of InitiatingProcessCreationTime)

Met de volgende voorbeeldquery worden processen gevonden die toegang hebben tot meer dan 10 IP-adressen via poort 445 (SMB), mogelijk scannen op bestandsshares.

Voorbeeldquery:

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

De query is een samenvatting van beide InitiatingProcessId en InitiatingProcessCreationTime zodat er naar één proces wordt gekeken, zonder meerdere processen met dezelfde proces-id te mengen.

Opdrachtregels opvragen

Er zijn verschillende manieren om een opdrachtregel te maken om een taak uit te voeren. Een aanvaller kan bijvoorbeeld verwijzen naar een afbeeldingsbestand zonder pad, zonder bestandsextensie, met behulp van omgevingsvariabelen of met aanhalingstekens. De aanvaller kan ook de volgorde van parameters wijzigen of meerdere aanhalingstekens en spaties toevoegen.

Als u duurzamere query's wilt maken rond opdrachtregels, past u de volgende procedures toe:

  • Identificeer de bekende processen (zoals net.exe of psexec.exe) door te vergelijken op de bestandsnaamvelden, in plaats van te filteren op de opdrachtregel zelf.
  • Opdrachtregelsecties parseren met de functie parse_command_line()
  • Wanneer u een query uitvoert op opdrachtregelargumenten, moet u niet zoeken naar een exacte overeenkomst voor meerdere niet-gerelateerde argumenten in een bepaalde volgorde. Gebruik in plaats daarvan reguliere expressies of gebruik meerdere afzonderlijke contains-operators.
  • Gebruik hoofdlettergevoelige overeenkomsten. Gebruik =~bijvoorbeeld , in~en contains in plaats van ==, inen contains_cs.
  • Als u technieken voor het verhullen van opdrachtregels wilt beperken, kunt u aanhalingstekens verwijderen, komma's vervangen door spaties en meerdere opeenvolgende spaties vervangen door één spatie. Er zijn complexere verduisteringstechnieken waarvoor andere benaderingen nodig zijn, maar deze aanpassingen kunnen helpen bij het aanpakken van veelvoorkomende methoden.

In de volgende voorbeelden ziet u verschillende manieren om een query te maken die zoekt naar het bestand net.exe om de firewallservice 'MpsSvc' te stoppen:

// 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"

Gegevens uit externe bronnen opnemen

Als u lange lijsten of grote tabellen in uw query wilt opnemen, gebruikt u de operator externaldata om gegevens op te nemen uit een opgegeven URI. U kunt gegevens ophalen uit bestanden in TXT-, CSV-, JSON - of andere indelingen. In het onderstaande voorbeeld ziet u hoe u de uitgebreide lijst met sha-256-hashes van MalwareBazaar (abuse.ch) kunt gebruiken om bijlagen in e-mailberichten te controleren:

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

Tekenreeksen parseren

Er zijn verschillende functies die u kunt gebruiken om tekenreeksen die moeten worden geparseerd of geconverteerd efficiënt te verwerken.

Tekenreeks Functie Gebruiksvoorbeeld
Opdrachtregels parse_command_line() Pak de opdracht en alle argumenten uit.
Paden parse_path() Pak de secties van een bestands- of mappad uit.
Versienummers parse_version() Deconstrueer een versienummer met maximaal vier secties en maximaal acht tekens per sectie. Gebruik de geparseerde gegevens om de leeftijd van de versie te vergelijken.
IPv4-adressen parse_ipv4() Converteer een IPv4-adres naar een lang geheel getal. Als u IPv4-adressen wilt vergelijken zonder ze te converteren, gebruikt u ipv4_compare().
IPv6-adressen parse_ipv6() Converteer een IPv4- of IPv6-adres naar de canonieke IPv6-notatie. Als u IPv6-adressen wilt vergelijken, gebruikt u ipv6_compare().

Lees meer over Kusto-tekenreeksfuncties voor meer informatie over alle ondersteunde parseringsfuncties.

Opmerking

Sommige tabellen in dit artikel zijn mogelijk niet beschikbaar in Microsoft Defender voor Eindpunt. Schakel Microsoft Defender XDR in om bedreigingen op te sporen met behulp van meer gegevensbronnen. U kunt uw geavanceerde opsporingswerkstromen van Microsoft Defender voor Eindpunt naar Microsoft Defender XDR verplaatsen door de stappen in Geavanceerde opsporingsquery's migreren van Microsoft Defender voor Eindpunt te volgen.

Tip

Wil je meer weten? Engage met de Microsoft Security-community in onze Tech Community: Microsoft Defender XDR Tech Community.