1"""`StateChecker`."""
2
3import logging
4from types import MappingProxyType
5
6from suricata_check.checkers.interface import CheckerInterface
7from suricata_check.utils.checker import (
8 get_rule_suboptions,
9 is_rule_option_always_equal_to_regex,
10 is_rule_option_always_put_before,
11 is_rule_option_set,
12 is_rule_suboption_set,
13)
14from suricata_check.utils.checker_typing import ISSUES_TYPE, Issue
15from suricata_check.utils.regex_provider import get_regex_provider
16from suricata_check.utils.rule import Rule
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 = MappingProxyType(
42 {
43 "S500": {"severity": logging.INFO},
44 "S501": {"severity": logging.INFO},
45 "S510": {"severity": logging.INFO},
46 "S511": {"severity": logging.INFO},
47 "S520": {"severity": logging.INFO},
48 "S521": {"severity": logging.INFO},
49 "S522": {"severity": logging.INFO},
50 },
51 )
52
53 def _check_rule(
54 self: "StateChecker",
55 rule: Rule,
56 ) -> ISSUES_TYPE:
57 issues: ISSUES_TYPE = []
58
59 if is_rule_option_set(rule, "flow") and not is_rule_option_always_put_before(
60 rule,
61 "established",
62 ["to_server", "to_client", "from_server", "from_client"],
63 sequence=[suboption[0] for suboption in get_rule_suboptions(rule, "flow")],
64 ):
65 issues.append(
66 Issue(
67 code="S500",
68 message="""\
69The rule specifies the connection state after the connection direction in the `flow` option.
70Consider specifying the connection state first like `established,to_server`.\
71""",
72 ),
73 )
74
75 if is_rule_suboption_set(rule, "flow", "from_client") or is_rule_suboption_set(
76 rule,
77 "flow",
78 "from_server",
79 ):
80 issues.append(
81 Issue(
82 code="S501",
83 message="""\
84The rule has set `from_client` or `from_server` as a `flow` option.
85Consider using `to_client` or `to_server` instead.\
86""",
87 ),
88 )
89
90 if is_rule_option_always_equal_to_regex(rule, "flowbits", _S510_REGEX) is False:
91 issues.append(
92 Issue(
93 code="S510",
94 message="""\
95The rule sets flowbits with a non-standard name.
96Consider using `RULESET.description` as name for the flowbit.\
97""",
98 ),
99 )
100
101 if (
102 (
103 is_rule_suboption_set(rule, "flowbits", "set")
104 or is_rule_suboption_set(rule, "flowbits", "unset")
105 )
106 and not (
107 is_rule_suboption_set(rule, "flowbits", "isset")
108 or is_rule_suboption_set(rule, "flowbits", "isnotset")
109 )
110 and not (
111 is_rule_option_set(rule, "noalert")
112 or is_rule_suboption_set(rule, "flowbits", "noalert")
113 )
114 ):
115 issues.append(
116 Issue(
117 code="S511",
118 message="""\
119The rule (un)sets a flowbit but does not use the noalert option.
120Consider using the noalert option to prevent unnecessary alerts.\
121""",
122 ),
123 )
124
125 if is_rule_option_always_equal_to_regex(rule, "xbits", _S520_REGEX) is False:
126 issues.append(
127 Issue(
128 code="S520",
129 message="""\
130The rule sets xbits with a non-standard name.
131Consider using `RULESET.description` as name for the xbit.\
132""",
133 ),
134 )
135
136 if (
137 (
138 is_rule_suboption_set(rule, "xbits", "set")
139 or is_rule_suboption_set(rule, "xbits", "unset")
140 )
141 and not is_rule_option_set(rule, "noalert")
142 and not (
143 is_rule_suboption_set(rule, "xbits", "isset")
144 or is_rule_suboption_set(rule, "xbits", "isnotset")
145 )
146 ):
147 issues.append(
148 Issue(
149 code="S521",
150 message="""\
151The rule (un)sets a xbit but does not use the noalert option.
152Consider using the noalert option to prevent unnecessary alerts.\
153""",
154 ),
155 )
156
157 if (is_rule_suboption_set(rule, "xbits", "set")) and not is_rule_suboption_set(
158 rule,
159 "xbits",
160 "expire",
161 ):
162 issues.append(
163 Issue(
164 code="S522",
165 message="""\
166The rule sets a xbit but does not explcitly set the expire option.
167Consider setting the expire option to indicate for how long the xbit remains relevant.\
168""",
169 ),
170 )
171
172 return issues