Skip to content

Commit fc6791f

Browse files
authored
Merge pull request #37 from arblade/main
adding Oring regexes
2 parents 0f40b79 + 1a63781 commit fc6791f

File tree

2 files changed

+125
-20
lines changed

2 files changed

+125
-20
lines changed

sigma/backends/splunk/splunk.py

+114-14
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import re
22
from sigma.conversion.state import ConversionState
3-
from sigma.rule import SigmaRule
4-
from sigma.conversion.base import TextQueryBackend
3+
from sigma.modifiers import SigmaRegularExpression
4+
from sigma.rule import SigmaRule, SigmaDetection
5+
from sigma.conversion.base import TextQueryBackend, DeferredQueryExpression
56
from sigma.conversion.deferred import DeferredTextQueryExpression
67
from sigma.conditions import (
78
ConditionFieldEqualsValueExpression,
@@ -10,15 +11,15 @@
1011
ConditionNOT,
1112
ConditionItem,
1213
)
13-
from sigma.types import SigmaCompareExpression
14-
from sigma.exceptions import SigmaFeatureNotSupportedByBackendError
14+
from sigma.types import SigmaCompareExpression, SigmaString
15+
from sigma.exceptions import SigmaFeatureNotSupportedByBackendError, SigmaError
1516
from sigma.pipelines.splunk.splunk import (
1617
splunk_sysmon_process_creation_cim_mapping,
1718
splunk_windows_registry_cim_mapping,
1819
splunk_windows_file_event_cim_mapping,
1920
)
2021
import sigma
21-
from typing import Callable, ClassVar, Dict, List, Optional, Pattern, Tuple
22+
from typing import Any, Callable, ClassVar, Dict, List, Optional, Pattern, Tuple, Union
2223

2324

2425
class SplunkDeferredRegularExpression(DeferredTextQueryExpression):
@@ -30,6 +31,48 @@ class SplunkDeferredRegularExpression(DeferredTextQueryExpression):
3031
default_field = "_raw"
3132

3233

