Cibersegurança e segurança da informação

KQL para Iniciantes: Primeiras Consultas para Investigação de Incidentes

6 min de leituraPublicado: 5 de agosto de 2026
Ilustração visual profissional sobre KQL para iniciantes em SIEM e deteção
Resposta rápida

KQL — Kusto Query Language — é uma linguagem de consulta para ler e analisar dados em produtos como Azure Monitor e Microsoft Sentinel. Uma consulta geralmente começa com uma tabela e continua com um pipeline de comandos: filtros de tempo e eventos, seleção ou criação de campos, sumário por utilizador ou ativo e apresentação dos resultados relevantes para a investigação. A chave para aprender é começar com uma pergunta e construir a consulta passo a passo.

Um analista SOC não precisa de memorizar centenas de comandos para começar a trabalhar com KQL. Ele precisa de entender o modelo de pensamento: em que tabela se encontra a informação, qual é o período de tempo, quais as linhas relevantes, quais os campos necessários e como resumir os resultados para responder a uma pergunta de investigação.

KQL é uma linguagem para leitura e análise. Não é SQL, embora existam conceitos semelhantes. Os dados fluem da esquerda para a direita através de um Pipe — o símbolo | — e cada linha recebe o resultado da linha anterior. Desta forma, é possível construir uma pequena pesquisa, verificar um resultado e adicionar uma etapa adicional sem escrever tudo de uma vez.

Os exemplos no guia utilizam nomes de tabelas e campos comuns, mas o Schema varia entre Workspaces e fontes. Antes de copiar uma Query, abra alguns registos, verifique os campos reais e adapte a lógica. Todos os dados no exercício são simulados.

Estrutura Básica de uma Query

A primeira linha geralmente indica a Table. Cada Pipe adiciona um Operator. Por exemplo, uma Query que mostra os dez últimos logins da tabela SigninLogs:

SigninLogs
| where TimeGenerated > ago(24h)
| project TimeGenerated, UserPrincipalName, IPAddress, ResultType
| sort by TimeGenerated desc
| take 10

Leia a consulta como uma frase: pegue SigninLogs, mantenha apenas os eventos da última hora, mostre quatro campos, ordene do mais recente para o mais antigo e pegue dez linhas. A ordem das etapas é importante tanto para a compreensão quanto para o desempenho. A filtragem precoce reduz a quantidade de dados que passa para as etapas seguintes.

Passo 1: Conheça a Tabela

Antes de investigar, execute algumas linhas para ver o Schema e exemplos. O comando take é excelente para aprender, mas não garante os eventos mais recentes, a menos que sejam classificados. Pode usar project para reduzir a visualização e project-away para ocultar campos desnecessários.

SigninLogs
| take 5

Observe os campos do tipo datetime, string, dynamic e int. Um campo dynamic pode conter JSON ou um array e, por vezes, requer parse_json, mv-expand ou acesso a uma propriedade interna. Não presuma que ResultType é idêntico para todas as fontes. Na documentação da tabela ou em exemplos reais, verifique o significado dos valores.

Passo 2: Filtragem Usando where

where é a principal ferramenta de investigação. É possível filtrar por tempo, utilizador, IP, resultado ou texto. É preferível começar com um intervalo de tempo estreito e usar comparações que se adequem ao tipo de campo.

SigninLogs
| where TimeGenerated between (datetime(2026-08-01 08:00:00) .. datetime(2026-08-01 12:00:00))
| where UserPrincipalName =~ "student@contoso.example"
| project TimeGenerated, UserPrincipalName, IPAddress, AppDisplayName, ResultType

O operador =~ compara strings sem considerar maiúsculas e minúsculas. Para pesquisa parcial, pode usar contains, has ou startswith. Em muitos casos, has é mais eficiente e preciso para procurar um termo completo. Evite a filtragem muito ampla com contains quando é possível usar um campo estruturado.

Passo 3: Seleção de Campos e Criação de Contexto

