Linguagem de consulta Kusto no Microsoft Sentinel

Kusto Query Language é a linguagem que você usará para trabalhar e manipular dados no Microsoft Sentinel. Os logs que você alimenta em seu espaço de trabalho não valem muito se você não puder analisá-los e obter as informações importantes ocultas em todos esses dados. Kusto Query Language não tem apenas o poder e a flexibilidade para obter essas informações, mas a simplicidade para ajudá-lo a começar rapidamente. Se você tem experiência em scripts ou trabalha com bancos de dados, muito do conteúdo deste artigo será muito familiar. Se não, não se preocupe, pois a natureza intuitiva da linguagem rapidamente permite que você comece a escrever suas próprias consultas e gerar valor para sua organização.

Este artigo apresenta os conceitos básicos da Kusto Query Language, cobrindo algumas das funções e operadores mais usados, que devem abordar 75 a 80% das consultas que você escreverá no dia a dia. Quando precisar de mais profundidade ou executar consultas mais avançadas, aproveite a nova pasta de trabalho Advanced KQL for Microsoft Sentinel (consulte esta postagem introdutória no blog). Veja também a documentação oficial da Kusto Query Language, bem como uma variedade de cursos on-line (como o da Pluralsight).

Background - Porquê Kusto Query Language?

O Microsoft Sentinel foi criado sobre o serviço Azure Monitor e usa os espaços de trabalho do Log Analytics do Azure Monitor para armazenar todos os seus dados. Estes dados incluem qualquer um dos seguintes:

  • dados ingeridos de fontes externas em tabelas predefinidas usando conectores de dados do Microsoft Sentinel.
  • dados ingeridos de fontes externas em tabelas personalizadas definidas pelo usuário, usando conectores de dados criados sob medida, bem como alguns tipos de conectores prontos para uso.
  • dados criados pelo próprio Microsoft Sentinel, resultantes das análises que cria e realiza - por exemplo, alertas, incidentes e informações relacionadas com a UEBA.
  • dados carregados no Microsoft Sentinel para ajudar na deteção e análise - por exemplo, feeds de inteligência de ameaças e listas de observação.

A Kusto Query Language foi desenvolvida como parte do serviço Azure Data Explorer e, portanto, é otimizada para pesquisar em armazenamentos de big data em um ambiente de nuvem. Inspirado pelo famoso explorador submarino Jacques Cousteau (e pronunciado de acordo com "koo-STOH"), foi projetado para ajudá-lo a mergulhar profundamente em seus oceanos de dados e explorar seus tesouros escondidos.

A Linguagem de Consulta Kusto também é usada no Azure Monitor (e, portanto, no Microsoft Sentinel), incluindo alguns recursos adicionais do Azure Monitor, que permitem recuperar, visualizar, analisar e analisar dados em armazenamentos de dados do Log Analytics. No Microsoft Sentinel, você está usando ferramentas baseadas na Kusto Query Language sempre que estiver visualizando e analisando dados e procurando ameaças, seja em regras e pastas de trabalho existentes ou na criação de suas próprias.

Como o Kusto Query Language faz parte de quase tudo o que você faz no Microsoft Sentinel, uma compreensão clara de como ele funciona ajuda você a tirar muito mais proveito do seu SIEM.

O que é uma consulta?

Uma consulta Kusto Query Language é uma solicitação somente leitura para processar dados e retornar resultados – ela não grava nenhum dado. As consultas operam em dados organizados em uma hierarquia de bancos de dados, tabelas e colunas, semelhante ao SQL.

As solicitações são declaradas em linguagem simples e usam um modelo de fluxo de dados projetado para tornar a sintaxe fácil de ler, gravar e automatizar. Veremos isso em detalhes.

As consultas Kusto Query Language são compostas por instruções separadas por ponto-e-vírgula. Existem muitos tipos de declarações, mas apenas dois tipos amplamente utilizados que discutiremos aqui:

  • Instruções de expressão tabular são o que normalmente queremos dizer quando falamos sobre consultas – estas são o corpo real da consulta. A coisa importante a saber sobre instruções de expressão tabular é que elas aceitam uma entrada tabular (uma tabela ou outra expressão tabular) e produzem uma saída tabular. Pelo menos um deles é necessário. A maior parte do restante deste artigo discutirá esse tipo de declaração.

  • As instruções let permitem criar e definir variáveis e constantes fora do corpo da consulta, para facilitar a legibilidade e a versatilidade. Estes são opcionais e dependem das suas necessidades específicas. Abordaremos esse tipo de afirmação no final do artigo.

