Procedure consigliate per query in Ricerca avanzata

Si applica a:

  • Microsoft Defender XDR

Applicare queste raccomandazioni per ottenere risultati più velocemente ed evitare timeout durante l'esecuzione di query complesse. Per altre informazioni su come migliorare le prestazioni della query, vedere procedure consigliate per le query di Esplora dati.

Informazioni sulle quote delle risorse della CPU

A seconda delle dimensioni, ogni tenant ha accesso a una quantità impostata di risorse CPU allocate per l'esecuzione di query di ricerca avanzate. Per informazioni dettagliate sui vari parametri di utilizzo, vedere Le quote di ricerca avanzate e i parametri di utilizzo.

Dopo aver eseguito la query, è possibile visualizzare il tempo di esecuzione e l'utilizzo delle risorse (basso, medio, alto). Alto indica che l'esecuzione della query ha richiesto più risorse e potrebbe essere migliorata per restituire risultati in modo più efficiente.

Dettagli della query nella scheda **Risultati** nel portale di Microsoft Defender

I clienti che eseguono regolarmente più query devono tenere traccia dell'utilizzo e applicare le indicazioni sull'ottimizzazione in questo articolo per ridurre al minimo le interruzioni risultanti dal superamento delle quote o dei parametri di utilizzo.

Vedere Ottimizzazione delle query KQL per visualizzare alcuni dei modi più comuni per migliorare le query.

Suggerimenti generali per l'ottimizzazione

  • Dimensiona le nuove query: se si sospetta che una query restituirà un set di risultati di grandi dimensioni, valutarla prima usando l'operatore count. Usare limit o il relativo sinonimo take per evitare set di risultati di grandi dimensioni.

  • Applica filtri in anticipo: applica filtri temporali e altri filtri per ridurre il set di dati, in particolare prima di usare funzioni di trasformazione e analisi, ad esempio substring(), replace(), trim(), toupper() o parse_json(). Nell'esempio seguente viene usata la funzione di analisi extractjson() dopo che gli operatori di filtro hanno ridotto il numero di record.

    DeviceEvents
    | where Timestamp > ago(1d)
    | where ActionType == "UsbDriveMount"
    | where DeviceName == "user-desktop.domain.com"
    | extend DriveLetter = extractjson("$.DriveLetter", AdditionalFields)
    
  • Contiene battute: per evitare la ricerca di sottostringhe all'interno di containsparole inutilmente, usare l'operatore has anziché . Informazioni sugli operatori stringa

  • Cercare colonne specifiche: cercare in una colonna specifica anziché eseguire ricerche full-text in tutte le colonne. Non usare * per controllare tutte le colonne.

  • Distinzione tra maiuscole e minuscole per velocità: le ricerche con distinzione tra maiuscole e minuscole sono più specifiche e in genere più efficienti. I nomi degli operatori stringa con distinzione tra maiuscole e minuscole, ad has_cs esempio e contains_cs, terminano in genere con _cs. È anche possibile usare l'operatore == equals con distinzione tra maiuscole e minuscole anziché =~.

  • Analizza, non estrarre: quando possibile, usa l'operatore di analisi o una funzione di analisi come parse_json(). Evitare l'operatore matches regex stringa o la funzione extract(), che usano entrambe l'espressione regolare. Riservare l'uso di espressioni regolari per scenari più complessi. Altre informazioni sull'analisi delle funzioni

  • Filtrare le tabelle e non le espressioni: non filtrare in base a una colonna calcolata se è possibile filtrare in base a una colonna di tabella.

  • Nessun termine di tre caratteri: evitare di confrontare o filtrare usando termini con tre caratteri o meno. Questi termini non sono indicizzati e la loro corrispondenza richiederà più risorse.

  • Progetto in modo selettivo: consente di comprendere più facilmente i risultati proiettando solo le colonne necessarie. Anche la proiezione di colonne specifiche prima dell'esecuzione di join o operazioni simili consente di migliorare le prestazioni.

Ottimizzare l'operatore join

