Condividi tramite


Linguaggio di query Kusto in Microsoft Sentinel

Linguaggio di query Kusto è il linguaggio usato per usare e modificare i dati in Microsoft Sentinel. I log inseriti nell'area di lavoro non valgono molto se non è possibile analizzarli e ottenere le informazioni importanti nascoste in tutti i dati. Il Linguaggio di query Kusto non ha solo la potenza e la flessibilità per ottenere tali informazioni, ma la semplicità per iniziare rapidamente. Se si ha uno sfondo nello scripting o nell'uso dei database, gran parte del contenuto di questo articolo dovrebbe essere familiare. In caso contrario, non occorre preoccuparsi, poiché la natura intuitiva del linguaggio consente di iniziare rapidamente a scrivere query personalizzate e a promuovere il valore per l'organizzazione.

Questo articolo presenta le nozioni di base di Linguaggio di query Kusto, che illustra alcune delle funzioni e degli operatori più usati, che devono indirizzare il 75 all'80% delle query che gli utenti scrivono giorno dopo giorno. Quando è necessaria una maggiore profondità o per eseguire query più avanzate, è possibile sfruttare la nuova cartella di lavoro avanzata KQL per Microsoft Sentinel (vedere questo post di blog introduttivo). Vedere anche la documentazione ufficiale Linguaggio di query Kusto e i vari corsi online (ad esempio Pluralsight).

Background - Perché il Linguaggio di query Kusto?

Microsoft Sentinel si basa sul servizio Monitoraggio di Azure e usa le aree di lavoro Log Analytics di Monitoraggio di Azure per archiviare tutti i dati. Questi dati includono uno dei seguenti:

  • dati inseriti da origini esterne in tabelle predefinite usando i connettori dati di Microsoft Sentinel.
  • dati inseriti da origini esterne in tabelle personalizzate definite dall'utente, usando connettori dati creati in modo personalizzato e alcuni tipi di connettori predefiniti.
  • dati creati da Microsoft Sentinel stesso, risultanti dalle analisi create ed eseguite, ad esempio avvisi, eventi imprevisti e informazioni correlate all'UEBA.
  • dati caricati in Microsoft Sentinel per facilitare il rilevamento e l'analisi, ad esempio feed di intelligence sulle minacce e watchlist.

Il Linguaggio di query Kusto è stato sviluppato Esplora dati di Azure ed è quindi ottimizzato per la ricerca in archivi Big Data in un ambiente cloud. Ispirato dal famoso esploratore sottomarino Jacques Cousteau (e pronunciato di conseguenza "koo-STOH"), è progettato per aiutarti a immergerti in profondità nei tuoi oceani di dati ed esplorare i loro tesori nascosti.

Linguaggio di query Kusto viene usato anche in Monitoraggio di Azure e supporta funzionalità aggiuntive di Monitoraggio di Azure che consentono di recuperare, visualizzare, analizzare e analizzare i dati negli archivi dati di Log Analytics. In Microsoft Sentinel si usano strumenti basati sul Linguaggio di query Kusto ogni volta che si visualizzano e si analizzano i dati e si esegue la ricerca di minacce, sia nelle regole e nelle cartelle di lavoro esistenti che nella compilazione personalizzata.

Poiché Linguaggio di query Kusto fa parte di quasi tutto ciò che si fa in Microsoft Sentinel, una chiara comprensione del funzionamento consente di ottenere un maggior numero di informazioni dal sistema SIEM.

Che cos'è una query?

Una query del Linguaggio di query Kusto è una richiesta di sola lettura per elaborare i dati e restituire i risultati. Non scrive dati. Le query operano su dati organizzati in una gerarchia di database, tabelle e colonne, simili a SQL.

Le richieste vengono dichiarate in linguaggio normale e usano un modello di flusso di dati progettato per semplificare la lettura, la scrittura e l'automazione della sintassi.

