Skapa en ASIM-parser

Slutförd

ASIM-användare (Advanced Security Information Model) använder enande parsers i stället för tabellnamn i sina frågor, för att visa data i ett normaliserat format och för att inkludera alla data som är relevanta för schemat i frågan. Enande parsers använder i sin tur källspecifika parsers för att hantera den specifika informationen för varje källa.

Microsoft Sentinel tillhandahåller inbyggda, källspecifika parsare för många datakällor. Du kanske vill ändra eller utveckla dessa källspecifika parsers i följande situationer:

När enheten tillhandahåller händelser som passar ett ASIM-schema, men en källspecifik parser för enheten och det relevanta schemat inte är tillgängligt i Microsoft Sentinel.

När ASIM-källspecifika parsers är tillgängliga för din enhet, men enheten skickar händelser i en metod eller ett annat format än förväntat av ASIM-parsarna. Till exempel:

Källenheten kan konfigureras för att skicka händelser på ett sätt som inte är standard.

Enheten kan ha en annan version än den som stöds av ASIM-parsern.

Händelserna kan samlas in, ändras och vidarebefordras av ett mellanliggande system.

Utvecklingsprocess för anpassad parser

Följande arbetsflöde beskriver de övergripande stegen i utvecklingen av en anpassad ASIM- och källspecifik parser:

  1. Samla in exempelloggar.

  2. Identifiera de scheman eller scheman som händelserna som skickas från källan representerar.

  3. Mappa källhändelsefälten till det identifierade schemat eller scheman.

  4. Utveckla en eller flera ASIM-parsers för din källa. Du måste utveckla en parser för filtrering och en parameterlös parser för varje schema som är relevant för källan.

  5. Testa parsern.

  6. Distribuera parsarna till dina Microsoft Sentinel-arbetsytor.

  7. Uppdatera den relevanta ASIM-enande parsern för att referera till den nya anpassade parsern.

  8. Du kanske också vill bidra med dina parsers till den primära ASIM-fördelningen. Bidragna parsare kan också göras tillgängliga på alla arbetsytor som inbyggda parsers.

Samla in exempelloggar

För att skapa effektiva ASIM-parsers behöver du en representativ uppsättning loggar, som i de flesta fall kräver att källsystemet konfigureras och ansluts till Microsoft Sentinel. Om du inte har källenheten tillgänglig kan du distribuera många enheter för utveckling och testning med molnbaserade betala per användning-tjänster.

Dessutom kan du hitta leverantörsdokumentationen och exemplen för loggarna för att påskynda utvecklingen och minska misstagen genom att säkerställa bred loggformattäckning.

En representativ uppsättning loggar bör innehålla:

  • Händelser med olika händelseresultat.
  • Händelser med olika svarsåtgärder.
  • Olika format för användarnamn, värdnamn och ID och andra fält som kräver värdenormalisering.

Mappning

Innan du utvecklar en parser mappar du den information som är tillgänglig i källhändelsen eller händelser till det schema som du identifierade:

  • Mappa alla obligatoriska fält och helst även rekommenderade fält.
  • Försök mappa all information som är tillgänglig från källan till normaliserade fält. Om det inte är tillgängligt som en del av det valda schemat bör du överväga att mappa till fält som är tillgängliga i andra scheman.
  • Mappa värden för fält vid källan till de normaliserade värden som tillåts av ASIM. Det ursprungliga värdet lagras i ett separat fält, till exempel EventOriginalResultDetails.

Utveckla parsers

Utveckla både en filtrering och en parameterlös parser för varje relevant schema.

En anpassad parser är en KQL-fråga som utvecklats på sidan Microsoft Sentinel-loggar. Parser-frågan har tre delar:

Filterparse > > Förbered fält

Filtrera relevanta poster

I många fall innehåller en tabell i Microsoft Sentinel flera typer av händelser. Till exempel:

  • Syslog-tabellen innehåller data från flera källor.
  • Anpassade tabeller kan innehålla information från en enda källa som tillhandahåller mer än en händelsetyp och som kan passa olika scheman.