L'operatore join unisce le righe da due tabelle in base ai valori corrispondenti nelle colonne specificate. Applicare questi suggerimenti per ottimizzare le query che usano questo operatore.

  • Tabella più piccola a sinistra: l'operatore join corrisponde ai record nella tabella sul lato sinistro dell'istruzione join ai record a destra. Con la tabella più piccola a sinistra, sarà necessario trovare una corrispondenza per un numero inferiore di record, accelerando così la query.

    Nella tabella seguente si riduce la tabella DeviceLogonEvents a sinistra per coprire solo tre dispositivi specifici prima di aggiungerla ai IdentityLogonEvents SID dell'account.

    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
    
  • Usare il tipo inner join: il tipo di join predefinito o le righe innerunique-join deduplicate nella tabella a sinistra tramite la chiave di join prima di restituire una riga per ogni corrispondenza alla tabella a destra. Se nella tabella sinistra sono presenti più righe con lo stesso valore per la join chiave, tali righe verranno deduplicate in modo da lasciare una singola riga casuale per ogni valore univoco.

    Questo comportamento predefinito può escludere informazioni importanti dalla tabella a sinistra che possono fornire informazioni utili. Ad esempio, la query seguente mostrerà solo un messaggio di posta elettronica contenente un allegato specifico, anche se lo stesso allegato è stato inviato usando più messaggi di posta elettronica:

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

    Per risolvere questa limitazione, viene applicato il tipo inner join specificando kind=inner di visualizzare tutte le righe della tabella a sinistra con valori corrispondenti a destra:

    EmailAttachmentInfo
    | where Timestamp > ago(1h)
    | where Subject == "Document Attachment" and FileName == "Document.pdf"
    | join kind=inner (DeviceFileEvents | where Timestamp > ago(1h)) on SHA256
    
  • Aggiungere record da un intervallo di tempo: durante l'analisi degli eventi di sicurezza, gli analisti cercano gli eventi correlati che si verificano nello stesso periodo di tempo. L'applicazione dello stesso approccio quando si usa join offre anche vantaggi per le prestazioni riducendo il numero di record da controllare.

    La query seguente verifica la presenza di eventi di accesso entro 30 minuti dalla ricezione di un file dannoso:

    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)
    
  • Applicare filtri di ora su entrambi i lati: anche se non si sta analizzando un intervallo di tempo specifico, l'applicazione di filtri temporali nelle tabelle sinistra e destra può ridurre il numero di record per controllare e migliorare join le prestazioni. La query seguente si applica Timestamp > ago(1h) a entrambe le tabelle in modo che unisca solo i record dell'ora precedente:

    EmailAttachmentInfo
    | where Timestamp > ago(1h)
    | where Subject == "Document Attachment" and FileName == "Document.pdf"
    | join kind=inner (DeviceFileEvents | where Timestamp > ago(1h)) on SHA256
    
  • Usare hint per le prestazioni: usare gli hint con l'operatore join per indicare al back-end di distribuire il carico durante l'esecuzione di operazioni a elevato utilizzo di risorse. Altre informazioni sugli hint per l'aggiunta

    Ad esempio, l'hint shuffle consente di migliorare le prestazioni delle query quando si uniscono tabelle usando una chiave con cardinalità elevata, una chiave con molti valori univoci, ad AccountObjectId esempio nella query seguente:

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

    L'hint broadcast è utile quando la tabella a sinistra è piccola (fino a 100.000 record) e la tabella destra è estremamente grande. Ad esempio, la query seguente sta tentando di unire alcuni messaggi di posta elettronica con argomenti specifici con tutti i messaggi contenenti collegamenti nella EmailUrlInfo tabella:

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

Ottimizzare l'operatore summarize

L'operatore summarize aggrega il contenuto di una tabella. Applicare questi suggerimenti per ottimizzare le query che usano questo operatore.

  • Trova valori distinti: in generale, usare summarize per trovare valori distinti che possono essere ripetitivi. Può non essere necessario usarlo per aggregare colonne che non hanno valori ripetitivi.

    Anche se un singolo messaggio di posta elettronica può far parte di più eventi, l'esempio seguente non è un uso efficiente di perché un ID messaggio di summarize rete per un singolo messaggio di posta elettronica viene sempre fornito con un indirizzo mittente univoco.

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

    L'operatore summarize può essere facilmente sostituito con project, generando potenzialmente gli stessi risultati e consumando meno risorse:

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

    L'esempio seguente è un uso più efficiente di summarize perché possono essere presenti più istanze distinte di un indirizzo mittente che invia un messaggio di posta elettronica allo stesso indirizzo del destinatario. Tali combinazioni sono meno distinte e probabilmente hanno duplicati.

    EmailEvents
    | where Timestamp > ago(1h)
    | summarize by SenderFromAddress, RecipientEmailAddress
    
  • Riordinare la query: sebbene summarize sia preferibile usare in colonne con valori ripetitivi, le stesse colonne possono anche avere cardinalità elevata o un numero elevato di valori univoci. Come l'operatore join , è anche possibile applicare l'hint shuffle con summarize per distribuire il carico di elaborazione e potenzialmente migliorare le prestazioni quando si opera su colonne con cardinalità elevata.

    La query seguente usa summarize per contare l'indirizzo di posta elettronica del destinatario distinto, che può essere eseguito in centinaia di migliaia in organizzazioni di grandi dimensioni. Per migliorare le prestazioni, incorpora hint.shufflekey:

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