Ambiente de demonstração

Você pode praticar instruções Kusto Query Language - incluindo as deste artigo - em um ambiente de demonstração do Log Analytics no portal do Azure. Não há cobrança para usar esse ambiente de prática, mas você precisa de uma conta do Azure para acessá-lo.

Explore o ambiente de demonstração. Como o Log Analytics em seu ambiente de produção, ele pode ser usado de várias maneiras:

  • Escolha uma tabela na qual criar uma consulta. Na guia Tabelas padrão (mostrada no retângulo vermelho no canto superior esquerdo), selecione uma tabela na lista de tabelas agrupadas por tópicos (mostrada no canto inferior esquerdo). Expanda os tópicos para ver as tabelas individuais e você pode expandir ainda mais cada tabela para ver todos os seus campos (colunas). Clicar duas vezes em uma tabela ou no nome de um campo o colocará no ponto do cursor na janela de consulta. Digite o restante da consulta seguindo o nome da tabela, conforme indicado abaixo.

  • Encontre uma consulta existente para estudar ou modificar. Selecione a guia Consultas (mostrada no retângulo vermelho no canto superior esquerdo) para ver uma lista de consultas disponíveis prontamente. Ou selecione Consultas na barra de botões no canto superior direito. Você pode explorar as consultas que vêm com o Microsoft Sentinel pronto para uso. Clicar duas vezes em uma consulta colocará toda a consulta na janela de consulta no ponto do cursor.

    Mostra o ambiente de demonstração do Log Analytics.

Como neste ambiente de demonstração, você pode consultar e filtrar dados na página Logs do Microsoft Sentinel. Você pode selecionar uma tabela e detalhar para ver as colunas. Você pode modificar as colunas padrão mostradas usando o seletor de colunas e pode definir o intervalo de tempo padrão para consultas. Se o intervalo de tempo for explicitamente definido na consulta, o filtro de tempo ficará indisponível (acinzentado).

Estrutura da consulta

Um bom lugar para começar a aprender Kusto Query Language é entender a estrutura geral da consulta. A primeira coisa que você notará ao olhar para uma consulta Kusto é o uso do símbolo pipe (|). A estrutura de uma consulta Kusto começa com a obtenção de seus dados de uma fonte de dados e, em seguida, passando os dados através de um "pipeline", e cada etapa fornece algum nível de processamento e, em seguida, passa os dados para a próxima etapa. No final do pipeline, você obterá seu resultado final. Na verdade, este é o nosso pipeline:

Get Data | Filter | Summarize | Sort | Select

Esse conceito de passar dados pelo pipeline cria uma estrutura muito intuitiva, pois é fácil criar uma imagem mental de seus dados em cada etapa.

Para ilustrar isso, vamos dar uma olhada na consulta a seguir, que examina os logs de entrada do Microsoft Entra. Ao ler cada linha, você pode ver as palavras-chave que indicam o que está acontecendo com os dados. Incluímos o estágio relevante no pipeline como um comentário em cada linha.

Nota