Därför bör en parser först filtrera endast de poster som är relevanta för målschemat.

Filtrering i KQL görs med hjälp av operatorn where . Till exempel bearbetar Sysmon händelse 1 skapande av rapporter och normaliseras därför till ProcessEvent-schemat . Händelsen Sysmon event 1 är en del av tabellen Händelse , så du använder följande filter:

Event | where Source == "Microsoft-Windows-Sysmon" and EventID == 1

Viktigt!

En parser bör inte filtrera efter tid. Frågan som använder parsern tillämpar ett tidsintervall.

Filtrera efter källtyp med hjälp av en bevakningslista

I vissa fall innehåller själva händelsen inte information som skulle tillåta filtrering för specifika källtyper.

Till exempel skickas Infoblox DNS-händelser som Syslog-meddelanden och är svåra att skilja från Syslog-meddelanden som skickas från andra källor. I sådana fall förlitar sig parsern på en lista över källor som definierar relevanta händelser. Den här listan finns i ASimSourceType-bevakningslistan.

Så här använder du ASimSourceType-visningslistan i dina parsers:

  • Ta med följande rad i början av parsern:
let Sources_by_SourceType=(sourcetype:string){_GetWatchlist('ASimSourceType') | where SearchKey == tostring(sourcetype) | extend Source=column_ifexists('Source','') | where isnotempty(Source)| distinct Source };
  • Lägg till ett filter som använder visningslistan i avsnittet parserfiltrering. Till exempel innehåller Infoblox DNS-parsern följande i filtreringsavsnittet:
| where Computer in (Sources_by_SourceType('InfobloxNIOS'))

Så här använder du det här exemplet i parsern:

  • Ersätt Dator med namnet på det fält som innehåller källinformationen för din källa. Du kan behålla detta som dator för alla parsare baserat på Syslog.

  • Ersätt InfobloxNIOS-token med ett valfritt värde för parsern. Informera parser-användare om att de måste uppdatera ASimSourceType-visningslistan med ditt valda värde och listan över källor som skickar händelser av den här typen.

Filtrering baserat på parserparametrar

När du utvecklar filtreringsparsers kontrollerar du att parsern accepterar filtreringsparametrarna för det relevanta schemat, enligt beskrivningen i referensartikeln för det schemat. Om du använder en befintlig parser som utgångspunkt ser du till att parsern innehåller rätt funktionssignatur. I de flesta fall är den faktiska filtreringskoden också liknande för filtrering av parsare för samma schema.

När du filtrerar kontrollerar du att du:

  • Filtrera innan du parsar med hjälp av fysiska fält. Om de filtrerade resultaten inte är tillräckligt exakta upprepar du testet efter parsningen för att finjustera resultatet. Mer information finns i filtreringsoptimering.
  • Filtrera inte om parametern inte har definierats och fortfarande har standardvärdet.

I följande exempel visas hur du implementerar filtrering för en strängparameter, där standardvärdet vanligtvis är *, och för en listparameter, där standardvärdet vanligtvis är en tom lista.

srcipaddr=='*' or ClientIP==srcipaddr
array_length(domain_has_any) == 0 or Name has_any (domain_has_any)

Filtreringsoptimering

Observera följande filtreringsrekommendationer för att säkerställa parserns prestanda:

  • Filtrera alltid på inbyggda fält i stället för parsade fält. Även om det ibland är enklare att filtrera med hjälp av parsade fält, påverkar det prestanda avsevärt.
  • Använd operatorer som ger optimerad prestanda. I synnerhet ==, har och startarmed. Användning av operatorer som innehåller eller matchar regex påverkar också prestanda avsevärt.

Filtreringsrekommendationer för prestanda kanske inte alltid är lätta att följa. Att till exempel använda har är mindre exakt än contains. I andra fall är matchning av det inbyggda fältet, till exempel SyslogMessage, mindre exakt än att jämföra ett extraherat fält, till exempel DvcAction. I sådana fall rekommenderar vi att du fortfarande förfiltrerar med hjälp av en prestandaoptimeringsoperator över ett inbyggt fält och upprepar filtret med mer exakta villkor efter parsning.