Scenari di query

Identificare processi univoci con ID processo

Gli ID processo (PID) sono riciclati in Windows e riutilizzati per i nuovi processi. Di per sé, non possono servire come identificatori univoci per processi specifici.

Per ottenere un identificatore univoco per un computer specifico, usare l'ID processo insieme all'ora di creazione del processo. Quando si uniscono o si riepilogano i dati relativi a processi, includere le colonne per l'identificatore del computer (DeviceId o DeviceName), l'ID processo (ProcessId o InitiatingProcessId) e l'ora di creazione del processo (ProcessCreationTime o InitiatingProcessCreationTime)

La seguente query di esempio trova i processi che accedono a più di 10 indirizzi IP dalla porta 445 (SMB), possibilmente analizzando le condivisioni file.

Query di esempio:

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 query riepiloga sia InitiatingProcessId che InitiatingProcessCreationTime in modo da visualizzare un singolo processo, senza combinare più processi con lo stesso ID processo.

Righe di comando di query

Esistono diversi modi per creare una riga di comando per eseguire un'attività. Ad esempio, un utente malintenzionato potrebbe fare riferimento a un file di immagine senza percorso, senza estensione di file, usando variabili di ambiente o virgolette. L'utente malintenzionato potrebbe anche modificare l'ordine dei parametri o aggiungere più virgolette e spazi.

Per creare query più durevoli sulle righe di comando, applicare le procedure seguenti:

  • Identificare i processi noti , ad esempio net.exe o psexec.exe, tramite la corrispondenza nei campi del nome file, anziché applicare filtri alla riga di comando stessa.
  • Analizzare le sezioni della riga di comando usando la funzione parse_command_line()
  • Quando si eseguono query per gli argomenti della riga di comando, non cercare una corrispondenza esatta su più argomenti non correlati in un determinato ordine. Usare invece espressioni regolari o utilizzare più operatori distinti.
  • Usare corrispondenza maiuscole/minuscole. Ad esempio, usare =~, in~e contains anziché ==, ine contains_cs.
  • Per attenuare le tecniche di offuscamento della riga di comando, prendere in considerazione la rimozione delle virgolette, la sostituzione delle virgole con spazi e la sostituzione di più spazi consecutivi con un singolo spazio. Esistono tecniche di offuscamento più complesse che richiedono altri approcci, ma queste modifiche possono aiutare ad affrontare quelle comuni.

Gli esempi seguenti illustrano diversi modi per costruire una query che cerca il file net.exe per arrestare il servizio firewall "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"

Inserire dati da origini esterne

Per incorporare elenchi lunghi o tabelle di grandi dimensioni nella query, usare l'operatore externaldata per inserire dati da un URI specificato. È possibile ottenere dati da file in formato TXT, CSV, JSON o altri formati. L'esempio seguente mostra come è possibile utilizzare l'elenco completo di hash SHA-256 malware forniti da MalwareBazaar (abuse.ch) per controllare gli allegati nei messaggi di posta elettronica:

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

Analizzare le stringhe

Esistono varie funzioni che è possibile usare per gestire in modo efficiente le stringhe che richiedono l'analisi o la conversione.

Stringa Funzione Esempio di utilizzo
Righe di comando parse_command_line() Estrarre il comando e tutti gli argomenti.
Percorsi parse_path() Estrarre le sezioni di un percorso di file o cartella.
Numeri di versione parse_version() Decostruire un numero di versione con un massimo di quattro sezioni e fino a otto caratteri per sezione. Usare i dati analizzati per confrontare l'età della versione.
Indirizzi IPv4 parse_ipv4() Convertire un indirizzo IPv4 in un numero intero lungo. Per confrontare gli indirizzi IPv4 senza convertirli, usare ipv4_compare().
Indirizzi IPv6 parse_ipv6() Convertire un indirizzo IPv4 o IPv6 nella notazione IPv6 canonica. Per confrontare gli indirizzi IPv6, usare ipv6_compare().

Per informazioni su tutte le funzioni di analisi supportate, leggere le informazioni sulle funzioni stringa Kusto.

Nota

Alcune tabelle di questo articolo potrebbero non essere disponibili in Microsoft Defender per endpoint. Attivare Microsoft Defender XDR per cercare le minacce usando più origini dati. È possibile spostare i flussi di lavoro di ricerca avanzati da Microsoft Defender per endpoint a Microsoft Defender XDR seguendo la procedura descritta in Eseguire la migrazione di query di ricerca avanzate da Microsoft Defender per endpoint.

Consiglio

Per saperne di più, Visitare la community di Microsoft Security nella Tech Community: Tech Community di Microsoft Defender XDR.