Le query del Linguaggio di query Kusto sono costituite da istruzioni separate da punti e virgola. Esistono molti tipi di istruzioni, ma solo due tipi ampiamente usati:

  • Le istruzioni di espressione tabulare sono ciò che in genere si intende quando si parla di query, ovvero il corpo effettivo della query. La cosa importante da sapere sulle istruzioni di espressione tabulare è che accettano un input tabulare (una tabella o un'altra espressione tabulare) e producono un output tabulare. Almeno uno di questi è obbligatorio. La maggior parte del resto di questo articolo illustra questo tipo di istruzione.

  • Le istruzioni let consentono di creare e definire variabili e costanti al di fuori del corpo della query, per semplificare la leggibilità e la versatilità. Questi sono facoltativi e dipendono dalle esigenze specifiche. Questo tipo di dichiarazione viene affrontato alla fine dell'articolo.

Ambiente demo

È possibile praticare le istruzioni del linguaggio di query Kusto, incluse quelle contenute in questo articolo, in un ambiente demo di Log Analytics nel portale di Azure. Non è previsto alcun addebito per l'uso di questo ambiente di pratica, ma è necessario un account Azure per accedervi.

Esplorare l'ambiente demo. Come Log Analytics nell'ambiente di produzione, può essere usato in molti modi:

  • Scegliere una tabella in cui compilare una query. Nella scheda Tabelle predefinita (visualizzata nel rettangolo rosso in alto a sinistra), selezionare una tabella dall'elenco di tabelle raggruppate in base agli argomenti (mostrati in basso a sinistra). Espandere gli argomenti per visualizzare le singole tabelle ed espandere ulteriormente ogni tabella per visualizzare tutti i relativi campi (colonne). Facendo doppio clic su una tabella o un nome di campo, viene posizionato al punto del cursore nella finestra di query. Digitare il resto della query seguendo il nome della tabella, come indicato di seguito.

  • Trovare una query esistente da studiare o modificare. Selezionare la scheda Query (mostrata nel rettangolo rosso in alto a sinistra) per visualizzare un elenco di query disponibili nella casella predefinita. In alternativa, selezionare Query dalla barra dei pulsanti in alto a destra. È possibile esplorare le query fornite con Microsoft Sentinel in modo predefinito. Facendo doppio clic su una query, l'intera query viene inserita nella finestra della query al punto del cursore.

    Mostra l'ambiente demo di Log Analytics.

Come in questo ambiente demo, è possibile eseguire query e filtrare i dati nella pagina Log di Microsoft Sentinel. È possibile selezionare una tabella ed eseguire il drill-down per visualizzare le colonne. È possibile modificare le colonne predefinite visualizzate usando Scelta Risorse Colonna ed è possibile impostare l'intervallo di tempo predefinito per le query. Se l'intervallo di tempo è definito in modo esplicito nella query, il filtro temporale non è disponibile (disattivato). Per ulteriori informazioni, vedere,

Se si esegue l'onboarding nella piattaforma unificata per le operazioni di sicurezza di Microsoft, è anche possibile eseguire query e filtrare i dati nella pagina Ricerca avanzata di Microsoft Defender. Per altre informazioni, vedere Ricerca avanzata con i dati di Microsoft Sentinel nel portale di Microsoft Defender.

Struttura della query

Un buon punto di partenza per iniziare a imparare il Linguaggio di query Kusto consiste nel comprendere la struttura complessiva delle query. La prima cosa che si nota quando si esamina una query Kusto è l'uso del simbolo di pipe (|). La struttura di una query Kusto inizia con il recupero dei dati da un'origine dati e quindi il passaggio dei dati in una "pipeline" e ogni passaggio fornisce un certo livello di elaborazione e quindi passa i dati al passaggio successivo. Alla fine della pipeline si ottiene il risultato finale. In effetti, questa è la pipeline:

Get Data | Filter | Summarize | Sort | Select

Questo concetto di passaggio dei dati verso il basso della pipeline rende una struttura intuitiva, in quanto è facile creare un'immagine mentale dei dati in ogni passaggio.

Per illustrare questo aspetto, si esaminerà la query seguente, che esamina i log di accesso di Microsoft Entra. Durante la lettura di ogni riga, è possibile visualizzare le parole chiave che indicano cosa accade ai dati. La fase pertinente nella pipeline è stata inclusa come commento in ogni riga.

Nota

È possibile aggiungere commenti a qualsiasi riga in una query precedendoli con una doppia barra (//).

SigninLogs                              // Get data
| evaluate bag_unpack(LocationDetails)  // Ignore this line for now; we'll come back to it at the end.
| where RiskLevelDuringSignIn == 'none' // Filter
   and TimeGenerated >= ago(7d)         // Filter
| summarize Count = count() by city     // Summarize
| sort by Count desc                    // Sort
| take 5                                // Select

Poiché l'output di ogni passaggio funge da input per il passaggio seguente, l'ordine dei passaggi può determinare i risultati della query e influire sulle prestazioni. È fondamentale ordinare i passaggi in base a ciò che si vuole ottenere dalla query.

Suggerimento

  • Una buona regola generale consiste nel filtrare i dati in anticipo, per cui si passano solo i dati pertinenti nella pipeline. Ciò aumenta notevolmente le prestazioni e garantisce che non si includano accidentalmente dati irrilevanti nei passaggi di riepilogo.
  • Questo articolo illustra alcune altre procedure consigliate da tenere presente. Per un elenco più completo, vedere Procedure consigliate per le query.

Si spera ora di avere un apprezzamento per la struttura complessiva di una query nel Linguaggio di query Kusto. Verranno ora esaminati gli operatori di query effettivi, che vengono usati per creare una query.

Tipo di dati

Prima di accedere agli operatori di query, si esaminerà prima di tutto i tipi di dati. Come nella maggior parte dei linguaggi, il tipo di dati determina quali calcoli e manipolazioni è possibile eseguire su un valore. Ad esempio, se si dispone di un valore di tipo stringa, non sarà possibile eseguire calcoli aritmetici su di esso.

In Linguaggio di query Kusto la maggior parte dei tipi di dati segue le convenzioni standard e ha nomi probabilmente visti in precedenza. La tabella seguente mostra l'elenco completo:

Tabella dei tipi di dati

Type Nome/i aggiuntivo/i Tipo .NET equivalente
bool Boolean System.Boolean
datetime Date System.DateTime
dynamic System.Object
guid uuid, uniqueid System.Guid
int System.Int32
long System.Int64
real Double System.Double
string System.String
timespan Time System.TimeSpan
decimal System.Data.SqlTypes.SqlDecimal

Anche se la maggior parte dei tipi di dati è standard, è possibile che si abbia meno familiarità con tipi come dinamico, intervallo di tempoe GUID.

Dynamic ha una struttura simile a JSON, ma con una differenza chiave: può archiviare Linguaggio di query Kusto tipi di dati specifici che JSON tradizionale non può, ad esempio un valore dinamico annidato o un intervallo di tempo. Ecco un esempio di tipo dinamico:

{
"countryOrRegion":"US",
"geoCoordinates": {
"longitude":-122.12094116210936,
"latitude":47.68050003051758
},
"state":"Washington",
"city":"Redmond"
}

Intervallo di tempo è un tipo di dati che fa riferimento a una misura di tempo, ad esempio ore, giorni o secondi. Non confondere timespan con datetime, che restituisce una data e un'ora effettive, non una misura di ora. La tabella seguente mostra un elenco di suffissi intervallo di tempo.

Suffissi intervallo di tempo

Funzione Descrizione
D giorni
H hours
M minutes
S seconds
Ms milliseconds
Microsecond microsecondi
Tick nanosecondi

Guid è un tipo di dati che rappresenta un identificatore univoco globale a 128 bit, che segue il formato standard [8]-[4]-[4]-[12], dove ogni [numero] rappresenta il numero di caratteri e ogni carattere può variare da 0 a 9 o a-f.

Nota

Il Linguaggio di query Kusto include operatori tabulari e scalari. Nel resto di questo articolo, se si visualizza semplicemente la parola "operatore", è possibile presupporre che significa operatore tabulare, a meno che non diversamente specificato.

Recupero, limitazione, ordinamento e filtraggio dei dati

Il vocabolario principale di Linguaggio di query Kusto, la base che consente di eseguire la maggior parte delle attività, è una raccolta di operatori per filtrare, ordinare e selezionare i dati. Le attività rimanenti richiedono di estendere la conoscenza del linguaggio per soddisfare le esigenze più avanzate. Si esaminerà ora alcuni dei comandi usati nell'esempio precedente e si esaminerà take, sorte where.

Per ognuno di questi operatori, viene esaminato l'uso nell'esempio precedente di SigninLogs e viene illustrato un suggerimento utile o una procedura consigliata.

Acquisizione dei dati

La prima riga di qualsiasi query di base specifica la tabella da utilizzare. Nel caso di Microsoft Sentinel, è probabile che questo sia il nome di un tipo di log nell'area di lavoro, ad esempio SigninLogs, SecurityAlert o CommonSecurityLog. Ad esempio:

SigninLogs

In Linguaggio di query Kusto, i nomi dei log fanno distinzione tra maiuscole e minuscole, signinLogs quindi SigninLogs vengono interpretati in modo diverso. Prestare attenzione quando si scelgono i nomi per i log personalizzati, in modo che siano facilmente identificabili e non troppo simili a un altro log.

Limitazione dei dati: includi / limite

L'operatore includi (e l'operatore limite identico) viene usato per limitare i risultati restituendo solo un determinato numero di righe. È seguito da un numero intero che specifica il numero di righe da restituire. In genere, viene usato alla fine di una query dopo aver determinato l'ordinamento e, in tal caso, restituisce il numero specificato di righe all'inizio dell'ordinamento.

L'uso di take in precedenza nella query può essere utile per testare una query, quando non si vogliono restituire set di dati di grandi dimensioni. Tuttavia, se si inserisce l'operazione take prima di qualsiasi sort operazione, take restituisce le righe selezionate in modo casuale ed eventualmente un set diverso di righe ogni volta che viene eseguita la query. Ecco un esempio di uso di includi:

SigninLogs
      | take 5

Screenshot dei risultati dell'operatore includi.

Suggerimento

Quando si lavora su una query completamente nuova in cui è possibile che non si conosca l'aspetto della query, può essere utile inserire un'istruzione take all'inizio per limitare artificialmente il set di dati per un'elaborazione e una sperimentazione più veloci. Dopo aver soddisfatto la query completa, è possibile rimuovere il passaggio take iniziale.

Ordinamento dei dati: ordina / ordine

L'operatore ordina (e l'operatore ordine identico) viene usato per ordinare i dati in base a una colonna specificata. Nell'esempio seguente i risultati sono stati ordinati in base a TimeGenerated e si ha impostato l'ordinamento in ordine decrescente con il parametro desc, inserendo i valori più alti per primi. Per un ordine crescente, si userà asc.

