Skip to content

Prepend necessary conditions to query #53

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 68 additions & 5 deletions sigma/backends/splunk/splunk.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from dataclasses import dataclass
import re
from sigma.conversion.state import ConversionState
from sigma.modifiers import SigmaRegularExpression
Expand All @@ -10,6 +11,8 @@
ConditionAND,
ConditionNOT,
ConditionItem,
ConditionType,
ConditionValueExpression,
)
from sigma.types import SigmaCompareExpression, SigmaString
from sigma.exceptions import SigmaFeatureNotSupportedByBackendError, SigmaError
Expand All @@ -23,6 +26,14 @@
from typing import Any, Callable, ClassVar, Dict, List, Optional, Pattern, Tuple, Union


@dataclass
class DeferredSimpleExpression(DeferredQueryExpression):
content: str

def finalize_expression(self) -> Any:
return self.content


class SplunkDeferredRegularExpression(DeferredTextQueryExpression):
template = 'regex {field}{op}"{value}"'
operators = {
Expand Down Expand Up @@ -273,12 +284,42 @@ def convert_condition_field_eq_val_re(
state, cond.field, super().convert_condition_field_eq_val_re(cond, state)
).postprocess(None, cond)

def convert_condition(self, cond: ConditionType, state: ConversionState) -> Any:
if isinstance(cond, ConditionOR):
if self.decide_convert_condition_as_in_expression(
cond, state
) and not cond.parent_condition_chain_contains(ConditionOR):
return DeferredSimpleExpression(
state, self.convert_condition_as_in_expression(cond, state)
).postprocess(None, cond)
elif isinstance(cond, ConditionNOT):
if not cond.parent_condition_chain_contains(ConditionOR):
return DeferredSimpleExpression(
state, self.convert_condition_not(cond, state)
).postprocess(None, cond)
elif isinstance(cond, ConditionFieldEqualsValueExpression):
if not (cond.parent_condition_chain_contains(ConditionOR)) and not (
cond.parent_condition_chain_contains(ConditionNOT)
):
return DeferredSimpleExpression(
state, self.convert_condition_field_eq_val(cond, state)
).postprocess(None, cond)
elif isinstance(cond, ConditionValueExpression):
if not (cond.parent_condition_chain_contains(ConditionOR)) and not (
cond.parent_condition_chain_contains(ConditionNOT)
):
return DeferredSimpleExpression(
state, self.convert_condition_val(cond, state)
).postprocess(None, cond)
return super().convert_condition(cond, state)

def convert_condition_field_eq_field(
self,
cond: ConditionFieldEqualsValueExpression,
state: "sigma.conversion.state.ConversionState",
) -> SplunkDeferredFieldRefExpression:
"""Defer FieldRef matching to pipelined with `where` command after main search expression."""

if cond.parent_condition_chain_contains(ConditionOR):
raise SigmaFeatureNotSupportedByBackendError(
"ORing FieldRef matching is not yet supported by Splunk backend",
Expand All @@ -300,40 +341,62 @@ def finalize_query(
if state.has_deferred():
deferred_regex_or_expressions = []
no_regex_oring_deferred_expressions = []

start_expressions = []
for index, deferred_expression in enumerate(state.deferred):

if type(deferred_expression) == SplunkDeferredORRegularExpression:
deferred_regex_or_expressions.append(
deferred_expression.finalize_expression()
)
elif type(deferred_expression) == DeferredSimpleExpression:
fin = deferred_expression.finalize_expression()
if isinstance(fin, DeferredQueryExpression):
pass
else:
start_expressions.append(fin)
else:
no_regex_oring_deferred_expressions.append(deferred_expression)

start_part = f"{' '.join(start_expressions)}"
state.deferred = no_regex_oring_deferred_expressions
if len(deferred_regex_or_expressions) > 0:

SplunkDeferredORRegularExpression.reset() # need to reset class for potential future conversions
# remove deferred oring regex expressions from the state
# as they will be taken into account by the super().finalize_query
state.deferred = no_regex_oring_deferred_expressions

return super().finalize_query(
rule,
self.deferred_start
start_part
+ self.deferred_start
+ self.deferred_separator.join(deferred_regex_or_expressions)
+ "\n| search "
+ query,
index,
state,
output_format,
)
else:
if isinstance(query, DeferredQueryExpression):
if start_part != "":
query = start_part
else:
pass
else:
query = " ".join(
[elem for elem in [start_part, query] if elem != ""]
)

return super().finalize_query(rule, query, index, state, output_format)

def finalize_query_default(
self, rule: SigmaRule, query: str, index: int, state: ConversionState
) -> str:
table_fields = " | table " + ",".join(rule.fields) if rule.fields else ""
return query + table_fields
if not rule._backreferences: # if rule is not part of a correlation rule
table_fields = " | table " + ",".join(rule.fields) if rule.fields else ""
return query + table_fields
else:
return query

def finalize_query_savedsearches(
self, rule: SigmaRule, query: str, index: int, state: ConversionState
Expand Down
7 changes: 4 additions & 3 deletions tests/test_backend_splunk.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ def test_splunk_regex_query_implicit_or(splunk_backend: SplunkBackend):
)
)
== [
'\n| rex field=fieldA "(?<fieldAMatch>foo.*bar)"\n| eval fieldACondition=if(isnotnull(fieldAMatch), "true", "false")\n| rex field=fieldA "(?<fieldAMatch2>boo.*foo)"\n| eval fieldACondition2=if(isnotnull(fieldAMatch2), "true", "false")\n| search fieldACondition="true" OR fieldACondition2="true" fieldB="foo" fieldC="bar"'
'fieldB="foo" fieldC="bar"\n| rex field=fieldA "(?<fieldAMatch>foo.*bar)"\n| eval fieldACondition=if(isnotnull(fieldAMatch), "true", "false")\n| rex field=fieldA "(?<fieldAMatch2>boo.*foo)"\n| eval fieldACondition2=if(isnotnull(fieldAMatch2), "true", "false")\n| search fieldACondition="true" OR fieldACondition2="true"'
]
)

Expand Down Expand Up @@ -327,7 +327,7 @@ def test_splunk_cidr_or(splunk_backend: SplunkBackend):
"""
)
)
== ['fieldA="192.168.0.0/16" OR fieldA="10.0.0.0/8" fieldB="foo" fieldC="bar"']
== ['fieldB="foo" fieldC="bar" fieldA="192.168.0.0/16" OR fieldA="10.0.0.0/8"']
)


Expand Down Expand Up @@ -376,6 +376,7 @@ def test_splunk_fieldref_or(splunk_backend: SplunkBackend):
)
)


def test_splunk_exists(splunk_backend: SplunkBackend):
assert (
splunk_backend.convert(
Expand All @@ -394,7 +395,7 @@ def test_splunk_exists(splunk_backend: SplunkBackend):
"""
)
)
== ['fieldA=* NOT fieldB=*']
== ["fieldA=* NOT fieldB=*"]
)


Expand Down
6 changes: 5 additions & 1 deletion tests/test_backend_splunk_correlations.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from test_backend_splunk import splunk_backend
from sigma.collection import SigmaCollection


def test_event_count_correlation_rule_stats_query(splunk_backend):
correlation_rule = SigmaCollection.from_yaml(
"""
Expand Down Expand Up @@ -38,6 +39,7 @@ def test_event_count_correlation_rule_stats_query(splunk_backend):
| search event_count >= 10"""
]


def test_value_count_correlation_rule_stats_query(splunk_backend):
correlation_rule = SigmaCollection.from_yaml(
"""
Expand Down Expand Up @@ -75,6 +77,7 @@ def test_value_count_correlation_rule_stats_query(splunk_backend):
| search value_count < 10"""
]


def test_temporal_correlation_rule_stats_query(splunk_backend):
correlation_rule = SigmaCollection.from_yaml(
"""
Expand Down Expand Up @@ -124,4 +127,5 @@ def test_temporal_correlation_rule_stats_query(splunk_backend):
| bin _time span=15m
| stats dc(event_type) as event_type_count by _time fieldC

| search event_type_count >= 2"""]
| search event_type_count >= 2"""
]
Loading