1
1
import re
2
2
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
5
6
from sigma .conversion .deferred import DeferredTextQueryExpression
6
7
from sigma .conditions import (
7
8
ConditionFieldEqualsValueExpression ,
10
11
ConditionNOT ,
11
12
ConditionItem ,
12
13
)
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
15
16
from sigma .pipelines .splunk .splunk import (
16
17
splunk_sysmon_process_creation_cim_mapping ,
17
18
splunk_windows_registry_cim_mapping ,
18
19
splunk_windows_file_event_cim_mapping ,
19
20
)
20
21
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
22
23
23
24
24
25
class SplunkDeferredRegularExpression (DeferredTextQueryExpression ):
@@ -30,6 +31,48 @@ class SplunkDeferredRegularExpression(DeferredTextQueryExpression):
30
31
default_field = "_raw"
31
32
32
33
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
+
33
76
class SplunkDeferredCIDRExpression (DeferredTextQueryExpression ):
34
77
template = 'where {op}cidrmatch("{value}", {field})'
35
78
operators = {
@@ -111,19 +154,23 @@ class SplunkBackend(TextQueryBackend):
111
154
# Correlations
112
155
correlation_methods : ClassVar [Dict [str , str ]] = {
113
156
"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",
115
158
}
116
159
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
+ }
118
163
119
164
correlation_search_single_rule_expression : ClassVar [str ] = "{query}"
120
165
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
+ )
124
169
correlation_search_multi_rule_query_expression_joiner : ClassVar [str ] = "\n "
125
170
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
+ )
127
174
correlation_search_field_normalization_expression_joiner : ClassVar [str ] = ""
128
175
129
176
event_count_aggregation_expression : ClassVar [Dict [str , str ]] = {
@@ -190,11 +237,23 @@ def convert_condition_field_eq_val_re(
190
237
state : "sigma.conversion.state.ConversionState" ,
191
238
) -> SplunkDeferredRegularExpression :
192
239
"""Defer regular expression matching to pipelined regex command after main search expression."""
240
+
193
241
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" ),
197
254
)
255
+ # returning fieldX=true
256
+ return super ().convert_condition_field_eq_val_str (cond_true , state )
198
257
return SplunkDeferredRegularExpression (
199
258
state , cond .field , super ().convert_condition_field_eq_val_re (cond , state )
200
259
).postprocess (None , cond )
@@ -214,6 +273,47 @@ def convert_condition_field_eq_val_cidr(
214
273
state , cond .field , super ().convert_condition_field_eq_val_cidr (cond , state )
215
274
).postprocess (None , cond )
216
275
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
+
217
317
def finalize_query_default (
218
318
self , rule : SigmaRule , query : str , index : int , state : ConversionState
219
319
) -> str :
0 commit comments