Ett exempel finns i följande Infoblox DNS-parserfragment. Parsern kontrollerar först att fältet SyslogMessage har ordet klient. Termen kan dock användas på en annan plats i meddelandet, så efter att ha parsat fältet Log_Type kontrollerar parsern igen att ordet klient verkligen var fältets värde.

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

Parsning

När frågan har valt relevanta poster kan den behöva parsa dem. Vanligtvis krävs parsning om flera händelsefält förmedlas i ett enda textfält.

De KQL-operatorer som utför parsning visas nedan, ordnade efter deras prestandaoptimering. Den första ger den mest optimerade prestandan, medan den sista ger minst optimerad prestanda.

Operatör beskrivning
dela Parsa en sträng med avgränsade värden.
parse_csv Parsa en sträng med värden som är formaterade som en CSV-rad (kommaavgränsade värden).
Tolka Parsa flera värden från en godtycklig sträng med hjälp av ett mönster, vilket kan vara ett förenklat mönster med bättre prestanda eller ett reguljärt uttryck.
extract_all Parsa enskilda värden från en godtycklig sträng med ett reguljärt uttryck. extract_all har en liknande prestanda som parsa om den senare använder ett reguljärt uttryck.
Extrahera Extrahera ett enda värde från en godtycklig sträng med ett reguljärt uttryck. Med extrahering får du bättre prestanda än parsning eller extract_all om ett enda värde behövs. Att använda flera aktiveringar av extrahering över samma källsträng är dock mindre effektivt än en enskild pars eller extract_all och bör undvikas.
parse_json Parsa värdena i en sträng formaterad som JSON. Om bara några få värden behövs från JSON ger användning av parsa, extrahera eller extract_all bättre prestanda.
parse_xml Parsa värdena i en sträng som är formaterad som XML. Om det bara behövs några få värden från XML-koden ger parsning, extrahering eller extract_all bättre prestanda.

Förutom att parsa strängen kan parsningsfasen kräva mer bearbetning av de ursprungliga värdena, inklusive:

  • Formatering och typkonvertering. Källfältet, när det har extraherats, kan behöva formateras för att passa målschemafältet. Du kan till exempel behöva konvertera en sträng som representerar datum och tid till ett datetime-fält. Funktioner som todatetime och tohex är användbara i dessa fall.

  • Värdesökning. Värdet för källfältet, när det har extraherats, kan behöva mappas till den uppsättning värden som angetts för målschemafältet. Vissa källor rapporterar till exempel numeriska DNS-svarskoder, medan schemat kräver vanligare textsvarskoder. Funktions-iff och skiftläge kan vara till hjälp för att mappa några värden.

    Till exempel tilldelar Microsoft DNS-parsern fältet EventResult baserat på händelse-ID och svarskod med hjälp av en iff-instruktion enligt följande:

    extend EventResult = iff(EventId==257 and ResponseCode==0 ,'Success','Failure')
    

    För flera värden använder du datatabell och sökning, vilket visas i samma DNS-parser:

    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')
    

Mappa värden

I många fall måste det ursprungliga värdet som extraheras normaliseras. I ASIM använder till exempel en MAC-adress kolon som avgränsare, medan källan kan skicka en avstavad MAC-adress. Den primära operatorn för att transformera värden utökas, tillsammans med en bred uppsättning KQL-sträng-, numeriska och datumfunktioner, vilket visas i avsnittet Parsning ovan.

Användningsfall, iff och uppslagsuttryck när det finns ett behov av att mappa en uppsättning värden till de värden som tillåts av målfältet.

När varje källvärde mappas till ett målvärde definierar du mappningen med hjälp av datatabelloperatorn och sökningen för att mappa. Till exempel

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

