1"""`StateChecker`."""
2
3import logging
4
5import idstools.rule
6
7from suricata_check.checkers.interface import CheckerInterface
8from suricata_check.utils.checker import (
9 get_rule_suboptions,
10 is_rule_option_always_equal_to_regex,
11 is_rule_option_always_put_before,
12 is_rule_option_set,
13 is_rule_suboption_set,
14)
15from suricata_check.utils.checker_typing import ISSUES_TYPE, Issue
16from suricata_check.utils.regex import get_regex_provider
17
18_regex_provider = get_regex_provider()
19
20_S510_REGEX = _regex_provider.compile(
21 r"^((set|isset|toggle|unset|isnotset),[A-Z]+\.\w+)|(noalert)$",
22 _regex_provider.IGNORECASE,
23)
24
25_S520_REGEX = _regex_provider.compile(
26 r"^((set|isset|toggle|unset|isnotset),[A-Z]+\.\w+)|(noalert),.*$",
27 _regex_provider.IGNORECASE,
28)
29
30_logger = logging.getLogger(__name__)
31
32
[docs]
33class StateChecker(CheckerInterface):
34 """The `StateChecker` contains several checks for Suricata options relating to the connection or detection state.
35
36 Codes S500-S510 report on non-standard usages of `flow`
37 Codes S510-S520 report on non-standard usages of `flowbits`
38 Codes S520-S530 report on non-standard usages of `xbits`
39 """
40
41 codes = {
42 "S500": {"severity": logging.INFO},
43 "S501": {"severity": logging.INFO},
44 "S510": {"severity": logging.INFO},
45 "S511": {"severity": logging.INFO},
46 "S520": {"severity": logging.INFO},
47 "S521": {"severity": logging.INFO},
48 "S522": {"severity": logging.INFO},
49 }
50
51 def _check_rule(
52 self: "StateChecker",
53 rule: idstools.rule.Rule,
54 ) -> ISSUES_TYPE:
55 issues: ISSUES_TYPE = []
56
57 if is_rule_option_set(rule, "flow") and not is_rule_option_always_put_before(
58 rule,
59 "established",
60 ["to_server", "to_client", "from_server", "from_client"],
61 sequence=[suboption[0] for suboption in get_rule_suboptions(rule, "flow")],
62 ):
63 issues.append(
64 Issue(
65 code="S500",
66 message="""\
67The rule specifies the connection state after the connection direction in the `flow` option.
68Consider specifying the connection state first like `established,to_server`.\
69""",
70 ),
71 )
72
73 if is_rule_suboption_set(rule, "flow", "from_client") or is_rule_suboption_set(
74 rule, "flow", "from_server"
75 ):
76 issues.append(
77 Issue(
78 code="S501",
79 message="""\
80The rule has set `from_client` or `from_server` as a `flow` option.
81Consider using `to_client` or `to_server` instead.\
82""",
83 ),
84 )
85
86 if is_rule_option_always_equal_to_regex(rule, "flowbits", _S510_REGEX) is False:
87 issues.append(
88 Issue(
89 code="S510",
90 message="""\
91The rule sets flowbits with a non-standard name.
92Consider using `RULESET.description` as name for the flowbit.\
93""",
94 ),
95 )
96
97 if (
98 (
99 is_rule_suboption_set(rule, "flowbits", "set")
100 or is_rule_suboption_set(rule, "flowbits", "unset")
101 )
102 and not (
103 is_rule_suboption_set(rule, "flowbits", "isset")
104 or is_rule_suboption_set(rule, "flowbits", "isnotset")
105 )
106 and not (
107 is_rule_option_set(rule, "noalert")
108 or is_rule_suboption_set(rule, "flowbits", "noalert")
109 )
110 ):
111 issues.append(
112 Issue(
113 code="S511",
114 message="""\
115The rule (un)sets a flowbit but does not use the noalert option.
116Consider using the noalert option to prevent unnecessary alerts.\
117""",
118 ),
119 )
120
121 if is_rule_option_always_equal_to_regex(rule, "xbits", _S520_REGEX) is False:
122 issues.append(
123 Issue(
124 code="S520",
125 message="""\
126The rule sets xbits with a non-standard name.
127Consider using `RULESET.description` as name for the xbit.\
128""",
129 ),
130 )
131
132 if (
133 (
134 is_rule_suboption_set(rule, "xbits", "set")
135 or is_rule_suboption_set(rule, "xbits", "unset")
136 )
137 and not is_rule_option_set(rule, "noalert")
138 and not (
139 is_rule_suboption_set(rule, "xbits", "isset")
140 or is_rule_suboption_set(rule, "xbits", "isnotset")
141 )
142 ):
143 issues.append(
144 Issue(
145 code="S521",
146 message="""\
147The rule (un)sets a xbit but does not use the noalert option.
148Consider using the noalert option to prevent unnecessary alerts.\
149""",
150 ),
151 )
152
153 if (is_rule_suboption_set(rule, "xbits", "set")) and not is_rule_suboption_set(
154 rule, "xbits", "expire"
155 ):
156 issues.append(
157 Issue(
158 code="S522",
159 message="""\
160The rule sets a xbit but does not explcitly set the expire option.
161Consider setting the expire option to indicate for how long the xbit remains relevant.\
162""",
163 ),
164 )
165
166 return issues