1"""`OrderChecker`."""
2
3import logging
4from types import MappingProxyType
5
6from suricata_check.checkers.interface import CheckerInterface
7from suricata_check.utils.checker import (
8 are_rule_options_put_before,
9 count_rule_options,
10 get_rule_keyword_sequences,
11 get_rule_option_position,
12 is_rule_option_always_put_before,
13 is_rule_option_first,
14 is_rule_option_put_before,
15 is_rule_option_set,
16)
17from suricata_check.utils.checker_typing import ISSUES_TYPE, Issue
18from suricata_check.utils.regex import (
19 ALL_DETECTION_KEYWORDS,
20 ALL_TRANSFORMATION_KEYWORDS,
21 BUFFER_KEYWORDS,
22 CONTENT_KEYWORDS,
23 FLOW_STREAM_KEYWORDS,
24 MODIFIER_KEYWORDS,
25 OTHER_PAYLOAD_KEYWORDS,
26 PERFORMANCE_DETECTION_OPTIONS,
27 POINTER_MOVEMENT_KEYWORDS,
28 SIZE_KEYWORDS,
29 TRANSFORMATION_KEYWORDS,
30 get_rule_body,
31)
32from suricata_check.utils.regex_provider import get_regex_provider
33from suricata_check.utils.rule import Rule
34
35_regex_provider = get_regex_provider()
36
37
38# Regular expressions are placed here such that they are compiled only once.
39# This has a significant impact on the performance.
40REGEX_S210 = _regex_provider.compile(
41 r"^\(.*content\s*:.*;\s*content\s*:.*;.*(depth|offset)\s*:.*\)$",
42)
43
44
[docs]
45class OrderChecker(CheckerInterface):
46 """The `OrderChecker` contains several checks on the ordering Suricata options.
47
48 Note that the correct ordering of detection options is as follows:
49 1. Buffer
50 2. Size
51 3. Transformation
52 4. Content
53 5. Pointer movement
54 6. Fast pattern
55 7. Nocase
56 8. Other payload options
57
58 Codes S200-S209 report on the non-standard ordering of common options.
59
60 Codes S210-S219 report on the non-standard ordering of content matches.
61
62 Codes S220-S229 report on the non-standard ordering of flow options.
63
64 Codes S230-S239 report on the non-standard ordering of detection options.
65
66 Codes S240-S249 report on the non-standard ordering of threshold options.
67 """
68
69 codes = MappingProxyType(
70 {
71 "S200": {"severity": logging.INFO},
72 "S201": {"severity": logging.INFO},
73 "S202": {"severity": logging.INFO},
74 "S203": {"severity": logging.INFO},
75 "S204": {"severity": logging.INFO},
76 "S205": {"severity": logging.INFO},
77 "S206": {"severity": logging.INFO},
78 "S207": {"severity": logging.INFO},
79 "S208": {"severity": logging.INFO},
80 "S210": {"severity": logging.INFO},
81 "S211": {"severity": logging.INFO},
82 "S212": {"severity": logging.INFO},
83 "S220": {"severity": logging.INFO},
84 "S221": {"severity": logging.INFO},
85 "S222": {"severity": logging.INFO},
86 "S223": {"severity": logging.INFO},
87 "S224": {"severity": logging.INFO},
88 "S230": {"severity": logging.INFO},
89 "S231": {"severity": logging.INFO},
90 "S232": {"severity": logging.INFO},
91 "S233": {"severity": logging.INFO},
92 "S234": {"severity": logging.INFO},
93 "S235": {"severity": logging.INFO},
94 "S236": {"severity": logging.INFO},
95 "S240": {"severity": logging.INFO},
96 "S241": {"severity": logging.INFO},
97 },
98 )
99
100 def _check_rule( # noqa: C901, PLR0912, PLR0915
101 self: "OrderChecker",
102 rule: Rule,
103 ) -> ISSUES_TYPE:
104 issues: ISSUES_TYPE = []
105
106 body = get_rule_body(rule)
107
108 if is_rule_option_first(rule, "msg") is not True:
109 issues.append(
110 Issue(
111 code="S200",
112 message="""The rule body does not have msg as the first option.
113Consider reording to make msg the first option.""",
114 ),
115 )
116
117 if is_rule_option_put_before(rule, "reference", ("content", "pcre")) is True:
118 issues.append(
119 Issue(
120 code="S201",
121 message="""The rule body contains the reference option before the detection logic.
122Consider reording to put the detection logic directly after the msg option.""",
123 ),
124 )
125
126 if is_rule_option_put_before(rule, "classtype", ("reference",)) is True:
127 issues.append(
128 Issue(
129 code="S202",
130 message="""The rule body contains the classtype option before the reference option.
131Consider reording to put the classtype option directly after the reference option.""",
132 ),
133 )
134
135 if is_rule_option_put_before(rule, "classtype", ("content", "pcre")) is True:
136 issues.append(
137 Issue(
138 code="S203",
139 message="""The rule body contains the classtype option before the detection logic.
140Consider reording to put the classtype option directly after the detection logic.""",
141 ),
142 )
143
144 if is_rule_option_put_before(rule, "sid", ("classtype",)) is True:
145 issues.append(
146 Issue(
147 code="S204",
148 message="""The rule body contains the sid option before the classtype option.
149Consider reording to put the sid option directly after the classtype option.""",
150 ),
151 )
152
153 if is_rule_option_put_before(rule, "sid", ("reference",)) is True:
154 issues.append(
155 Issue(
156 code="S205",
157 message="""The rule body contains the sid option before the reference option.
158Consider reording to put the sid option directly after the reference option.""",
159 ),
160 )
161
162 if is_rule_option_put_before(rule, "sid", ("content", "pcre")) is True:
163 issues.append(
164 Issue(
165 code="S206",
166 message="""The rule body contains the sid option before the detection logic.
167Consider reording to put the sid option directly after the detection logic.""",
168 ),
169 )
170
171 if is_rule_option_put_before(rule, "rev", ("sid",)) is True:
172 issues.append(
173 Issue(
174 code="S207",
175 message="""The rule body contains the rev option before the sid option.
176Consider reording to put the rev option directly after the sid option.""",
177 ),
178 )
179
180 if is_rule_option_put_before(rule, "metadata", ("sid", "rev")) is True:
181 issues.append(
182 Issue(
183 code="S208",
184 message="""The rule body contains does not have the metadata option as the last option.
185Consider making metadata the last option.""",
186 ),
187 )
188
189 if (
190 REGEX_S210.match(
191 body,
192 )
193 is not None
194 ):
195 issues.append(
196 Issue(
197 code="S210",
198 message="""The rule body contains a content matches modified by depth or offset \
199that is not the first content match.
200Consider moving the modified content match to the beginning of the detection options.""",
201 ),
202 )
203
204 if count_rule_options(rule, "depth") > 1:
205 issues.append(
206 Issue(
207 code="S211",
208 message="""The rule body contains more than one content matche modified by depth.
209Consider making the second content match relative to the first using the within option.""",
210 ),
211 )
212
213 if count_rule_options(rule, "offset") > 1:
214 issues.append(
215 Issue(
216 code="S212",
217 message="""The rule body contains more than one content matche modified by offset.
218Consider making the second content match relative to the first using the distance option.""",
219 ),
220 )
221
222 if (
223 is_rule_option_set(rule, "flow")
224 and get_rule_option_position(rule, "flow") != 1
225 ):
226 issues.append(
227 Issue(
228 code="S220",
229 message="""The rule flow option is set but not directly following the msg option.
230Consider moving the flow option to directly after the msg option.""",
231 ),
232 )
233
234 if (
235 is_rule_option_always_put_before(
236 rule,
237 "flow",
238 FLOW_STREAM_KEYWORDS,
239 )
240 is False
241 ):
242 issues.append(
243 Issue(
244 code="S221",
245 message="""The rule contains flow or stream keywords before the flow option in the rule body.
246Consider moving the flow option to before the flow and/or stream keywords.""",
247 ),
248 )
249
250 if (
251 are_rule_options_put_before(
252 rule,
253 ("content", "pcre"),
254 FLOW_STREAM_KEYWORDS,
255 )
256 is True
257 ):
258 issues.append(
259 Issue(
260 code="S222",
261 message="""The rule contains flow or stream keywords after content buffers or detection logic.
262Consider moving the flow and/or stream keywords to before content buffers and detection options.""",
263 ),
264 )
265
266 if (
267 is_rule_option_put_before(
268 rule,
269 "urilen",
270 FLOW_STREAM_KEYWORDS,
271 )
272 is True
273 ):
274 issues.append(
275 Issue(
276 code="S223",
277 message="""The rule contains the urilen option before the flow or stream keywords in the rule body.
278Consider moving the urilen option to after the flow and/or stream keywords.""",
279 ),
280 )
281
282 if (
283 is_rule_option_always_put_before(
284 rule,
285 "urilen",
286 ("content", "pcre"),
287 )
288 is False
289 ):
290 issues.append(
291 Issue(
292 code="S224",
293 message="""The rule contains the urilen option after content buffers or detection logic.
294Consider moving the urilen option to before content buffers and detection options.""",
295 ),
296 )
297
298 # Detects pointer movement before any content or buffer option or between a buffer and a content option.
299 for sequence in get_rule_keyword_sequences(
300 rule,
301 seperator_keywords=CONTENT_KEYWORDS,
302 ):
303 if (
304 are_rule_options_put_before(
305 rule,
306 POINTER_MOVEMENT_KEYWORDS,
307 set(CONTENT_KEYWORDS).union(BUFFER_KEYWORDS),
308 sequence=sequence,
309 )
310 is True
311 ):
312 issues.append(
313 Issue(
314 code="S230",
315 message="""The rule contains pointer movement before the content option in sequence {}.
316Consider moving the pointer movement options to after the content option.""".format(
317 sequence,
318 ),
319 ),
320 )
321
322 # Detects fast_pattern before any content or buffer option or between a buffer and a content option.
323 for sequence in get_rule_keyword_sequences(
324 rule,
325 seperator_keywords=CONTENT_KEYWORDS,
326 ):
327 if (
328 is_rule_option_put_before(
329 rule,
330 "fast_pattern",
331 set(SIZE_KEYWORDS)
332 .union(ALL_TRANSFORMATION_KEYWORDS)
333 .union(CONTENT_KEYWORDS)
334 .union(POINTER_MOVEMENT_KEYWORDS),
335 sequence=sequence,
336 )
337 is True
338 ):
339 issues.append(
340 Issue(
341 code="S231",
342 message="""The rule contains the fast_pattern option before \
343size options, transformation options, the content option or pointer movement options in sequence {}.
344Consider moving the fast_pattern option to after \
345size options, transformation options, the content option or pointer movement options.""".format(
346 sequence,
347 ),
348 ),
349 )
350
351 # Detects no_case before any content or buffer option or between a buffer and a content option.
352 for sequence in get_rule_keyword_sequences(
353 rule,
354 seperator_keywords=CONTENT_KEYWORDS,
355 ):
356 if (
357 is_rule_option_put_before(
358 rule,
359 "nocase",
360 set(SIZE_KEYWORDS)
361 .union(ALL_TRANSFORMATION_KEYWORDS)
362 .union(CONTENT_KEYWORDS)
363 .union(POINTER_MOVEMENT_KEYWORDS)
364 .union(PERFORMANCE_DETECTION_OPTIONS),
365 sequence=sequence,
366 )
367 is True
368 ):
369 issues.append(
370 Issue(
371 code="S232",
372 message="""The rule contains the nocase option before \
373size options, transformation options, the content option, pointer movement options, or fast_pattern option in sequence {}.
374Consider moving the nocase option to after \
375size options, transformation options, the content option, pointer movement options, or fast_pattern option.""".format(
376 sequence,
377 ),
378 ),
379 )
380
381 # Detects modifier options before any content or buffer option or between a buffer and a content option.
382 for sequence in get_rule_keyword_sequences(
383 rule,
384 seperator_keywords=CONTENT_KEYWORDS,
385 ):
386 if (
387 are_rule_options_put_before(
388 rule,
389 MODIFIER_KEYWORDS,
390 set(CONTENT_KEYWORDS),
391 sequence=sequence,
392 )
393 is True
394 ):
395 issues.append(
396 Issue(
397 code="S233",
398 message="""The rule contains modifier options before the content option.
399Consider moving the modifier options to after the content option.""",
400 ),
401 )
402
403 # Detects other detection options before any content or buffer option or between a buffer and a content option.
404 for sequence in get_rule_keyword_sequences(
405 rule,
406 seperator_keywords=CONTENT_KEYWORDS,
407 ):
408 if (
409 are_rule_options_put_before(
410 rule,
411 OTHER_PAYLOAD_KEYWORDS,
412 set(CONTENT_KEYWORDS).union(BUFFER_KEYWORDS),
413 sequence=sequence,
414 )
415 is True
416 ):
417 issues.append(
418 Issue(
419 code="S234",
420 message="""The rule contains other detection options before \
421size options, transformation options, the content option, pointer movement options, nocase option, or fast_pattern option.
422Consider moving the other detection options to after \
423size options, transformation options, the content option, pointer movement options, nocase option, or fast_pattern option.""",
424 ),
425 )
426
427 # Detects size options after any transformation options, content option or other detection options.
428 for sequence in get_rule_keyword_sequences(
429 rule,
430 seperator_keywords=CONTENT_KEYWORDS,
431 ):
432 if (
433 are_rule_options_put_before(
434 rule,
435 set(TRANSFORMATION_KEYWORDS)
436 .union(CONTENT_KEYWORDS)
437 .union(OTHER_PAYLOAD_KEYWORDS),
438 SIZE_KEYWORDS,
439 sequence=sequence,
440 )
441 is True
442 ):
443 issues.append(
444 Issue(
445 code="S235",
446 message="""The rule contains other size options after \
447any transformation options, content option or other detection options.
448Consider moving the size options to after any transformation options, content option or other detection options""",
449 ),
450 )
451
452 # Detects transformation options after any content option or other detection options.
453 for sequence in get_rule_keyword_sequences(
454 rule,
455 seperator_keywords=CONTENT_KEYWORDS,
456 ):
457 if (
458 are_rule_options_put_before(
459 rule,
460 set(CONTENT_KEYWORDS).union(OTHER_PAYLOAD_KEYWORDS),
461 TRANSFORMATION_KEYWORDS,
462 sequence=sequence,
463 )
464 is True
465 ):
466 issues.append(
467 Issue(
468 code="S236",
469 message="""The rule contains other transformation options after \
470any content option or other detection options.
471Consider moving the transformation options to after any content option or other detection options""",
472 ),
473 )
474
475 if (
476 is_rule_option_put_before(
477 rule,
478 "threshold",
479 ALL_DETECTION_KEYWORDS,
480 )
481 is True
482 ):
483 issues.append(
484 Issue(
485 code="S240",
486 message="""The rule contains the threshold option before some detection option.
487Consider moving the threshold option to after the detection options.""",
488 ),
489 )
490
491 if (
492 is_rule_option_always_put_before(
493 rule,
494 "threshold",
495 ("reference", "sid"),
496 )
497 is False
498 ):
499 issues.append(
500 Issue(
501 code="S241",
502 message="""The rule contains the threshold option after the reference and/or sid option.
503Consider moving the threshold option to before the reference and sid options.""",
504 ),
505 )
506
507 return issues