34+
class SplunkDeferredORRegularExpression(DeferredTextQueryExpression):
35+
field_counts = {}
36+
default_field = "_raw"
37+
operators = {
38+
True: "!=",
39+
False: "=",
40+
}
41+
42+
def __init__(self, state, field, arg) -> None:
43+
SplunkDeferredORRegularExpression.add_field(field)
44+
index_suffix = SplunkDeferredORRegularExpression.get_index_suffix(field)
45+
self.template = (
46+
'rex field={field} "(?<{field}Match'
47+
+ index_suffix
48+
+ '>{value})"\n| eval {field}Condition'
49+
+ index_suffix
50+
+ "=if(isnotnull({field}Match"
51+
+ index_suffix
52+
+ '), "true", "false")'
53+
)
54+
return super().__init__(state, field, arg)
55+
56+
@classmethod
57+
def add_field(cls, field):
58+
cls.field_counts[field] = (
59+
cls.field_counts.get(field, 0) + 1
60+
) # increment the field count
61+
62+
@classmethod
63+
def get_index_suffix(cls, field):
64+
65+
index_suffix = cls.field_counts.get(field, 0)
66+
if index_suffix == 1:
67+
# return nothing for the first field use
68+
return ""
69+
return str(index_suffix)
70+
71+
@classmethod
72+
def reset(cls):
73+
cls.field_counts = {}
74+
75+
3376
class SplunkDeferredCIDRExpression(DeferredTextQueryExpression):
3477
template = 'where {op}cidrmatch("{value}", {field})'
3578
operators = {
@@ -111,19 +154,23 @@ class SplunkBackend(TextQueryBackend):
111154
# Correlations
112155
correlation_methods: ClassVar[Dict[str, str]] = {
113156
"stats": "Correlation using stats command (more efficient, static time window)",
114-
#"transaction": "Correlation using transaction command (less efficient, sliding time window",
157+
# "transaction": "Correlation using transaction command (less efficient, sliding time window",
115158
}
116159
default_correlation_method: ClassVar[str] = "stats"
117-
default_correlation_query: ClassVar[str] = {"stats": "{search}\n\n{aggregate}\n\n{condition}"}
160+
default_correlation_query: ClassVar[str] = {
161+
"stats": "{search}\n\n{aggregate}\n\n{condition}"
162+
}
118163

119164
correlation_search_single_rule_expression: ClassVar[str] = "{query}"
120165
correlation_search_multi_rule_expression: ClassVar[str] = "| multisearch\n{queries}"
121-
correlation_search_multi_rule_query_expression: ClassVar[
122-
str
123-
] = '[ search {query} | eval event_type="{ruleid}"{normalization} ]'
166+
correlation_search_multi_rule_query_expression: ClassVar[str] = (
167+
'[ search {query} | eval event_type="{ruleid}"{normalization} ]'
168+
)
124169
correlation_search_multi_rule_query_expression_joiner: ClassVar[str] = "\n"
125170

126-
correlation_search_field_normalization_expression: ClassVar[str] = " | rename {field} as {alias}"
171+
correlation_search_field_normalization_expression: ClassVar[str] = (
172+
" | rename {field} as {alias}"
173+
)
127174
correlation_search_field_normalization_expression_joiner: ClassVar[str] = ""
128175

129176
event_count_aggregation_expression: ClassVar[Dict[str, str]] = {
@@ -190,11 +237,23 @@ def convert_condition_field_eq_val_re(
190237
state: "sigma.conversion.state.ConversionState",
191238
) -> SplunkDeferredRegularExpression:
192239
"""Defer regular expression matching to pipelined regex command after main search expression."""
240+
193241
if cond.parent_condition_chain_contains(ConditionOR):
194-
raise SigmaFeatureNotSupportedByBackendError(
195-
"ORing regular expressions is not yet supported by Splunk backend",
196-
source=cond.source,
242+
# adding the deferred to the state
243+
SplunkDeferredORRegularExpression(
244+
state,
245+
cond.field,
246+
super().convert_condition_field_eq_val_re(cond, state),
247+
).postprocess(None, cond)
248+
249+
cond_true = ConditionFieldEqualsValueExpression(
250+
cond.field
251+
+ "Condition"
252+
+ str(SplunkDeferredORRegularExpression.get_index_suffix(cond.field)),
253+
SigmaString("true"),
197254
)
255+
# returning fieldX=true
256+
return super().convert_condition_field_eq_val_str(cond_true, state)
198257
return SplunkDeferredRegularExpression(
199258
state, cond.field, super().convert_condition_field_eq_val_re(cond, state)
200259
).postprocess(None, cond)
@@ -214,6 +273,47 @@ def convert_condition_field_eq_val_cidr(
214273
state, cond.field, super().convert_condition_field_eq_val_cidr(cond, state)
215274
).postprocess(None, cond)
216275

276+
def finalize_query(
277+
self,
278+
rule: SigmaRule,
279+
query: Union[str, DeferredQueryExpression],
280+
index: int,
281+
state: ConversionState,
282+
output_format: str,
283+
) -> Union[str, DeferredQueryExpression]:
284+
285+
if state.has_deferred():
286+
deferred_regex_or_expressions = []
287+
no_regex_oring_deferred_expressions = []
288+
289+
for index, deferred_expression in enumerate(state.deferred):
290+
291+
if type(deferred_expression) == SplunkDeferredORRegularExpression:
292+
deferred_regex_or_expressions.append(
293+
deferred_expression.finalize_expression()
294+
)
295+
else:
296+
no_regex_oring_deferred_expressions.append(deferred_expression)
297+
298+
if len(deferred_regex_or_expressions) > 0:
299+
SplunkDeferredORRegularExpression.reset() # need to reset class for potential future conversions
300+
# remove deferred oring regex expressions from the state
301+
# as they will be taken into account by the super().finalize_query
302+
state.deferred = no_regex_oring_deferred_expressions
303+
304+
return super().finalize_query(
305+
rule,
306+
self.deferred_start
307+
+ self.deferred_separator.join(deferred_regex_or_expressions)
308+
+ "\n| search "
309+
+ query,
310+
index,
311+
state,
312+
output_format,
313+
)
314+
315+
return super().finalize_query(rule, query, index, state, output_format)
316+
217317
def finalize_query_default(
218318
self, rule: SigmaRule, query: str, index: int, state: ConversionState
219319
) -> str:

tests/test_backend_splunk.py

+11-6
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,7 @@ def test_splunk_regex_query(splunk_backend: SplunkBackend):
172172

173173

174174
def test_splunk_regex_query_implicit_or(splunk_backend: SplunkBackend):
175-
with pytest.raises(
176-
SigmaFeatureNotSupportedByBackendError, match="ORing regular expressions"
177-
):
175+
assert (
178176
splunk_backend.convert(
179177
SigmaCollection.from_yaml(
180178
"""
@@ -194,12 +192,15 @@ def test_splunk_regex_query_implicit_or(splunk_backend: SplunkBackend):
194192
"""
195193
)
196194
)
195+
== [
196+
'\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"'
197+
]
198+
)
197199

198200

199201
def test_splunk_regex_query_explicit_or(splunk_backend: SplunkBackend):
200-
with pytest.raises(
201-
SigmaFeatureNotSupportedByBackendError, match="ORing regular expressions"
202-
):
202+
203+
assert (
203204
splunk_backend.convert(
204205
SigmaCollection.from_yaml(
205206
"""
@@ -217,6 +218,10 @@ def test_splunk_regex_query_explicit_or(splunk_backend: SplunkBackend):
217218
"""
218219
)
219220
)
221+
== [
222+
'\n| rex field=fieldA "(?<fieldAMatch>foo.*bar)"\n| eval fieldACondition=if(isnotnull(fieldAMatch), "true", "false")\n| rex field=fieldB "(?<fieldBMatch>boo.*foo)"\n| eval fieldBCondition=if(isnotnull(fieldBMatch), "true", "false")\n| search fieldACondition="true" OR fieldBCondition="true"'
223+
]
224+
)
220225

221226

222227
def test_splunk_single_regex_query(splunk_backend: SplunkBackend):

0 commit comments

Comments
 (0)