SIGMA Rules Pipelines - Parte 02
Por Davi Chaves
Table of Contents
- SIGMA Rules - Parte 02 - Pipelines
- Objetivo
- The Field Naming Problem
- Pipelines, what are they?
- Specification of Pipelines
- Specification of Transformations 1. field_name_mapping 1. add_condition
- Specification of Conditions
- Mapping Process Creation Rules
O principal objetivo das SIGMAS Rules é descrever assinaturas de logs de maneira genérica, independente de fornecedor e ambiente. Fontes genéricas de logs (logsources) são uma camada adicional de abstração entre a lógica de detecção e a fonte de logs, permitindo que o usuário de uma regra utilize o mesmo conjunto de regras de criação de processos para eventos do Sysmon, assim como para eventos gerados por uma solução de Detecção e Resposta de Endpoint (EDR).
No entanto, as regras Sigma precisam, em última instância, se transformar em consultas específicas para o SIEM, EDR ou qualquer outra ferramenta utilizada para esse fim, levando em consideração suas convenções exclusivas de nomenclatura de campos e outros requisitos específicos. A abordagem do pySigma (e, portanto, também do Sigma CLI) para fazer isso são os pipelines de processamento. Muitos backends já incluem um ou vários pipelines, sendo que alguns são aplicados automaticamente pelo backend, como é o caso dos backends do QRadar e InsightIDR, devido a esses sistemas possuírem uma taxonomia fixa. Outros backends, como Elasticsearch e Splunk, exigem o uso explícito de pipelines de processamento, pois há muitas maneiras de inserir dados nessas plataformas.
Objetivo
Voltanto para nosso exemplo do artigo anterior, temos uma SIGMA rule que, se não for aplicada nenhuma pipeline, é gerado uma query com fields genéricos, que podem, ou não, serém os mesmos fields que o SIEM da sua companhia.
logsource:
product: windows
detection:
selection:
Image|endswith: '\certutil.exe'
CommandLine|contains:
- '-encode'
- '/encode'
condition: selection
|
| Sem pipeline
V
Image="*\\certutil.exe" CommandLine IN ("*-encode*", "*/encode*")
|
| Com uma pipeline
V
index="company-windows" EventCode=4688 NewProcessName="*\\certutil.exe" CommandLine IN ("*-encode*", "*/encode*")
No exemplo acima, poderiamos utilizar uma pipeline para mapear o campo genérico Image
para NewProcessName
. Além disso, baseado no logsource (product: windows
) podemos adicionar o campo index="company-windows"
para focar a busca em apenas logs do ambiente Windows. Note que, caso sua empresa tivesse, por exemplo, vários indexes de logs windows, como “company-windows, windows-logs, windows-something”, você poderia utilizar a pipeline da mesma forma para adicionar todos esses indexes na query final.
Exemplo Thomas Patzke
Abaixo, um exemplo retirado do blog do Thomas Patzke:
Na priveira conversão, não é utilizado nenhuma pipeline. Por conta disso, veja que o resultado contem campos bem genéricos. Na segunda conversão, a pipeline splunk
é utilizada. Essa pipeline é bem genérica, ela apenas vai mapear os logsources da regras SIGMA em EventIds do Sysmon. No caso dessa regra em específico, se trata da categoria de “criação de processos”. Portanto, essa pipeline genérica apenas fez o mapeamento windows & process_creation --> EventID=1
. Na terceira conversão, temos uma pipeline específica do ambiente SIEM dele. Ou seja, no ambiente splunk dele, os logs Windows estão armazenados no index="windows"
, o field Image
se chama win.Image
e, ao invés do field padrão EventID
, foi utilizado dois fields com sintaxe ligeiramente diferente: event_id
e evtid
.
O importante é entender que pipelines, além de fazerem mapeamentos, são como um funil: vão transformar regras genéricas em coisas mais específicas. Nesse artigo, vamos primeiramente nos focar em regras mais específicas, como a terceira conversão do Patzke.
The Field Naming Problem
No pior dos casos, caso cada regra SIGMA utilizasse campos totalmente diferentes uns dos outros, precisariamos de uma pipeline para cada regra! Isso tiraria um grande poder das SIGMA rules.
Entretanto, como os autores das SIGMAs rules seguem uma certa convensão de campos, uma única pipeline pode, muitas vezes, ser aplicada para várias regras.
Como exemplo prático, o repositório oficial de SIGMA rules possui milhares de regras da categoria process_creation
. Dessa forma, com uma única pipeline, poderíamos utilizar todas essas regras.
Pipelines, what are they?
Um pipeline de processamento define uma sequência de transformações aplicadas a uma regra Sigma antes de convertê-la na linguagem de consulta alvo. Essas transformações incluem mapeamentos de campos, adição de sufixos aos nomes dos campos e muito mais.
Os pipelines podem ser representados tanto como arquivos YAML, destinados a usuários finais criando pipelines específicos para o ambiente das regras Sigma, quanto como código Python. Este último é o método preferido para implementar pipelines específicos para o backend e oferece algumas vantagens. Nesse artigo, vamos nós focar em pipelines no formato YAML.
Os itens de processamento dentro de um pipeline são executados na ordem definida pela prioridade. Uma cadeia típica de processamento envolve:
- Traduzir fontes genéricas de logs em fontes específicas, como converter regras Sigma de criação de processos em Sysmon EventID 1.
- Transformar assinaturas de logs na taxonomia usada pelo backend.
- Aplicar transformações específicas do ambiente.
Os pipelines podem ser encadeados com base em sua prioridade, criando um fluxo contínuo de transformações para as regras Sigma.
Specification of Pipelines
Uma pipeline, também chamada de ProcessingPipeline
, pode conter uma variedade de campos responsáveis por fazerem diversos processamentos nas regras SIGMA. Abaixo, um exemplo de pipeline contendo alguns desses campos. Apesar disso, nesse artigo iremos apenas tratar das “transformations”.
# Exemplo de pipeline retirado do repositório pySigma
name: Test
priority: 10
allowed_backends:
- test-a
- test-b
transformations: # Vamos abordar apenas esse campo
- id: test
field_name_mapping:
EventID: EventCode
rule_conditions:
- type: "true"
dummy: test-true
- type: "false"
dummy: test-false
rule_cond_op: or
detection_item_conditions:
- type: "true"
dummy: test-true
- type: "false"
dummy: test-false
detection_item_cond_op: or
postprocessing:
- id: test
type: embed
prefix: "[ "
suffix: " ]"
rule_conditions:
- type: "true"
dummy: test-true
- type: "false"
dummy: test-false
rule_cond_op: or
finalizers:
- type: concat
prefix: "('"
separator: "', '"
suffix: "')"
vars:
test_string: abc
test_number: 123
Specification of Transformations
O campo “transformations” de pipelines no formato YAML contém uma ou mais ProcessingItem
.
Um ProcessingItem
é composto por uma ProcessingCondition
^[ProcessingCondition
não tem relação com o campo “condition” de uma regra SIGMA] opcional e uma Transformation
^[Perceba que o campo “transformations” de pipelines no formato YAML não contem uma lista de Transformation
, mas sim uma lista de ProcessingItem
] que é aplicada no caso de a condição ser avaliada como verdadeira em relação à regra SIGMA fornecida ou se a condição não estiver presente. Como exemplo, antes mesmo de entendermos o que cada campo significa, abaixo está uma pipeline contendo três ProcessingItem
, com cada um contendo uma Transformation
, e duas ProcessingCondition
.
name: My cool pipeline
transformations:
# ProcessingItem 1
- id: cool_id_1
# Transformation 1
type: field_name_mapping
mapping:
EventID:
- event_id
# ProcessingItem 2
- id: cool_id_2
# Transformation 2
type: field_name_prefix
prefix: "win."
# ProcessingCondition 2
field_name_conditions:
- type: include_fields
fields:
- Image
# ProcessingItem 3
- id: cool_id_3
# Transformation 3
type: add_condition
conditions:
index: windows
# ProcessingCondition 3
rule_conditions:
- type: logsource
product: windows
# Note: no "ProcessingItem 3", apesar dos campos "conditions:" e "rule_conditions" possuirem o mesmo termo "conditions", eles são coisas completamente diferentes. O primeiro é um campo do "Transformation 3", já o segundo é o campo "rule_conditions" do "ProcessingItem 3".
Abaixo, uma tabela retirado da documentação oficial contendo todos os tipos de transformações atualmente suportadas.
Identifier | Parameters | Meaning |
---|---|---|
field_name_mapping | mapping |
Map a field name to one or multiple different. |
field_name_prefix_mapping | mapping |
Map a field name prefix to one or multiple different prefixes. |
field_name_suffix | suffix |
Add field name suffix. |
field_name_prefix | prefix |
Add field name prefix. |
drop_detection_item | Deletes detection items. This should only used in combination with a detection item condition. | |
wildcard_placeholders | include , exclude |
Replaces placeholders with wildcards. This transformation is useful if remaining placeholders should be replaced with something meaningful to make conversion of rules possible without defining the placeholders content. |
value_placeholders | include , exclude |
Replaces placeholders with values contained in variables defined in the configuration. |
query_expression_placeholders | include , exclude , expression , mapping |
Replaces a placeholder with a plain query containing the placeholder or an identifier mapped from the placeholder name. The main purpose is the generation of arbitrary list lookup expressions which are passed to the resulting query. |
add_condition | conditions , name , template |
Add a condition expression to rule conditions. |
change_logsource | category , product , service |
Replace log source as defined in transformation parameters. |
replace_string | regex , replacement |
Replace string part matched by regular expresssion with replacement string that can reference capture groups. It operates on the plain string representation of the SigmaString value. |
map_string | mapping |
Map static string value to one or multiple other strings. |
set_state | key , val |
Set pipeline state key to value. |
rule_failure | message |
Raise a SigmaTransformationError with the provided message. This enables transformation pipelines to signalize that a certain situation can’t be handled, e.g. only a subset of values is allowed because the target data model doesn’t offers all possibilities. |
detection_item_failure | message |
Raise a SigmaTransformationError with the provided message. This enables transformation pipelines to signalize that a certain situation can’t be handled, e.g. only a subset of values is allowed because the target data model doesn’t offers all possibilities. |
Por enquanto, vamos deixar o tema de ProcessingCondition de lado para entendermos as Transformation . Como mostrado acima, existem muitas transformações possíveis. Diante disso, não irei explicar cada uma delas, irei apenas dar uma intuição sobre o porquê de usar transformações |
field_name_mapping
Suponha que em nossos logs de criação de processo o campo contendo o nome do processo não fosso Image
. Dessa forma, fazer um simples mapeamento do campo Image
para NewProcessName
, por exemplo, já seria o suficiente. Estamos assumindo, por simplicidade, que essa pipeline só seria aplicada por nós em logs de criação de processos, pois, caso ela fosse aplicada em qualquer outro log que também contesse o campo Image
, o mapeamento também seria feito. Veremos mais a frente como utilizar “condições” para garantir que essa transformação só seja aplicada em casos específicos.
transformations:
# ProcessingItem
- id: some_cool_id
# Transformation
type: field_name_mapping
mapping:
Image: NewProcessName
add_condition
Suponha, agora, que queiramos procurar por logs apénas com o index: company-windows
e com o EventCode: 4688
. Para isso, vamos utilizar a Transformation
“add_condition” para adicionar um novo campo.
transformations:
...
# ProcessingItem
- id: some_cool_id2
# Transformation
type: add_condition
conditions:
index: company-windows
EventCode: 4688
É importante notar que, adicionar os campos index: company-windows
e EventCode: 4688
para todos os logs, provavelmente, não é uma boa ideia. O ideal seria adicionar esse campo apenas quando a regra SIGMA conter o logsource index: windows
, category: processes_creation
e service: windows_audit
. Veremos como alcançar isso usando as ProcessingCondition
mais a diante.
Specification of Conditions
As ProcessingCondition
de um ProcessingItem
servem apenas para uma coisa: dizer se a Transformation
será aplicada ou não. Além disso, é importante frisar que qualquer ProcessingCondition
pode ser utilizada para qualquer ProcessingItem. Diante disso, é obvio a importância de condições, pois vão surgir situações nas quais queremos aplicar certas trasnformações apenas sob certas condições.
Existem três grupos de ProcessingCondition
:
RuleProcessingCondition
DetectionItemProcessingCondition
FieldNameProcessingCondition
Todas as três sãoProcessingCondition
, mas possuem algumas pequenas diferenças. É importante que fique avisado que eu não compreendi totalmente logo de início o motivo dessa divisão, então não fique surpreso se você também achar um pouco desnecessário esses três agrupamentos.
RuleProcessingCondition
Atualmente, existem cinco condições nesse grupo, estas são avaliadas para a regra SIGMA como um todo.
Type | Parameters | Meaning |
---|---|---|
logsource | category , product , service |
Matches log source on rule. Not specified log source fields are ignored. |
contains_detection_item | field , value |
Returns True if rule contains a detection item that matches the given field name and value. |
processing_item_applied | processing_item_id |
Checks if processing item was applied to rule. |
is_sigma_rule | Checks if rule is a SigmaRule. | |
processing_item_applied | Checks if rule is a SigmaCorrelationRule. |
Exemplo: LogsourceCondition
Como explicado no exemplo da transformação “add_condition”, só faz sentido adicionar o index: company-windows
se a regra SIGMA conter no logsource product: windows
. Portanto, podemos utilizar a LogsourceCondition
para aplicar essa transformação apenas nesse caso específico.
name: My cool pipeline
transformations:
# ProcessingItem
- id: cool_id
# Transformation
type: add_condition
conditions:
index: company-windows
rule_conditions:
# RuleProcessingCondition (LogsourceCondition)
- type: logsource
product: windows
DetectionItemProcessingCondition
Atualmente, existem duas condições nesse grupo, estas são avaliadas para a regra SIGMA como um todo, estas são avaliadas para cada item de detecção de uma regra SIGMA.
Type | Parameters | Meaning |
---|---|---|
match_string | cond , pattern |
Match string values with a regular expression ‘pattern’. The parameter ‘cond’ determines for detection items with multiple values if any or all strings must match. |
processing_item_applied | processing_item_id |
Checks if processing item was applied to detection item. |
FieldNameProcessingCondition
Atualmente, existem três condições nesse grupo, estas são avaliadas para nomes de campos em itens de detecção, campos da regra SIGMA ou em outros casos que é requerido fazer um “matching” em nomes de campos sem um contexto de um item de detecção.
Type | Parameters | Meaning |
---|---|---|
include_fields | fields , type |
Matches on field name if it is contained in fields list. |
exclude_fields | fields , type |
Matches on field name if it is not contained in fields list. |
processing_item_applied | processing_item_id |
Checks if processing item was applied to a field name. |
Exemplo: ExcludeFieldCondition
Suponha que todos os campos dos logs que você deseja rodar as regras SIGMA possuem o prefíxo “Process.”, com a excessão do campo Image
. Nesse caso, podemos utilizar essa condição para excluir os campos desejados e fazer com que a transformação seja aplicada apenas nos campos corretos.
name: My cool pipeline
transformations:
# ProcessingItem
- id: cool_id
# Transformation
type: field_name_prefix
prefix: "Process."
field_name_conditions:
# FieldNameProcessingCondition (ExcludeFieldCondition)
- type: exclude_fields
fields:
- Image