project retorna apenas os campos selecionados. extend cria um novo campo sem remover os existentes. Isso é útil para rótulos, cálculos e normalização local.

SigninLogs
| where TimeGenerated > ago(24h)
| extend Outcome = iff(tostring(ResultType) == "0", "Success", "Failure")
| project TimeGenerated, UserPrincipalName, IPAddress, AppDisplayName, Outcome

Ao investigar, prefira nomes de campos que esclareçam o significado. Pode usar project-rename para mudar o nome para exibição, mas não oculte a origem dos dados. Um Ticket profissional deve indicar de qual Table e campo veio cada descoberta importante.

Passo 4: summarize — Transformar Eventos em uma Imagem

summarize agrupa eventos e calcula Aggregations. É possível contar, encontrar a primeira e a última vez, coletar valores, calcular o número de utilizadores únicos e construir uma Baseline. BY define os grupos.

SigninLogs
| where TimeGenerated > ago(24h)
| summarize Attempts=count(),
            FirstSeen=min(TimeGenerated),
            LastSeen=max(TimeGenerated),
            Applications=make_set(AppDisplayName, 10)
  by UserPrincipalName, IPAddress
| sort by Attempts desc

make_set é útil para exibir valores diferentes, mas listas grandes são pesadas e dificultam a leitura. Limite o número de itens. dcount fornece uma contagem aproximada de valores únicos e é adequado para análise ampla; distinct retorna combinações únicas quando é preciso vê-las.

countif e dcountif

Em investigações de Autenticação, às vezes é necessário contar sucessos e falhas no mesmo grupo. countif conta apenas as linhas que satisfazem uma condição:

SigninLogs
| where TimeGenerated > ago(24h)
| summarize Failures=countif(tostring(ResultType) != "0"),
            Successes=countif(tostring(ResultType) == "0"),
            UniqueUsers=dcount(UserPrincipalName)
  by IPAddress
| where Failures >= 5
| sort by Failures desc

O resultado é um Lead. Não prova que o sucesso ocorreu após as falhas, e um IP partilhado pode ser um NAT, Proxy ou VPN. É preciso abrir os eventos brutos e construir uma Timeline antes da Classification.

Janelas de Tempo Usando bin

bin agrupa datetimes em segmentos fixos. Isso permite ver um Burst de atividade em vez de resumir um dia inteiro. Escolha o Span de acordo com o comportamento: um Password Spray pode estender-se por um longo período para evitar um limite; um Brute Force contra uma única conta pode ser rápido.

SigninLogs
| where TimeGenerated > ago(24h)
| summarize Failures=countif(tostring(ResultType) != "0"),
            Successes=countif(tostring(ResultType) == "0"),
            Users=dcount(UserPrincipalName)
  by IPAddress, bin(TimeGenerated, 30m)
| where Failures >= 10 and Successes >= 1
| sort by TimeGenerated desc

A coluna TimeGenerated no resultado representa o início do Bin. Para ver a ordem exata, use o resultado para selecionar o IP e a janela, e então execute uma segunda Query que mostra os eventos por tempo.

let — Transformar Query em Leitura

let permite atribuir um nome a um valor ou a uma tabela temporária. É útil para definir um Time range, Threshold ou um conjunto de dados que é usado várias vezes.

let Lookback = 24h;
let FailureThreshold = 10;
SigninLogs
| where TimeGenerated > ago(Lookback)
| summarize Failures=countif(tostring(ResultType) != "0"),
            Successes=countif(tostring(ResultType) == "0"),
            Users=dcount(UserPrincipalName),
            UserList=make_set(UserPrincipalName, 20)
  by IPAddress, bin(TimeGenerated, 30m)
| where Failures >= FailureThreshold and Successes >= 1
| project TimeGenerated, IPAddress, Failures, Successes, Users, UserList
| order by Failures desc

Nomes claros e comentários reduzem erros. Em KQL, é possível adicionar um Comment usando //. Uma Query que se destina a tornar-se uma Analytics rule deve descrever o objetivo da deteção, as premissas, as fontes, a versão e o Owner.