Nota

La direzione predefinita per gli ordinamenti è decrescente, quindi tecnicamente è necessario specificare solo se si desidera ordinare in ordine crescente. Tuttavia, specificando la direzione di ordinamento in qualsiasi caso, la query risulta più leggibile.

SigninLogs
| sort by TimeGenerated desc
| take 5

Come accennato, l'operatore sort viene inserito prima di quello take. È necessario ordinare prima di tutto per assicurarsi di ottenere i cinque record appropriati.

Screenshot dei risultati dell'operatore di ordinamento, con limite includi.

Top

L'operatore top consente di combinare le operazioni sort e take in un singolo operatore:

SigninLogs
| top 5 by TimeGenerated desc

Nei casi in cui due o più record hanno lo stesso valore nella colonna in base alla quale si esegue l'ordinamento, è possibile aggiungere altre colonne per l'ordinamento. Aggiungere colonne di ordinamento aggiuntive in un elenco delimitato da virgole, che si trova dopo la prima colonna di ordinamento, ma prima della parola chiave ordinamento. Ad esempio:

SigninLogs
| sort by TimeGenerated, Identity desc
| take 5

Ora, se TimeGenerated è lo stesso tra più record, tenta di ordinare in base al valore nella colonna Identity .

Nota

Quando usare sort e take e quando usare top

  • Se si esegue l'ordinamento solo in un campo, usare top, perché offre prestazioni migliori rispetto alla combinazione di sort e take.

  • Se è necessario ordinare più campi (come nell'ultimo esempio), top non è possibile eseguire questa operazione, quindi è necessario usare sort e take.

Filtraggio dei dati: dove

L'operatore where è probabilmente l'operatore più importante, perché è la chiave per assicurarsi di usare solo il subset di dati rilevanti per lo scenario. È consigliabile filtrare i dati il prima possibile nella query, perché ciò migliora le prestazioni delle query riducendo la quantità di dati che devono essere elaborati nei passaggi successivi; garantisce inoltre di eseguire calcoli solo sui dati desiderati. Vedere questo esempio:

SigninLogs
| where TimeGenerated >= ago(7d)
| sort by TimeGenerated, Identity desc
| take 5

L'operatore where specifica una variabile, un operatore di confronto (scalare) e un valore. In questo caso, è stato usato >= per indicare che il valore nella colonna TimeGenerated deve essere maggiore di (ovvero successivo a) o uguale a sette giorni fa.

Esistono due tipi di operatori di confronto nel Linguaggio di query Kusto: stringa e numerico. La tabella seguente mostra l'elenco completo degli operatori numerici:

Operatori numerici

Operatore Descrizione
+ Aggiunta
- Sottrazione
* Moltiplicazione
/ Divisione
% Modulo
< Minore di
> Maggiore di
== Uguale a
!= Diverso da
<= Minore o uguale a
>= Maggiore o uguale a
in Uguale a uno degli elementi
!in Non uguale a uno degli elementi

L'elenco degli operatori stringa è un elenco più lungo perché include permutazioni per la distinzione tra maiuscole e minuscole, posizioni di sottostringa, prefissi, suffissi e molto altro ancora. L'operatore == è sia un operatore numerico che un operatore stringa, ovvero può essere usato sia per i numeri che per il testo. Ad esempio, entrambe le istruzioni seguenti sono valide dove le istruzioni:

  • | where ResultType == 0
  • | where Category == 'SignInLogs'

Procedura consigliata: nella maggior parte dei casi, è probabile che si voglia filtrare i dati in base a più colonne o filtrare la stessa colonna in più modi. In questi casi, è consigliabile tenere presenti due procedure consigliate.

È possibile combinare più istruzioni where in un singolo passaggio usando la parola chiave e . Ad esempio:

SigninLogs
| where Resource == ResourceGroup
    and TimeGenerated >= ago(7d)

Quando si dispone di più filtri uniti in un'unica where istruzione usando la parola chiave e , si ottengono prestazioni migliori inserendo filtri che fanno riferimento solo a una singola colonna. Quindi, un modo migliore per scrivere la query precedente sarebbe:

SigninLogs
| where TimeGenerated >= ago(7d)
    and Resource == ResourceGroup

In questo esempio il primo filtro menziona una singola colonna (TimeGenerated), mentre la seconda fa riferimento a due colonne (Risorsa e ResourceGroup).

Riepilogo dei dati

Summarize è uno degli operatori tabulari più importanti in Linguaggio di query Kusto, ma è anche uno degli operatori più complessi per imparare se non si ha familiarità con i linguaggi di query in generale. Il processo di summarize consiste nell'acquisire una tabella di dati e restituire una nuova tabella aggregata da una o più colonne.

Struttura dell'istruzione riepiloga

La struttura di base di un'istruzione summarize è la seguente:

| summarize <aggregation> by <column>

Ad esempio, quanto segue restituisce il numero di record per ogni valore CounterName nella tabella Perf:

Perf
| summarize count() by CounterName

Screenshot dei risultati dell'operatore riepiloga con l'aggregazione conteggio.

Poiché l'output di summarize è una nuova tabella, tutte le colonne non specificate in modo esplicito nell'istruzione summarize non vengono passate alla pipeline. Per illustrare questo concetto, considerare questo esempio:

Perf
| project ObjectName, CounterValue, CounterName
| summarize count() by CounterName
| sort by ObjectName asc

Nella seconda riga si specifica che ci si occupa solo delle colonne ObjectName, CounterValue e CounterName.On the second line, we're specifying that we's care about the columns ObjectName, CounterValue, and CounterName. È stato quindi riepilogato per ottenere il conteggio dei record per CounterName e infine si tenta di ordinare i dati in ordine crescente in base alla colonna ObjectName. Sfortunatamente, questa query ha esito negativo con un errore (a indicare che ObjectName è sconosciuto) perché, quando riepilogato, sono state incluse solo le colonne Count e CounterName nella nuova tabella. Per evitare questo errore, è possibile aggiungere ObjectName alla fine del summarize passaggio, come illustrato di seguito:

Perf
| project ObjectName, CounterValue , CounterName
| summarize count() by CounterName, ObjectName
| sort by ObjectName asc

Il modo per leggere la summarize riga nella testa sarebbe: "riepilogare il conteggio dei record per CounterName e raggruppare per ObjectName". È possibile continuare ad aggiungere colonne, separate da virgole, alla fine dell'istruzione summarize .

Screenshot dei risultati dell'operatore riepiloga con due argomenti.

Basandosi sull'esempio precedente, se si vogliono aggregare più colonne contemporaneamente, è possibile ottenere questo risultato aggiungendo aggregazioni all'operatore summarize, separate da virgole. Nell'esempio seguente viene ottenuto non solo un conteggio di tutti i record, ma anche una somma dei valori nella colonna CounterValue in tutti i record (che corrispondono a tutti i filtri nella query):

Perf
| project ObjectName, CounterValue , CounterName
| summarize count(), sum(CounterValue) by CounterName, ObjectName
| sort by ObjectName asc

Screenshot dei risultati dell'operatore riepiloga con più aggregazioni.

Ridenominazione delle colonne aggregate

Questo sembra un buon momento per parlare dei nomi di colonna per queste colonne aggregate. All'inizio di questa sezione, si è detto che l'operatore summarize accetta una tabella di dati e produce una nuova tabella e solo le colonne specificate nell'istruzione summarize continuano verso il basso nella pipeline. Pertanto, se si esegue l'esempio precedente, le colonne risultanti per l'aggregazione saranno count_ e sum_CounterValue.

Il motore Kusto crea automaticamente un nome di colonna senza che sia necessario essere espliciti, ma spesso si ritiene che la nuova colonna abbia un nome più descrittivo. È possibile rinominare facilmente la colonna nell'istruzione summarize specificando un nuovo nome, seguito da = e l'aggregazione, come indicato di seguito:

Perf
| project ObjectName, CounterValue , CounterName
| summarize Count = count(), CounterSum = sum(CounterValue) by CounterName, ObjectName
| sort by ObjectName asc

Le colonne riepilogate sono ora denominate Count e CounterSum.

Screenshot dei nomi descrittivi delle colonne per le aggregazioni.

L'operatore summarize è più di quanto sia possibile coprire qui, ma è consigliabile investire il tempo necessario per apprenderlo perché si tratta di un componente chiave per qualsiasi analisi dei dati che si prevede di eseguire sui dati di Microsoft Sentinel.

Informazioni di riferimento sulle aggregazioni

Sono molte funzioni di aggregazione, ma alcune delle più comunemente usate sono sum(), count()e avg(). Ecco un elenco parziale (vedere l'elenco completo):

Funzione di aggregazione

Funzione Descrizione
arg_max() Restituisce una o più espressioni quando l'argomento è massimizzato
arg_min() Restituisce una o più espressioni quando l'argomento è minimizzato
avg() Restituisce un valore medio nel gruppo
buildschema() Restituisce lo schema minimo che ammette tutti i valori dell'input dinamico
count() Restituisce un conteggio del gruppo
countif() Restituisce un conteggio con il predicato del gruppo
dcount() Restituisce un numero approssimativo di valori distinti degli elementi del gruppo
make_bag() Restituisce un contenitore di proprietà di valori dinamici all'interno del gruppo
make_list() Restituisce un elenco di tutti i valori contenuti nel gruppo
make_set() Restituisce un set di valori distinti contenuti nel gruppo
max() Restituisce il valore massimo nel gruppo
min() Restituisce il valore minimo nel gruppo
percentiles() Restituisce il percentile approssimativo del gruppo
stdev() Restituisce la deviazione standard del gruppo
sum() Restituisce la somma degli elementi all'interno del gruppo
take_any() Restituisce un valore nonempty casuale per il gruppo
variance() Restituisce la varianza del gruppo

Selezione: aggiunta e rimozione di colonne

Quando si inizia a lavorare più con le query, è possibile che siano presenti più informazioni di quelle necessarie per gli argomenti, ovvero troppe colonne della tabella. Oppure potrebbero essere necessarie più informazioni di quelle disponibili, ovvero è necessario aggiungere una nuova colonna contenente i risultati dell'analisi di altre colonne. Verranno ora esaminati alcuni degli operatori chiave per la manipolazione delle colonne.

Progetto e project-away

Progetto equivale approssimativamente alle istruzioni seleziona di molti linguaggi. Consente di scegliere le colonne da tenere. L'ordine delle colonne restituite corrisponde all'ordine delle colonne elencate nell'istruzione project , come illustrato in questo esempio:

Perf
| project ObjectName, CounterValue, CounterName

Screenshot dei risultati dell'operatore di progetto.

Come si può immaginare, quando si lavora con set di dati di grandi dimensioni, potrebbero essere presenti molte colonne da mantenere e specificarle tutte in base al nome richiederebbero molta digitazione. Per questi casi, si dispone di project-away, che consente di specificare quali colonne rimuovere, anziché quelle da conservare, come illustrato di seguito:

Perf
| project-away MG, _ResourceId, Type

Suggerimento

Può essere utile usare project in due posizioni nelle query, all'inizio e di nuovo alla fine. L'uso di project in precedenza nella query consente di migliorare le prestazioni rimuovendo blocchi di dati di grandi dimensioni che non è necessario passare alla pipeline. Usandolo di nuovo alla fine, è possibile eliminare tutte le colonne che potrebbero essere state create nei passaggi precedenti e non sono necessarie nell'output finale.

Extend

Estendi viene usato per creare una nuova colonna calcolata. Ciò può essere utile quando si desidera eseguire un calcolo su colonne esistenti e visualizzare l'output per ogni riga. Si esaminerà un semplice esempio in cui si calcola una nuova colonna denominata Kbytes, che è possibile calcolare moltiplicando il valore MB (nella colonna Quantità esistente) per 1.024.

Usage
| where QuantityUnit == 'MBytes'
| extend KBytes = Quantity * 1024
| project DataType, MBytes=Quantity, KBytes

Nella riga finale dell'istruzione project è stata rinominata la colonna Quantità in Mbytes, in modo da poter indicare facilmente quale unità di misura è rilevante per ogni colonna.

Screenshot dei risultati dell'operatore Estendi.

Vale la pena notare che extend funziona anche con colonne già calcolate. Ad esempio, è possibile aggiungere un'altra colonna denominata Byte calcolata da Kbyte:

Usage
| where QuantityUnit == 'MBytes'
| extend KBytes = Quantity * 1024
| extend Bytes = KBytes * 1024
| project DataType, MBytes=Quantity, KBytes, Bytes

Screenshot dei risultati di due operatori Estendi.

Join di tabelle

Gran parte del lavoro in Microsoft Sentinel può essere eseguita usando un singolo tipo di log, ma in alcuni casi è necessario correlare i dati insieme o eseguire una ricerca su un altro set di dati. Analogamente alla maggior parte dei linguaggi di query, il Linguaggio di query Kusto offre alcuni operatori usati per eseguire vari tipi di join. In questa sezione vengono esaminati gli operatori union più usati e join.

Union

Unione accetta semplicemente due o più tabelle e restituisce tutte le righe. Ad esempio:

OfficeActivity
| union SecurityEvent

Verranno restituite tutte le righe dalle tabelle OfficeActivity e SecurityEvent. Union offre alcuni parametri che possono essere usati per regolare il comportamento dell'unione. Due delle più utili sono withsource e tipo:

OfficeActivity
| union withsource = SourceTable kind = inner SecurityEvent

Il parametro withsource consente di specificare il nome di una nuova colonna il cui valore in una determinata riga è il nome della tabella da cui proviene la riga. Nell'esempio la colonna SourceTable è stata denominata e, a seconda della riga, il valore è OfficeActivity o SecurityEvent.

L'altro parametro specificato è stato tipo, che ha due opzioni: inner o outer. Nell'esempio è stato specificato inner, ovvero le uniche colonne mantenute durante l'unione sono quelle presenti in entrambe le tabelle. In alternativa, se fosse stato specificato outer (ovvero il valore predefinito), verranno restituite tutte le colonne di entrambe le tabelle.

Join.

Join funziona in modo analogo a union, ad eccezione del fatto che invece di unire tabelle per creare una nuova tabella, verranno unite righe per creare una nuova tabella. Analogamente alla maggior parte dei linguaggi di database, è possibile eseguire più tipi di join. La sintassi generale per un join è:

T1
| join kind = <join type>
(
               T2
) on $left.<T1Column> == $right.<T2Column>

Dopo l'operatore join, si specifica il tipo di join da eseguire seguito da una parentesi aperta. All'interno delle parentesi è possibile specificare la tabella da aggiungere e tutte le altre istruzioni di query nella tabella da aggiungere. Dopo la parentesi di chiusura, viene usata la parola chiave on seguita dalle colonne sinistra (parola chiave $left.<columnName>) e destra ($right.<columnName>) separate con l'operatore ==. Ecco un esempio di inner join:

OfficeActivity
| where TimeGenerated >= ago(1d)
    and LogonUserSid != ''
| join kind = inner (
    SecurityEvent
    | where TimeGenerated >= ago(1d)
        and SubjectUserSid != ''
) on $left.LogonUserSid == $right.SubjectUserSid

Nota

Se entrambe le tabelle hanno lo stesso nome per le colonne in cui si esegue un join, non è necessario usare $left e $right. È invece possibile specificare solo il nome della colonna. L'uso di $left e $right, tuttavia, è più esplicito e generalmente considerato una procedura consigliata.

Per riferimento, la tabella seguente mostra un elenco dei tipi di join disponibili.

Tipi di join

Tipo di aggiunta Descrizione
inner Restituisce un singolo oggetto per ogni combinazione di righe corrispondenti di entrambe le tabelle.
innerunique Restituisce righe dalla tabella sinistra con valori distinti nel campo collegato che hanno una corrispondenza nella tabella destra.
Si tratta del tipo di join non specificato predefinito.
leftsemi Restituisce tutti i record della tabella sinistra che hanno una corrispondenza nella tabella destra.
Vengono restituite solo le colonne della tabella sinistra.
rightsemi Restituisce tutti i record della tabella destra che hanno una corrispondenza nella tabella sinistra.
Vengono restituite solo le colonne della tabella destra.
leftanti/
leftantisemi
Restituisce tutti i record della tabella sinistra che non hanno una corrispondenza nella tabella destra.
Vengono restituite solo le colonne della tabella sinistra.
rightanti/
rightantisemi
Restituisce tutti i record della tabella destra che non hanno una corrispondenza nella tabella sinistra.
Vengono restituite solo le colonne della tabella destra.
leftouter Restituisce tutti i record della tabella sinistra. Per i record che non hanno corrispondenze nella tabella corretta, i valori delle celle sono Null.
rightouter Restituisce tutti i record della tabella destra. Per i record che non hanno corrispondenze nella tabella sinistra, i valori delle celle sono Null.
fullouter Restituisce tutti i record delle tabelle sinistra e destra, corrispondenti o meno.
I valori non corrispondenti sono Null.

Suggerimento

È consigliabile avere la tabella più piccola a sinistra. In alcuni casi, seguendo questa regola è possibile ottenere enormi vantaggi in termini di prestazioni, a seconda dei tipi di join sufficienti e delle dimensioni delle tabelle.

Evaluate

È possibile ricordare che nel primo esempio è stato visualizzato l'operatore evaluate su una delle righe. L'operatore evaluate è meno comunemente usato di quelli che abbiamo toccato in precedenza. Tuttavia, vale la pena sapere come funziona l'operatore evaluate. Ancora una volta, ecco la prima query, in cui viene visualizzata evaluate nella seconda riga.

SigninLogs
| evaluate bag_unpack(LocationDetails)
| where RiskLevelDuringSignIn == 'none'
   and TimeGenerated >= ago(7d)
| summarize Count = count() by city
| sort by Count desc
| take 5

Questo operatore consente di richiamare i plug-in disponibili (funzioni predefinite). Molti di questi plug-in sono incentrati sulla data science, ad esempio autocluster, diffpattern e sequence_detect, consentendo di eseguire analisi avanzate e individuare anomalie statistiche e outlier.

Il plug-in usato nell'esempio viene chiamato bag_unpack e semplifica l'uso di un blocco di dati dinamici e la conversione in colonne. Tenere presente che i dati dinamici sono un tipo di dati simile a JSON, come illustrato in questo esempio:

{
"countryOrRegion":"US",
"geoCoordinates": {
"longitude":-122.12094116210936,
"latitude":47.68050003051758
},
"state":"Washington",
"city":"Redmond"
}

In questo caso, si desidera riepilogare i dati in base alla città, ma città è contenuta come proprietà all'interno della colonna LocationDetails. Per usare la proprietà città nella query, è necessario prima convertirla in una colonna usando bag_unpack.

Tornando ai passaggi originali della pipeline, è stato illustrato quanto segue:

Get Data | Filter | Summarize | Sort | Select

Ora che l'operatore evaluate è stato considerato, è possibile vedere che rappresenta una nuova fase nella pipeline, che ora ha un aspetto simile al seguente:

Get Data | Parse | Filter | Summarize | Sort | Select

Esistono molti altri esempi di operatori e funzioni che possono essere usati per analizzare le origini dati in un formato più leggibile e manipolabile. È possibile ottenere informazioni su di essi, e il resto del linguaggio di query Kusto, nella documentazione completa e nella cartella di lavoro.

Istruzioni "let"

Ora che sono stati trattati molti dei principali operatori e tipi di dati, si esaminerà l'istruzione let, che è un ottimo modo per semplificare la lettura, la modifica e la gestione delle query.

Let consente di creare e impostare una variabile o di assegnare un nome a un'espressione. Questa espressione può essere un singolo valore, ma potrebbe anche essere un'intera query. Ecco un semplice esempio:

let aWeekAgo = ago(7d);
SigninLogs
| where TimeGenerated >= aWeekAgo

In questo caso è stato specificato un nome di aWeekAgo e lo si imposta come uguale all'output di una funzione intervallo di tempo, che restituisce un valore data/ora. Terminare quindi l'istruzione let con un punto e virgola. Ora è disponibile una nuova variabile denominata aWeekAgo che può essere usata in qualsiasi punto della query.

Come accennato, è possibile usare un'istruzione let che accetta un'intera query e assegnare un nome al risultato. Poiché i risultati delle query, essendo espressioni tabulari, possono essere usati come input di query, è possibile trattare questo risultato denominato come tabella ai fini dell'esecuzione di un'altra query su di essa. Ecco una leggera modifica all'esempio precedente:

let aWeekAgo = ago(7d);
let getSignins = SigninLogs
| where TimeGenerated >= aWeekAgo;
getSignins

In questo caso è stata creata una seconda istruzione let, in cui è stato eseguito il wrapping dell'intera query in una nuova variabile denominata getSignins. Proprio come in precedenza, viene terminata la seconda istruzione let con un punto e virgola. Viene quindi chiamata la variabile nella riga finale, che esegue la query. Si noti che è stato possibile usare aWeekAgo nella seconda istruzione let. Ciò è dovuto al fatto che è stato specificato nella riga precedente; se fosse necessario scambiare le istruzioni let in modo che getSignins venisse per primo, si otterrebbe un errore.

È ora possibile usare getSignins come base di un'altra query (nella stessa finestra):

let aWeekAgo = ago(7d);
let getSignins = SigninLogs
| where TimeGenerated >= aWeekAgo;
getSignins
| where level >= 3
| project IPAddress, UserDisplayName, Level

Le istruzioni Let offrono maggiore potenza e flessibilità per organizzare le query. Let è in grado di definire valori scalari e tabulari, nonché creare funzioni definite dall'utente. Sono davvero utili quando si organizzano query più complesse che potrebbero eseguire più join.

Passaggi successivi

Anche se questo articolo ha trattato solo le nozioni di base necessarie, sono state illustrate le parti usate più spesso per svolgere il proprio lavoro in Microsoft Sentinel.

Cartella di lavoro avanzata KQL per Microsoft Sentinel

Sfruttare i vantaggi di una cartella di lavoro del linguaggio di query Kusto direttamente in Microsoft Sentinel, ovvero la cartella di lavoro KQL avanzato per Microsoft Sentinel. Offre informazioni dettagliate ed esempi per molte delle situazioni che è probabile riscontrare durante le operazioni di sicurezza quotidiane, oltre a fornire numerosi esempi predefiniti di regole di analisi, cartelle di lavoro, regole di ricerca e altri elementi che usano query Kusto. Avviare questa cartella di lavoro dalla pagina Cartelle di lavoro in Microsoft Sentinel.

Cartella di lavoro avanzata di KQL Framework: diventare esperti di KQL è un eccellente post di blog che illustra come usare questa cartella di lavoro.

Altre risorse

Vedere questa raccolta di risorse di apprendimento, training e competenze per ampliare e approfondire le conoscenze del Linguaggio di query Kusto.