Creare un parser ASIM

Completato

Gli utenti ASIM (Advanced Security Information Model) usano parser di unificazione anziché nomi di tabella nelle query per visualizzare i dati in un formato normalizzato e includere tutti i dati rilevanti per lo schema nella query. I parser di unificazione, a loro volta, usano parser specifici dell'origine per gestire i dettagli specifici di ogni origine.

Microsoft Sentinel offre parser predefiniti specifici dell'origine per molte origini dati. Si potrebbe voler modificare o sviluppare questi parser specifici dell'origine nelle situazioni seguenti:

Quando il dispositivo fornisce eventi che si adattano a uno schema ASIM, ma un parser specifico dell'origine per il dispositivo e lo schema rilevante 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. Ad 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:

  1. Raccogliere log di esempio.

  2. Identificare lo schema o gli schemi rappresentati dagli eventi inviati dall'origine.

  3. Eseguire il mapping dei campi degli eventi di origine allo schema o agli schemi identificati.

  4. 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.

  5. Testare il parser.

  6. Distribuire i parser nelle aree di lavoro di Microsoft Sentinel.

  7. Aggiornare il parser di unificazione ASIM rilevante per fare riferimento al nuovo parser personalizzato.

  8. Si potrebbe anche voler contribuire con i propri parser alla distribuzione ASIM primaria. 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 non si dispone del dispositivo di origine, i servizi cloud con pagamento in base al consumo consentono di distribuire molti dispositivi per lo sviluppo e il 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.

Mapping

Prima di sviluppare un parser, eseguire il mapping delle informazioni disponibili nell'evento o negli eventi di origine 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 sono disponibili nello schema selezionato, provare a eseguire 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 > Analizzare > Preparare i campi

Filtro dei record rilevanti

In molti casi, una tabella di Microsoft Sentinel include più tipi di eventi. Ad 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 applicato 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 Eventi, 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 nella watchlist ASimSourceType.

Per usare la watchlist 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 la watchlist 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 sull'origine. È possibile mantenere Computer per qualsiasi parser basato su Syslog.

  • Sostituire il token InfobloxNIOS con il valore desiderato per il parser. Informare gli utenti del parser che devono aggiornare la watchlist 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 rilevante, 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 dei filtri.
  • Non filtrare se il parametro non è definito e ha ancora 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, ciò influisce notevolmente sulle prestazioni.
  • Usare gli operatori che offrono prestazioni ottimizzate. In particolare, ==, has e startswith. Anche l'uso di operatori come l'espressione regolare contains o matches ha un impatto notevole 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 contains. In altri casi, la corrispondenza con il campo predefinito, come SyslogMessage, è meno accurata rispetto al confronto con un campo estratto, come 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 contenga la parola client. Tuttavia, il termine potrebbe essere usato in un altro punto del messaggio, quindi dopo aver analizzato il campo Log_Type, il parser controlla nuovamente che la parola client sia effettivamente il valore del campo.

Syslog | where ProcessName == "named" and SyslogMessage has "client"
…
      | extend Log_Type = tostring(Parser[1]),
      | where Log_Type == "client"

Analisi in corso

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
split 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 ha prestazioni simili a parse se quest'ultimo usa un'espressione regolare.
extract Estrarre un singolo valore da una stringa arbitraria usando un'espressione regolare. L'uso di extract fornisce prestazioni migliori rispetto a parse o extract_all se è necessario un singolo valore. Tuttavia, l'uso di più attivazioni di extract sulla stessa stringa di origine è meno efficiente di un singolo uso di 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 parse, extract 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 parse, extract o extract_all offre prestazioni migliori.

Oltre all'analisi della stringa, la fase di analisi può richiedere un'ulteriore elaborazione dei valori originali, tra cui:

  • Formattazione e conversione dei tipi. Il campo di origine, una volta estratto, potrebbe dover essere formattato 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. In questi casi sono utili funzioni come todatetime e tohex.

  • Valore lookup. 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 riportano codici di risposta DNS numerici, mentre lo schema richiede 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 datatable e lookup, 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 principale per la trasformazione dei valori è extend, insieme a un ampio set di funzioni stringa KQL, numerica e data, come illustrato nella sezione Analisi precedente.

Usare le istruzioni case, iff e lookup quando è necessario eseguire il mapping di un set di valori ai valori consentiti dal campo di destinazione.

Quando ogni valore di origine esegue il mapping a un valore di destinazione, definire il mapping usando l'operatore datatable e lookup. Ad 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 consente 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 mostra come combinare lookup e case. L'esempio di lookup precedente restituisce un valore vuoto nel campo DnsResponseCodeName se il valore lookup non viene trovato. L'esempio di case che segue lo amplia usando il risultato dell'operazione lookup, se disponibile, e in caso contrario specificando altre condizioni.

| 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.

Per preparare i campi nel set di risultati vengono usati gli operatori KQL seguenti:

Operatore Descrizione Quando usarlo in un parser
project-rename Rinomina i campi. Se nell'evento effettivo esiste un campo che deve essere solo rinominato, usare project-rename. Il campo rinominato si comporta ancora come un campo predefinito e le operazioni sul campo hanno prestazioni molto migliori.
project-away Rimuove i campi. Usare project-away per campi specifici che si desidera 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, come i valori temporanei usati durante l'analisi, usare project-away per rimuoverli dai risultati.
extend Aggiungere alias. Oltre al 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 le istruzioni condizionali come iff e case oppure usare una struttura di unione.

Per usare union 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 e un'elaborazione eccessiva, assicurarsi che ogni funzione inizi filtrando, tramite 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:

  1. Creare un file YAML basato sul modello rilevante per ogni schema e includervi la query. Iniziare con il modello YAML rilevante per lo schema e il tipo di parser, di filtro o senza parametri.

  2. Usare il convertitore di modelli ASIM da Yaml a ARM per convertire il file YAML in un modello di ARM.

  3. Se si distribuisce un aggiornamento, eliminare le versioni precedenti delle funzioni usando il portale o lo strumento PowerShell di eliminazione delle funzioni.

  4. Distribuire il modello usando il portale di Azure o PowerShell.

È anche possibile combinare più modelli in un singolo processo di distribuzione usando modelli collegati.