Creare un parser ASIM
Gli utenti di Advanced Security Information Model (ASIM) usano parser unificanti anziché nomi di tabella nelle query, per visualizzare i dati in un formato normalizzato e includere tutti i dati pertinenti allo schema nella query. Unificare i parser, a sua volta, usare parser specifici dell'origine per gestire i dettagli specifici di ogni origine.
Microsoft Sentinel offre parser predefiniti specifici dell'origine per molte origini dati. È possibile modificare o sviluppare questi parser specifici dell'origine nelle situazioni seguenti:
Quando il tuo dispositivo fornisce eventi che rispettano uno schema ASIM, ma un parser specifico per l'origine del dispositivo e lo schema pertinente non sono disponibili in Microsoft Sentinel.
Quando i parser ASIM specifici dell'origine sono disponibili per il dispositivo, ma il dispositivo invia eventi in un metodo o un formato diverso da quello previsto dai parser ASIM. Per esempio:
Il dispositivo di origine può essere configurato per inviare eventi in modo non standard.
Il dispositivo può avere una versione diversa da quella supportata dal parser ASIM.
Gli eventi possono essere raccolti, modificati e inoltrati da un sistema intermedio.
Processo di sviluppo del parser personalizzato
Il flusso di lavoro seguente descrive i passaggi generali per lo sviluppo di un parser ASIM personalizzato specifico dell'origine:
Raccogliere log di esempio.
Identificare lo schema o gli schemi rappresentati dagli eventi inviati dall'origine.
Eseguire il mapping dei campi dell'evento di origine allo schema o agli schemi identificati.
Sviluppare uno o più parser ASIM per l'origine. Sarà necessario sviluppare un parser di filtro e un parser senza parametri per ogni schema rilevante per l'origine.
Testare il parser.
Distribuire i parser nelle aree di lavoro di Microsoft Sentinel.
Aggiornare il parser di unificazione ASIM rilevante per fare riferimento al nuovo parser personalizzato.
È anche possibile contribuire i parser alla distribuzione primaria di ASIM. I parser che hanno contribuito possono anche essere resi disponibili in tutti le aree di lavoro come parser predefiniti.
Raccogliere log di esempio
Per creare parser ASIM efficaci, è necessario un set rappresentativo di log, che nella maggior parte dei casi richiede la configurazione del sistema di origine e la connessione a Microsoft Sentinel. Se il dispositivo di origine non è disponibile, i servizi con pagamento in base al consumo cloud consentono di distribuire molti dispositivi per lo sviluppo e i test.
Inoltre, trovare la documentazione del fornitore e gli esempi dei log può contribuire ad accelerare lo sviluppo e ridurre gli errori garantendo un'ampia copertura del formato dei log.
Un set rappresentativo di log deve includere:
- Eventi con risultati di eventi diversi.
- Eventi con azioni di risposta diverse.
- Formati diversi per nome utente, nome host e ID e altri campi che richiedono la normalizzazione dei valori.
Mappatura
Prima di sviluppare un parser, eseguire il mapping delle informazioni disponibili nell'evento di origine o degli eventi allo schema identificato:
- Eseguire il mapping di tutti i campi obbligatori e preferibilmente anche di quelli consigliati.
- Provare a eseguire il mapping delle informazioni disponibili nell'origine ai campi normalizzati. Se non è disponibile come parte dello schema selezionato, prendere in considerazione il mapping ai campi disponibili in altri schemi.
- Eseguire il mapping dei valori dei campi nell'origine ai valori normalizzati consentiti da ASIM. Il valore originale viene archiviato in un campo separato, ad esempio EventOriginalResultDetails.
Sviluppo di parser
Sviluppare sia un parser di filtro che un parser senza parametri per ogni schema rilevante.
Un parser personalizzato è una query KQL sviluppata nella pagina Log di Microsoft Sentinel. La query del parser è composta da tre parti:
Filtrare i > campi Di preparazione analisi >
Filtro dei record rilevanti
In molti casi, una tabella di Microsoft Sentinel include più tipi di eventi. Per esempio:
- La tabella Syslog contiene dati provenienti da più origini.
- Le tabelle personalizzate possono includere informazioni provenienti da una singola origine che fornisce più di un tipo di evento e può adattarsi a vari schemi.
Pertanto, un parser deve prima filtrare solo i record rilevanti per lo schema di destinazione.
Il filtro in KQL viene eseguito usando l'operatore where . Ad esempio, l'evento Sysmon 1 segnala la creazione di un processo e viene quindi normalizzato nello schema ProcessEvent. L'evento Sysmon 1 fa parte della tabella Event , quindi si userà il filtro seguente:
Event | where Source == "Microsoft-Windows-Sysmon" and EventID == 1
Importante
Un parser non deve filtrare in base al tempo. La query che usa il parser applicherà un intervallo di tempo.
Filtro in base al tipo di origine tramite una watchlist
In alcuni casi, l'evento stesso non contiene informazioni che consentono di filtrare per tipi di origine specifici.
Ad esempio, gli eventi DNS Infoblox vengono inviati come messaggi Syslog e sono difficili da distinguere dai messaggi Syslog inviati da altre origini. In questi casi, il parser si basa su un elenco di origini che definisce gli eventi rilevanti. Questo elenco viene mantenuto nell'elenco di controllo ASimSourceType.
Per usare l'elenco di controllo ASimSourceType nei parser:
- Includere la riga seguente all'inizio del parser:
let Sources_by_SourceType=(sourcetype:string){_GetWatchlist('ASimSourceType') | where SearchKey == tostring(sourcetype) | extend Source=column_ifexists('Source','') | where isnotempty(Source)| distinct Source };
- Aggiungere un filtro che usa l'elenco di controllo nella sezione filtro del parser. Ad esempio, il parser DNS Infoblox include quanto segue nella sezione filtro:
| where Computer in (Sources_by_SourceType('InfobloxNIOS'))
Per usare questo esempio nel parser:
Sostituire Computer con il nome del campo che include le informazioni di origine per l'origine. È possibile mantenerlo come Computer per qualsiasi parser basato su Syslog.
Sostituire il token InfobloxNIOS con un valore di propria scelta per il parser. Informare gli utenti del parser che devono aggiornare l'elenco di controllo ASimSourceType usando il valore selezionato e l'elenco di origini che inviano eventi di questo tipo.
Filtro basato sui parametri del parser
Quando si sviluppano parser di filtro, assicurarsi che il parser accetti i parametri di filtro per lo schema pertinente, come documentato nell'articolo di riferimento per tale schema. L'uso di un parser esistente come punto di partenza garantisce che il parser includa la firma della funzione corretta. Nella maggior parte dei casi, anche il codice di filtro effettivo è simile per i parser di filtro dello stesso schema.
Quando si filtra, assicurarsi di:
- Filtrare prima di analizzare usando i campi fisici. Se i risultati filtrati non sono sufficientemente accurati, ripetere il test dopo l'analisi per ottimizzare i risultati. Per altre informazioni, vedere Ottimizzazione del filtro.
- Non filtrare se il parametro non è definito e ha comunque il valore predefinito.
Gli esempi seguenti illustrano come implementare il filtro per un parametro stringa, in cui il valore predefinito è in genere '*' e per un parametro elenco, in cui il valore predefinito è in genere un elenco vuoto.
srcipaddr=='*' or ClientIP==srcipaddr
array_length(domain_has_any) == 0 or Name has_any (domain_has_any)
Ottimizzazione dei filtri
Per garantire le prestazioni del parser, tenere presenti le raccomandazioni di filtro seguenti:
- Filtrare sempre in base ai campi predefiniti anziché a quelli analizzati. Anche se a volte è più semplice filtrare usando i campi analizzati, influisce notevolmente sulle prestazioni.
- Usare gli operatori che offrono prestazioni ottimizzate. In particolare, ==, ha e startswith. L'uso di operatori come contiene o corrisponde a regex influisce notevolmente sulle prestazioni.
Le raccomandazioni di filtro per le prestazioni potrebbero non essere sempre facili da seguire. Ad esempio, l'uso di has è meno accurato di quanto contenga. In altri casi, la corrispondenza del campo predefinito, ad esempio SyslogMessage, è meno accurata rispetto al confronto di un campo estratto, ad esempio DvcAction. In questi casi, è consigliabile adottare un filtro preliminare usando un operatore di ottimizzazione delle prestazioni su un campo predefinito e ripetere il filtro usando condizioni più accurate dopo l'analisi.
Per un esempio, vedere il frammento di parser DNS Infoblox seguente. Il parser verifica innanzitutto che il campo SyslogMessage abbia la parola client. Tuttavia, il termine potrebbe essere usato in una posizione diversa nel messaggio, quindi dopo l'analisi del campo Log_Type, il parser verifica nuovamente che il client di parole fosse effettivamente il valore del campo.
Syslog | where ProcessName == "named" and SyslogMessage has "client"
…
| extend Log_Type = tostring(Parser[1]),
| where Log_Type == "client"
Analisi
Una volta che la query ha selezionato i record rilevanti, potrebbe essere necessario analizzarli. In genere, l'analisi è necessaria se più campi evento vengono trasmessi in un singolo campo di testo.
Gli operatori KQL che eseguono l'analisi sono elencati di seguito, ordinati in base all'ottimizzazione delle prestazioni. Il primo fornisce le prestazioni più ottimizzate, mentre l'ultimo fornisce le prestazioni meno ottimizzate.
| Operatore | Descrizione |
|---|---|
| diviso | Analizzare una stringa di valori delimitati. |
| parse_csv | Analizzare una stringa di valori formattata come riga CSV (valori delimitati da virgole). |
| parse | Analizzare più valori da una stringa arbitraria usando un criterio, che può essere un modello semplificato con prestazioni migliori o un'espressione regolare. |
| extract_all | Analizzare singoli valori da una stringa arbitraria usando un'espressione regolare. extract_all offre prestazioni simili da analizzare se quest'ultimo usa un'espressione regolare. |
| estratto | Estrarre un singolo valore da una stringa arbitraria usando un'espressione regolare. L'uso dell'estrazione offre prestazioni migliori rispetto all'analisi o alla extract_all se è necessario un singolo valore. Tuttavia, l'uso di più attivazioni dell'estrazione sulla stessa stringa di origine è meno efficiente di un singolo parse o extract_all e deve essere evitato. |
| parse_json | Analizzare i valori in una stringa formattata come JSON. Se sono necessari solo alcuni valori dal codice JSON, l'uso di analisi, estrazione o extract_all offre prestazioni migliori. |
| parse_xml | Analizzare i valori in una stringa formattata come XML. Se sono necessari solo alcuni valori dal codice XML, l'uso di analisi, estrazione o extract_all offre prestazioni migliori. |
Oltre all'analisi della stringa, la fase di analisi può richiedere un'elaborazione maggiore dei valori originali, tra cui:
Formattazione e conversione dei tipi. Il campo di origine, una volta estratto, potrebbe essere necessario formattare per adattarsi al campo dello schema di destinazione. Ad esempio, potrebbe essere necessario convertire una stringa che rappresenta data e ora in un campo datetime. Le funzioni come todatetime e tohex sono utili in questi casi.
Ricerca di valori. Potrebbe essere necessario eseguire il mapping del valore del campo di origine, una volta estratto, al set di valori specificati per il campo dello schema di destinazione. Ad esempio, alcune origini segnalano codici di risposta DNS numerici, mentre lo schema impone i codici di risposta di testo più comuni. Le funzioni iff e case possono essere utili per eseguire il mapping di alcuni valori.
Ad esempio, il parser DNS Microsoft assegna il campo EventResult in base all'ID evento e al codice di risposta usando un'istruzione iff, come indicato di seguito:
extend EventResult = iff(EventId==257 and ResponseCode==0 ,'Success','Failure')Per diversi valori, usare la tabella dati e la ricerca, come illustrato nello stesso parser DNS:
let RCodeTable = datatable(ResponseCode:int,ResponseCodeName:string) [ 0, 'NOERROR', 1, 'FORMERR'....]; ... | lookup RCodeTable on ResponseCode | extend EventResultDetails = case ( isnotempty(ResponseCodeName), ResponseCodeName, ResponseCode between (3841 .. 4095), 'Reserved for Private Use', 'Unassigned')
Mapping dei valori
In molti casi, il valore originale estratto deve essere normalizzato. Ad esempio, in ASIM un indirizzo MAC usa i due punti come separatore, mentre l'origine può inviare un indirizzo MAC delimitato da trattini. L'operatore primario per la trasformazione dei valori è esteso, insieme a un ampio set di funzioni stringa KQL, numeriche e date, come illustrato nella sezione Analisi precedente.
Casi d'uso, iff e istruzioni di ricerca quando è necessario eseguire il mapping di un set di valori ai valori consentiti dal campo di destinazione.
Quando ogni valore di origine viene mappato a un valore di destinazione, definire il mapping usando l'operatore datatable e la ricerca per eseguire il mapping. Per esempio
let NetworkProtocolLookup = datatable(Proto:real, NetworkProtocol:string)[
6, 'TCP',
17, 'UDP'
];
let DnsResponseCodeLookup=datatable(DnsResponseCode:int,DnsResponseCodeName:string)[
0,'NOERROR',
1,'FORMERR',
2,'SERVFAIL',
3,'NXDOMAIN',
...
];
...
| lookup DnsResponseCodeLookup on DnsResponseCode
| lookup NetworkProtocolLookup on Proto
Si noti che lookup è utile ed efficiente anche quando il mapping ha solo due valori possibili.
Quando le condizioni di mapping sono più complesse, usare le funzioni iff o case . La funzione iff abilita il mapping di due valori:
| extend EventResult =
iff(EventId==257 and ResponseCode==0,'Success','Failure’)
La funzione case supporta più di due valori di destinazione. L'esempio seguente illustra come combinare la ricerca e il case. L'esempio di ricerca precedente restituisce un valore vuoto nel campo DnsResponseCodeName se il valore di ricerca non viene trovato. L'esempio di caso seguente lo aumenta usando il risultato dell'operazione di ricerca , se disponibile, e specificando condizioni aggiuntive in caso contrario.
| extend DnsResponseCodeName =
case (
DnsResponseCodeName != "", DnsResponseCodeName,
DnsResponseCode between (3841 .. 4095), 'Reserved for Private Use',
'Unassigned'
)
Preparare i campi nel set di risultati
Il parser deve preparare i campi nel set di risultati per assicurarsi che vengano usati i campi normalizzati.
Gli operatori KQL seguenti vengono usati per preparare i campi nel set di risultati:
| Operatore | Descrizione | Quando usarlo in un parser |
|---|---|---|
| project-rename | Rinomina i campi. | Se esiste un campo nell'evento effettivo e deve essere rinominato solo, usare project-rename. Il campo rinominato si comporta ancora come un campo predefinito e le operazioni sul campo hanno prestazioni molto migliori. |
| progetto lontano | Rimuove i campi. | Usare project-away per campi specifici da rimuovere dal set di risultati. È consigliabile non rimuovere i campi originali non normalizzati dal set di risultati, a meno che non creino confusione o siano molto grandi e potrebbero avere implicazioni sulle prestazioni. |
| progetto | Seleziona i campi già esistenti o creati come parte dell'istruzione e rimuove tutti gli altri campi. | Non consigliato per l'uso in un parser, perché il parser non deve rimuovere altri campi non normalizzati. Se è necessario rimuovere campi specifici, ad esempio i valori temporanei usati durante l'analisi, usare project-away per rimuoverli dai risultati. |
| estendere | Aggiungere alias. | Oltre al suo ruolo nella generazione di campi calcolati, l'operatore extend viene usato anche per creare alias. |
Gestire le varianti di analisi
In molti casi, gli eventi in un flusso di eventi includono varianti che richiedono una logica di analisi diversa. Per analizzare varianti diverse in un singolo parser, usare istruzioni condizionali come iff e case oppure usare una struttura di unione.
Per usare l'unione per gestire più varianti, creare una funzione separata per ogni variante e usare l'istruzione union per combinare i risultati:
let AzureFirewallNetworkRuleLogs = AzureDiagnostics
| where Category == "AzureFirewallNetworkRule"
| where isnotempty(msg_s);
let parseLogs = AzureFirewallNetworkRuleLogs
| where msg_s has_any("TCP", "UDP")
| parse-where
msg_s with networkProtocol:string
" request from " srcIpAddr:string
":" srcPortNumber:int
…
| project-away msg_s;
let parseLogsWithUrls = AzureFirewallNetworkRuleLogs
| where msg_s has_all ("Url:","ThreatIntel:")
| parse-where
msg_s with networkProtocol:string
" request from " srcIpAddr:string
" to " dstIpAddr:string
…
union parseLogs, parseLogsWithUrls…
Per evitare eventi duplicati ed elaborazione eccessiva, assicurarsi che ogni funzione venga avviata filtrando, usando campi nativi, solo gli eventi da analizzare. Inoltre, se necessario, usare project-away in ogni ramo, prima dell'unione.
Distribuire i parser
Distribuire manualmente i parser copiandoli nella pagina Log di Monitoraggio di Azure e salvando la query come funzione. Questo metodo è utile per i test. Per altre informazioni, vedere Creare una funzione.
Per distribuire un numero elevato di parser, è consigliabile usare i modelli di ARM del parser, come indicato di seguito:
Creare un file YAML basato sul modello rilevante per ogni schema e includervi la query. Iniziare con il modello YAML pertinente per lo schema e il tipo di parser, il filtro o meno parametri.
Usare il convertitore di modelli yaml di ASIM per ARM per convertire il file YAML in un modello arm.
Se si distribuisce un aggiornamento, eliminare le versioni precedenti delle funzioni usando il portale o lo strumento powerShell per eliminare la funzione.
Distribuire il modello usando il portale di Azure o PowerShell.
È anche possibile combinare più modelli a un singolo processo di distribuzione usando modelli collegati.