Exercício Prático: Falhas Seguidas por Login Bem-Sucedido

No laboratório, execute a Query de resumo sobre dados simulados. Selecione uma linha onde existam Failures e Successes. Em seguida, faça um Drill-down:

let TargetIP = "203.0.113.25";
let WindowStart = datetime(2026-08-01 09:00:00);
SigninLogs
| where TimeGenerated between (WindowStart .. WindowStart + 30m)
| where IPAddress == TargetIP
| extend Outcome = iff(tostring(ResultType) == "0", "Success", "Failure")
| project TimeGenerated, UserPrincipalName, IPAddress, AppDisplayName, Outcome, ResultDescription
| order by TimeGenerated asc

Responda às perguntas: As falhas afetaram um ou vários utilizadores? O sucesso pertence ao mesmo utilizador? O IP é reconhecido como VPN? A MFA foi ativada? Existe alguma atividade adicional após o sucesso? Somente após recolher o contexto é possível determinar se se trata de um Password Spray, erro de utilizador, serviço antigo ou atividade aprovada.

Otimização e Segurança no Trabalho

  • Filtre o tempo cedo e use a tabela mais precisa.
  • Filtre em campos estruturados em vez de realizar pesquisa de texto em Raw data.
  • Exiba apenas os campos necessários usando project.
  • Evite join em grandes volumes antes de verificar alternativas como lookup ou summarize.
  • Limite make_set e make_list.
  • Verifique a Query em um intervalo pequeno antes de expandir.
  • Documente premissas, Thresholds e o significado dos valores de Result.
  • Não transforme uma Query em Deteção antes de verificar True/False/Benign Positives.

Erros Comuns

  • Copiar uma Query sem verificar o Schema local.
  • Resumir um dia inteiro e concluir que houve uma sequência de eventos.
  • Esquecer o intervalo de tempo e varrer um grande volume desnecessariamente.
  • Usar um nome de campo diferente entre duas fontes como se fosse uniforme.
  • Tratar o resultado da Query como prova de um ataque.
  • Criar listas enormes usando make_set.
  • Escrever uma única Query longa em vez de verificar cada etapa.

Resumo e CTA

Abra um ambiente de Log Analytics ou laboratório com dados simulados e construa uma Query em três passos: mostre cinco linhas, filtre um evento e resuma por utilizador ou IP. Salve cada versão e explique o que ela responde. Em seguida, vá para o guia de investigação de Incidentes no Microsoft Sentinel para ver como a Query se integra a um Case real.

Perguntas frequentes

O KQL é semelhante ao SQL?

Existem conceitos partilhados como filtragem, Projection e Aggregation, mas a sintaxe e o modelo Pipe são diferentes. É aconselhável aprender KQL como uma linguagem própria.

Qual a diferença entre where e search?

where filtra por expressão e campos definidos e é preferível na maioria das investigações. search pode procurar valores de forma mais ampla, mas às vezes é menos preciso e eficiente.

Uma Query pode alterar ou apagar logs?

KQL no contexto de Log Analytics e Sentinel é usado para leitura e análise. As operações de gestão e Ingestion são realizadas através de outros mecanismos e permissões apropriadas.

Qual a diferença entre summarize e distinct?

distinct retorna combinações únicas. summarize agrupa e calcula valores como count, min, max ou make_set.

Como saber qual Table procurar?

Comece pelo Data connector e pela documentação do Schema, use a pesquisa de Tables em Log Analytics e verifique exemplos de eventos. O mesmo Use Case pode usar tabelas diferentes entre organizações.

Quer verificar se este percurso é para si?

Deixe os seus dados e um consultor da HPI ligará para uma breve conversa de adequação, sem compromisso.

Os seus dados são armazenados em segurança.

Para cursos de SOC e Cibersegurança no âmbito do programa Cybersecurity & AI

Quer os detalhes do programa? Deixe os seus dados e entraremos em contacto.

Artigos relacionados