Você pode adicionar comentários a qualquer linha em uma consulta precedendo-os com uma barra dupla (//).

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

Como a saída de cada etapa serve como entrada para a etapa seguinte, a ordem das etapas pode determinar os resultados da consulta e afetar seu desempenho. É crucial que você ordene as etapas de acordo com o que deseja obter da consulta.

Gorjeta

  • Uma boa regra prática é filtrar seus dados antecipadamente, para que você esteja apenas passando dados relevantes pelo pipeline. Isso aumentará muito o desempenho e garantirá que você não inclua acidentalmente dados irrelevantes nas etapas de sumarização.
  • Este artigo irá apontar algumas outras práticas recomendadas a ter em mente. Para obter uma lista mais completa, consulte as práticas recomendadas de consulta.

Espero que agora você tenha uma apreciação pela estrutura geral de uma consulta no Kusto Query Language. Agora vamos examinar os próprios operadores de consulta, que são usados para criar uma consulta.

Tipos de dados

Antes de entrarmos nos operadores de consulta, vamos primeiro dar uma olhada rápida nos tipos de dados. Como na maioria dos idiomas, o tipo de dados determina quais cálculos e manipulações podem ser executados em relação a um valor. Por exemplo, se você tiver um valor do tipo string, não poderá executar cálculos aritméticos em relação a ele.

No Kusto Query Language, a maioria dos tipos de dados segue convenções padrão e tem nomes que você provavelmente já viu antes. A tabela a seguir mostra a lista completa:

Tabela de tipos de dados

Type Nomes adicionais 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

Embora a maioria dos tipos de dados seja padrão, você pode estar menos familiarizado com tipos como dinâmico, intervalo de tempo e guid.

O Dynamic tem uma estrutura muito semelhante ao JSON, mas com uma diferença fundamental: ele pode armazenar tipos de dados específicos da Kusto Query Language que o JSON tradicional não consegue, como um valor dinâmico aninhado ou um intervalo de tempo. Aqui está um exemplo de um tipo dinâmico:

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

Timespan é um tipo de dados que se refere a uma medida de tempo, como horas, dias ou segundos. Não confunda timespan com datetime, que avalia uma data e hora reais, não uma medida de tempo. A tabela a seguir mostra uma lista de sufixos de intervalo de tempo .

Sufixos Timespan

Function Description
D dias
H horas
M minutes
S segundos
Ms milissegundos
Microsecond microssegundos
Tick nanossegundos

Guid é um tipo de dados que representa um identificador global exclusivo de 128 bits, que segue o formato padrão de [8]-[4]-[4]-[4]-[12], onde cada [número] representa o número de caracteres e cada caractere pode variar de 0-9 ou a-f.

Nota

Kusto Query Language tem operadores tabulares e escalares. Ao longo do resto deste artigo, se você simplesmente vir a palavra "operador", você pode assumir que significa operador tabular, a menos que indicado de outra forma.

Obter, limitar, classificar e filtrar dados

O vocabulário central da Kusto Query Language - a base que lhe permitirá realizar a esmagadora maioria das suas tarefas - é uma coleção de operadores para filtrar, classificar e selecionar os seus dados. As tarefas restantes que você precisará fazer exigirão que você amplie seu conhecimento do idioma para atender às suas necessidades mais avançadas. Vamos expandir um pouco alguns dos comandos que usamos em nosso exemplo acima e olhar para take, sorte where.

Para cada um desses operadores, examinaremos seu uso em nosso exemplo anterior de SigninLogs e aprenderemos uma dica útil ou uma prática recomendada.

Obter dados

A primeira linha de qualquer consulta básica especifica com qual tabela você deseja trabalhar. No caso do Microsoft Sentinel, esse provavelmente será o nome de um tipo de log em seu espaço de trabalho, como SigninLogs, SecurityAlert ou CommonSecurityLog. Por exemplo:

SigninLogs

Observe que no Kusto Query Language, os nomes de log diferenciam maiúsculas de minúsculas, portanto SigninLogs , e signinLogs serão interpretados de forma diferente. Tenha cuidado ao escolher nomes para seus logs personalizados, para que eles sejam facilmente identificáveis e não muito semelhantes a outro log.

Limitação de dados: limite de tomada /

O operador take (e o operador limit idêntico) é usado para limitar seus resultados, retornando apenas um determinado número de linhas. Ele é seguido por um inteiro que especifica o número de linhas a serem retornadas. Normalmente, ele é usado no final de uma consulta depois de ter determinado sua ordem de classificação e, nesse caso, ele retornará o número determinado de linhas na parte superior da ordem classificada.

Usar take anteriormente na consulta pode ser útil para testar uma consulta, quando você não deseja retornar grandes conjuntos de dados. No entanto, se você colocar a take operação antes de qualquer sort operação, take retornará linhas selecionadas aleatoriamente - e possivelmente um conjunto diferente de linhas toda vez que a consulta for executada. Aqui está um exemplo de uso do take:

SigninLogs
      | take 5

Captura de tela dos resultados do operador take.

Gorjeta

Ao trabalhar em uma consulta totalmente nova, onde você pode não saber como será a consulta, pode ser útil colocar uma take instrução no início para limitar artificialmente seu conjunto de dados para processamento e experimentação mais rápidos. Quando estiver satisfeito com a consulta completa, você pode remover a etapa inicial take .

Classificando dados: ordem de classificação /

O operador de classificação (e o operador de ordem idêntico) é usado para classificar seus dados por uma coluna especificada. No exemplo a seguir, ordenamos os resultados por TimeGenerated e definimos a direção da ordem para descendente com o parâmetro desc , colocando os valores mais altos primeiro, para ordem crescente usaríamos asc.

Nota

A direção padrão para classificações é decrescente, portanto, tecnicamente, você só precisa especificar se deseja classificar em ordem crescente. No entanto, especificar a direção de classificação em qualquer caso tornará sua consulta mais legível.

SigninLogs
| sort by TimeGenerated desc
| take 5

Como mencionamos, colocamos o sort operador antes do take operador. Temos de ordenar primeiro para nos certificarmos de que obtemos os cinco registos adequados.

Captura de tela dos resultados do operador de classificação, com limite de tomada.

Início

O operador de topo permite-nos combinar as sort operações num take único operador:

SigninLogs
| top 5 by TimeGenerated desc

Nos casos em que dois ou mais registros têm o mesmo valor na coluna pela qual você está classificando, você pode adicionar mais colunas para classificar por. Adicione colunas de classificação extras em uma lista separada por vírgula, localizada após a primeira coluna de classificação, mas antes da palavra-chave de ordem de classificação. Por exemplo:

SigninLogs
| sort by TimeGenerated, Identity desc
| take 5

Agora, se TimeGenerated for o mesmo entre vários registros, ele tentará classificar pelo valor na coluna Identidade .

Nota

Quando usar sort e take, e quando usar top

  • Se você estiver classificando apenas em um campo, use top, pois ele oferece melhor desempenho do que a combinação de sort e take.

  • Se você precisar classificar em mais de um campo (como no último exemplo acima), top não pode fazer isso, então você deve usar sort e take.

Filtragem de dados: em que

O operador where é indiscutivelmente o operador mais importante, porque é a chave para garantir que você esteja trabalhando apenas com o subconjunto de dados que é relevante para o seu cenário. Você deve fazer o seu melhor para filtrar seus dados o mais cedo possível na consulta, porque isso melhorará o desempenho da consulta, reduzindo a quantidade de dados que precisam ser processados nas etapas subsequentes; Ele também garante que você esteja executando apenas cálculos nos dados desejados. Veja este exemplo:

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

O where operador especifica uma variável, um operador de comparação (escalar) e um valor. No nosso caso, costumávamos >= denotar que o valor na coluna TimeGenerated precisa ser maior que (ou seja, posterior a) ou igual a sete dias atrás.

Existem dois tipos de operadores de comparação na Kusto Query Language: string e numérico. A tabela a seguir mostra a lista completa de operadores numéricos:

Operadores numéricos

Operator Description
+ Adição
- Subtração
* Multiplicação
/ Divisão
% Módulo
< Menor que
> Maior que
== Igual a
!= Diferente de
<= Menor que ou igual a
>= Maior que ou igual a
in Igual a um dos elementos
!in Não igual a nenhum dos elementos

A lista de operadores de cadeia de caracteres é uma lista muito maior porque tem permutações para diferenciação de maiúsculas e minúsculas, locais de substring, prefixos, sufixos e muito mais. O == operador é um operador numérico e de cadeia de caracteres, o que significa que pode ser usado para números e texto. Por exemplo, ambas as instruções a seguir seriam válidas quando as instruções:

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

Gorjeta

Prática recomendada: na maioria dos casos, você provavelmente desejará filtrar seus dados por mais de uma coluna ou filtrar a mesma coluna de mais de uma maneira. Nesses casos, há duas práticas recomendadas que você deve ter em mente.

Você pode combinar várias where instruções em uma única etapa usando a palavra-chave e . Por exemplo:

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

Quando você tem vários filtros unidos em uma única where instrução usando a palavra-chave e , como acima, você obterá um melhor desempenho colocando filtros que fazem referência a uma única coluna primeiro. Assim, uma maneira melhor de escrever a consulta acima seria:

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

Neste exemplo, o primeiro filtro menciona uma única coluna (TimeGenerated), enquanto o segundo faz referência a duas colunas (Resource e ResourceGroup).

Resumindo dados

Summarize é um dos operadores tabulares mais importantes na Kusto Query Language, mas também é um dos operadores mais complexos para aprender se você é novo em linguagens de consulta em geral. O trabalho de é pegar uma tabela de dados e gerar uma nova tabela que é agregada summarize por uma ou mais colunas.

Estrutura da declaração resumida

A estrutura básica de uma summarize declaração é a seguinte:

| summarize <aggregation> by <column>

Por exemplo, o seguinte retornaria a contagem de registros para cada valor CounterName na tabela Perf :

Perf
| summarize count() by CounterName

Captura de tela dos resultados do operador de resumo com agregação de contagem.

Como a saída de summarize é uma nova tabela, quaisquer colunas não especificadas explicitamente na summarize instrução não serão passadas para o pipeline. Para ilustrar este conceito, considere este exemplo:

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

Na segunda linha, estamos especificando que nos preocupamos apenas com as colunas ObjectName, CounterValue e CounterName. Em seguida, resumimos para obter a contagem de registros por CounterName e, finalmente, tentamos classificar os dados em ordem crescente com base na coluna ObjectName . Infelizmente, essa consulta falhará com um erro (indicando que o ObjectName é desconhecido) porque, quando resumimos, incluímos apenas as colunas Count e CounterName em nossa nova tabela. Para evitar esse erro, podemos simplesmente adicionar ObjectName ao final da nossa summarize etapa, assim:

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

A maneira de ler a summarize linha em sua cabeça seria: "resumir a contagem de registros por CounterName e agrupar por ObjectName". Você pode continuar adicionando colunas, separadas por vírgulas, ao final da summarize instrução.

Captura de tela dos resultados do operador de resumo com dois argumentos.

Com base no exemplo anterior, se quisermos agregar várias colunas ao mesmo tempo, podemos conseguir isso adicionando agregações ao summarize operador, separadas por vírgulas. No exemplo abaixo, estamos obtendo não apenas uma contagem de todos os registros, mas também uma soma dos valores na coluna CounterValue em todos os registros (que correspondem a quaisquer filtros na consulta):

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

Captura de tela dos resultados do operador de resumo com várias agregações.

Renomeando colunas agregadas

Este parece ser um bom momento para falar sobre nomes de colunas para essas colunas agregadas. No início desta seção, dissemos que o summarize operador recebe uma tabela de dados e produz uma nova tabela, e apenas as colunas especificadas na summarize instrução continuarão no pipeline. Portanto, se você executasse o exemplo acima, as colunas resultantes para nossa agregação seriam count_ e sum_CounterValue.

O mecanismo Kusto criará automaticamente um nome de coluna sem que tenhamos que ser explícitos, mas muitas vezes, você descobrirá que prefere que sua nova coluna tenha um nome mais amigável. Você pode facilmente renomear sua coluna na summarize instrução especificando um novo nome, seguido por = e a agregação, assim:

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

Agora, nossas colunas resumidas serão chamadas de Contagem e ContraSoma.

Captura de tela de nomes de colunas amigáveis para agregações.

Há muito mais para o summarize operador do que podemos cobrir aqui, mas você deve investir tempo para aprendê-lo, porque é um componente chave para qualquer análise de dados que você planeja executar em seus dados do Microsoft Sentinel.

Referência de agregação

As são muitas funções de agregação, mas algumas das mais comumente usadas são sum(), count(), e avg(). Aqui está uma lista parcial (veja a lista completa):

Funções de agregação

Function Description
arg_max() Retorna uma ou mais expressões quando o argumento é maximizado
arg_min() Retorna uma ou mais expressões quando o argumento é minimizado
avg() Devolve o valor médio em todo o grupo
buildschema() Retorna o esquema mínimo que admite todos os valores da entrada dinâmica
count() Contagem de devoluções do grupo
countif() Os retornos contam com o predicado do grupo
dcount() Devolve uma contagem distinta aproximada dos elementos do grupo
make_bag() Retorna um conjunto de propriedades de valores dinâmicos dentro do grupo
make_list() Devolve uma lista de todos os valores dentro do grupo
make_set() Devolve um conjunto de valores distintos dentro do grupo
max() Devolve o valor máximo em todo o grupo
min() Devolve o valor mínimo em todo o grupo
percentiles() Devolve o percentil aproximado do grupo
stdev() Devolve o desvio padrão em todo o grupo
sum() Devolve a soma dos elementos dentro do grupo
take_any() Devolve um valor aleatório não vazio para o grupo
variance() Devolve a variância entre o grupo

Selecionar: adicionar e remover colunas

À medida que você começa a trabalhar mais com consultas, você pode descobrir que tem mais informações do que precisa sobre seus assuntos (ou seja, muitas colunas em sua tabela). Ou você pode precisar de mais informações do que você tem (ou seja, você precisa adicionar uma nova coluna que conterá os resultados da análise de outras colunas). Vejamos alguns dos principais operadores para manipulação de colunas.

Projeto e projeto fora

Project é aproximadamente equivalente às instruções selecionadas de muitas línguas. Ele permite que você escolha quais colunas manter. A ordem das colunas retornadas corresponderá à ordem das colunas listadas em sua project instrução, conforme mostrado neste exemplo:

Perf
| project ObjectName, CounterValue, CounterName

Captura de ecrã dos resultados do operador do projeto.

Como você pode imaginar, quando você está trabalhando com conjuntos de dados muito amplos, você pode ter muitas colunas que deseja manter, e especificá-las todas pelo nome exigiria muita digitação. Para esses casos, você tem project-away, que permite especificar quais colunas remover, em vez de quais manter, assim:

Perf
| project-away MG, _ResourceId, Type

Gorjeta

Pode ser útil usar project em dois locais em suas consultas, no início e novamente no final. Usar project no início da consulta pode ajudar a melhorar o desempenho, removendo grandes blocos de dados que você não precisa passar pelo pipeline. Usá-lo novamente no final permite que você se livre de quaisquer colunas que possam ter sido criadas em etapas anteriores e não sejam necessárias na sua saída final.

Expandido

Extend é usado para criar uma nova coluna calculada. Isso pode ser útil quando você deseja executar um cálculo em relação às colunas existentes e ver a saída de cada linha. Vejamos um exemplo simples onde calculamos uma nova coluna chamada Kbytes, que podemos calcular multiplicando o valor MB (na coluna Quantidade existente) por 1.024.

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

Na linha final de nossa project declaração, renomeamos a coluna Quantidade para Mbytes, para que possamos dizer facilmente qual unidade de medida é relevante para cada coluna.

Captura de tela dos resultados do operador de extensão.

Vale ressaltar que extend também funciona com colunas já calculadas. Por exemplo, podemos adicionar mais uma coluna chamada Bytes que é calculada a partir de Kbytes:

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

Captura de tela dos resultados de dois operadores de extensão.

Associar tabelas

Grande parte do seu trabalho no Microsoft Sentinel pode ser realizado usando um único tipo de log, mas há momentos em que você desejará correlacionar dados juntos ou realizar uma pesquisa em relação a outro conjunto de dados. Como a maioria das linguagens de consulta, Kusto Query Language oferece alguns operadores usados para executar vários tipos de junções. Nesta seção, veremos os operadores mais usados, union e join.

União

Union simplesmente pega duas ou mais tabelas e retorna todas as linhas. Por exemplo:

OfficeActivity
| union SecurityEvent

Isso retornaria todas as linhas das tabelas OfficeActivity e SecurityEvent . Union oferece alguns parâmetros que podem ser usados para ajustar como o sindicato se comporta. Dois dos mais úteis são withsource e kind:

OfficeActivity
| union withsource = SourceTable kind = inner SecurityEvent

O parâmetro withsource permite especificar o nome de uma nova coluna cujo valor em uma determinada linha será o nome da tabela da qual a linha veio. No exemplo acima, nomeamos a coluna SourceTable e, dependendo da linha, o valor será OfficeActivity ou SecurityEvent.

O outro parâmetro que especificamos foi tipo, que tem duas opções: interno ou externo. No exemplo acima, especificamos interior, o que significa que as únicas colunas que serão mantidas durante a união são aquelas que existem em ambas as tabelas. Como alternativa, se tivéssemos especificado outer (que é o valor padrão), todas as colunas de ambas as tabelas seriam retornadas.

Join

A junção funciona de forma semelhante à union, exceto que, em vez de unir tabelas para criar uma nova tabela, estamos unindo linhas para criar uma nova tabela. Como a maioria dos idiomas de banco de dados, há vários tipos de junções que você pode executar. A sintaxe geral dos fóruns é join :

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

Após o join operador, especificamos o tipo de junção que queremos realizar seguida de um parêntese aberto. Dentro dos parênteses é onde você especifica a tabela que deseja juntar, bem como quaisquer outras instruções de consulta nessa tabela que você deseja adicionar. Após o parêntese de encerramento, usamos a palavra-chave on seguida da nossa esquerda ($left.<columnName> palavra-chave) e direita ($right.<columnName>) colunas separadas com o operador == . Aqui está um exemplo de uma junção interna:

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

Nota

Se ambas as tabelas tiverem o mesmo nome para as colunas nas quais você está realizando uma junção, você não precisará usar $left e $right, em vez disso, você pode apenas especificar o nome da coluna. A utilização de $left e $right, no entanto, é mais explícita e geralmente considerada uma boa prática.

Para sua referência, a tabela a seguir mostra uma lista de tipos de associações disponíveis.

Tipos de Associações

Tipo de Adesão Description
inner Retorna um único para cada combinação de linhas correspondentes de ambas as tabelas.
innerunique Retorna linhas da tabela esquerda com valores distintos no campo vinculado que têm uma correspondência na tabela direita.
Este é o tipo de junção não especificado padrão.
leftsemi Retorna todos os registros da tabela esquerda que têm uma correspondência na tabela direita.
Somente as colunas da tabela à esquerda serão retornadas.
rightsemi Retorna todos os registros da tabela direita que têm uma correspondência na tabela esquerda.
Somente as colunas da tabela direita serão retornadas.
leftanti/
leftantisemi
Retorna todos os registros da tabela esquerda que não têm uma correspondência na tabela direita.
Somente as colunas da tabela à esquerda serão retornadas.
rightanti/
rightantisemi
Retorna todos os registros da tabela direita que não têm uma correspondência na tabela esquerda.
Somente as colunas da tabela direita serão retornadas.
leftouter Retorna todos os registros da tabela à esquerda. Para registros que não têm correspondência na tabela correta, os valores de célula serão nulos.
rightouter Retorna todos os registros da tabela correta. Para registros que não têm correspondência na tabela esquerda, os valores de célula serão nulos.
fullouter Retorna todos os registros das tabelas esquerda e direita, correspondentes ou não.
Os valores incompatíveis serão nulos.

Gorjeta

É uma boa prática ter a sua mesa mais pequena à esquerda. Em alguns casos, seguir essa regra pode oferecer enormes benefícios de desempenho, dependendo dos tipos de junções que você está realizando e do tamanho das tabelas.

Avaliar

Você deve se lembrar que , no primeiro exemplo, vimos o operador de avaliação em uma das linhas. O evaluate operador é menos comumente usado do que os que abordamos anteriormente. No entanto, saber como o operador funciona vale bem o evaluate seu tempo. Mais uma vez, aqui está a primeira consulta, onde você verá evaluate na segunda linha.

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

Este operador permite que você invoque plugins disponíveis (basicamente funções embutidas). Muitos desses plugins são focados em ciência de dados, como autocluster, diffpatterns e sequence_detect, permitindo que você realize análises avançadas e descubra anomalias estatísticas e outliers.

O plugin usado no exemplo acima foi chamado de bag_unpack, e torna muito fácil pegar um pedaço de dados dinâmicos e convertê-los em colunas. Lembre-se, dados dinâmicos são um tipo de dados muito semelhante ao JSON, como mostrado neste exemplo:

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

Nesse caso, queríamos resumir os dados por cidade, mas a cidade está contida como uma propriedade na coluna LocationDetails . Para usar a propriedade da cidade em nossa consulta, tivemos que primeiro convertê-la em uma coluna usando bag_unpack.

Voltando às nossas etapas originais do pipeline, vimos o seguinte:

Get Data | Filter | Summarize | Sort | Select

Agora que consideramos o evaluate operador, podemos ver que ele representa uma nova etapa no pipeline, que agora se parece com isto:

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

Há muitos outros exemplos de operadores e funções que podem ser usados para analisar fontes de dados em um formato mais legível e manipulável. Você pode aprender sobre eles - e o resto da Kusto Query Language - na documentação completa e na pasta de trabalho.

Deixe declarações

Agora que já abordamos muitos dos principais operadores e tipos de dados, vamos finalizar com a instrução let, que é uma ótima maneira de tornar suas consultas mais fáceis de ler, editar e manter.

Permite criar e definir uma variável ou atribuir um nome a uma expressão. Essa expressão pode ser um único valor, mas também pode ser uma consulta inteira. Eis um exemplo simples:

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

Aqui, especificamos um nome de aWeekAgo e o definimos como igual à saída de uma função timespan, que retorna um valor datetime. Em seguida, terminamos a instrução let com um ponto-e-vírgula. Agora temos uma nova variável chamada aWeekAgo que pode ser usada em qualquer lugar em nossa consulta.

Como acabamos de mencionar, você pode usar uma instrução let para fazer uma consulta inteira e dar um nome ao resultado. Como os resultados da consulta, sendo expressões tabulares, podem ser usados como entradas de consultas, você pode tratar esse resultado nomeado como uma tabela para fins de execução de outra consulta nele. Aqui está uma pequena modificação em relação ao exemplo anterior:

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

Nesse caso, criamos uma segunda instrução let , onde encapsulamos toda a nossa consulta em uma nova variável chamada getSignins. Assim como antes, terminamos a segunda instrução let com um ponto-e-vírgula. Em seguida, chamamos a variável na linha final, que executará a consulta. Observe que fomos capazes de usar aWeekAgo na segunda declaração let . Isso ocorre porque nós o especificamos na linha anterior; se trocássemos as instruções let para que getSignins viesse primeiro, teríamos um erro.

Agora podemos usar getSignins como base de outra consulta (na mesma janela):

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

Deixe que as declarações lhe deem mais poder e flexibilidade para ajudar a organizar as suas consultas. Vamos definir valores escalares e tabulares, bem como criar funções definidas pelo usuário. Eles realmente são úteis quando você está organizando consultas mais complexas que podem estar fazendo várias junções.

Próximos passos

Embora este artigo mal tenha arranhado a superfície, agora você tem a base necessária, e nós cobrimos as partes que você usará com mais frequência para realizar seu trabalho no Microsoft Sentinel.

Pasta de trabalho avançada do KQL para Microsoft Sentinel

Aproveite uma pasta de trabalho Kusto Query Language diretamente no próprio Microsoft Sentinel - a pasta de trabalho Advanced KQL for Microsoft Sentinel . Ele fornece ajuda passo a passo e exemplos para muitas das situações que você provavelmente encontrará durante suas operações de segurança diárias, e também aponta para muitos exemplos prontos e prontos de regras de análise, pastas de trabalho, regras de caça e mais elementos que usam consultas Kusto. Inicie esta pasta de trabalho a partir da folha Pastas de trabalho no Microsoft Sentinel.

Advanced KQL Framework Workbook - Capacitá-lo a se tornar conhecedor do KQL é uma excelente postagem de blog que mostra como usar essa pasta de trabalho.

Mais recursos

Veja esta coleção de recursos de aprendizagem, treinamento e qualificação para ampliar e aprofundar seu conhecimento da Kusto Query Language.