Observera att uppslag är användbart och effektivt även när mappningen bara har två möjliga värden.

När mappningsvillkoren är mer komplexa använder du iff- eller skiftlägesfunktionerna. Funktionen iff möjliggör mappning av två värden:

| extend EventResult = 
      iff(EventId==257 and ResponseCode==0,'Success','Failure’)

Ärendefunktionen stöder fler än två målvärden. Exemplet nedan visar hur du kombinerar uppslag och skiftläge. Uppslagsexemplet ovan returnerar ett tomt värde i fältet DnsResponseCodeName om uppslagsvärdet inte hittas. Exempelexemplet nedan utökar det genom att använda resultatet av uppslagsåtgärden om det är tillgängligt och ange ytterligare villkor i annat fall.

| extend DnsResponseCodeName = 
      case (
        DnsResponseCodeName != "", DnsResponseCodeName,
        DnsResponseCode between (3841 .. 4095), 'Reserved for Private Use',
        'Unassigned'
      )

Förbereda fält i resultatuppsättningen

Parsern måste förbereda fälten i resultatuppsättningen för att säkerställa att de normaliserade fälten används.

Följande KQL-operatorer används för att förbereda fält i resultatuppsättningen:

Operatör beskrivning När du ska använda i en parser
project-rename Byter namn på fält. Om det finns ett fält i den faktiska händelsen och bara behöver byta namn använder du project-rename. Det omdöpta fältet fungerar fortfarande som ett inbyggt fält, och åtgärder på fältet har mycket bättre prestanda.
project-away Tar bort fält. Använd project-away för specifika fält som du vill ta bort från resultatuppsättningen. Vi rekommenderar att du inte tar bort de ursprungliga fälten som inte normaliseras från resultatuppsättningen, såvida de inte skapar förvirring eller är mycket stora och kan få prestandakonsekvenser.
projekt Markerar fält som fanns tidigare eller skapades som en del av -instruktionen och tar bort alla andra fält. Rekommenderas inte för användning i en parser eftersom parsern inte bör ta bort andra fält som inte är normaliserade. Om du behöver ta bort specifika fält, till exempel tillfälliga värden som används under parsningen, använder du project-away för att ta bort dem från resultaten.
Utöka Lägg till alias. Förutom sin roll när det gäller att generera beräknade fält används utökaroperatorn också för att skapa alias.

Hantera parsningsvarianter

I många fall innehåller händelser i en händelseström varianter som kräver olika parsningslogik. Om du vill parsa olika varianter i en enskild parser använder du antingen villkorssatser som iff och case eller använder en unionsstruktur.

Om du vill använda union för att hantera flera varianter skapar du en separat funktion för varje variant och använder union-instruktionen för att kombinera resultaten:

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…

För att undvika duplicerade händelser och överdriven bearbetning kontrollerar du att varje funktion startar genom att filtrera, med hjälp av interna fält, endast de händelser som den är avsedd att parsa. Om det behövs kan du också använda project-away på varje gren, före unionen.

Distribuera parsers

Distribuera parsers manuellt genom att kopiera dem till Azure Monitor-loggsidan och spara frågan som en funktion. Den här metoden är användbar för testning. Mer information finns i Skapa en funktion.

Om du vill distribuera ett stort antal parsers rekommenderar vi att du använder PARSER ARM-mallar på följande sätt:

  1. Skapa en YAML-fil baserat på relevant mall för varje schema och inkludera din fråga i den. Börja med YAML-mallen som är relevant för ditt schema och parsertyp, filtrering eller parameterlös.

  2. Använd ASIM Yaml till ARM-mallkonverteraren för att konvertera YAML-filen till en ARM-mall.

  3. Om du distribuerar en uppdatering tar du bort äldre versioner av funktionerna med hjälp av portalen eller funktionen ta bort PowerShell-verktyget.

  4. Distribuera mallen med hjälp av Azure-portalen eller PowerShell.

Du kan också kombinera flera mallar till en enda distributionsprocess med hjälp av länkade mallar.