Source code for suricata_check.checkers.styleguide.sid

  1"""`SidChecker`."""
  2
  3import logging
  4from collections.abc import Mapping, Sequence
  5from typing import Optional
  6
  7from suricata_check.checkers.interface import CheckerInterface
  8from suricata_check.utils.checker import get_rule_option
  9from suricata_check.utils.checker_typing import ISSUES_TYPE, Issue, Rule
 10from suricata_check.utils.regex_provider import get_regex_provider
 11
 12SID_ALLOCATION: Mapping[str, Sequence[tuple[int, int]]] = {
 13    "local": [(1000000, 1999999)],
 14    "ET OPEN": [
 15        (2000000, 2103999),
 16        (2400000, 2609999),
 17    ],
 18    "ET": [(2700000, 2799999)],
 19    "ETPRO": [(2800000, 2899999)],
 20}
 21
 22_regex_provider = get_regex_provider()
 23
 24_MSG_PREFIX_REGEX = _regex_provider.compile(r"^\"([A-Z0-9 ]*).*\"$")
 25
 26_logger = logging.getLogger(__name__)
 27
 28
[docs] 29class SidChecker(CheckerInterface): 30 """The `SidChecker` contains several checks based on the Suricata SID allocation. 31 32 Specifically, the `SidChecker` checks for the following: 33 S300: Allocation to reserved SID range, whereas no range is reserved for the rule. 34 35 S301: Allocation to unallocated SID range, whereas local range should be used. 36 37 S302: Allocation to wrong reserved SID range, whereas another reserved range should be used. 38 39 S303: Allocation to unallocated SID range, whereas a reserved range should be used. 40 """ 41 42 codes = { 43 "S300": {"severity": logging.INFO}, 44 "S301": {"severity": logging.INFO}, 45 "S302": {"severity": logging.INFO}, 46 "S303": {"severity": logging.INFO}, 47 } 48 49 def _check_rule( 50 self: "SidChecker", 51 rule: Rule, 52 ) -> ISSUES_TYPE: 53 issues: ISSUES_TYPE = [] 54 55 sid = get_rule_option(rule, "sid") 56 msg = get_rule_option(rule, "msg") 57 58 if sid is not None and msg is not None: 59 sid = int(sid) 60 range_name = self.__get_range_name(sid, SID_ALLOCATION) 61 prefix = self.__get_msg_prefix(msg) 62 63 if ( 64 prefix not in SID_ALLOCATION.keys() 65 and range_name is not None 66 and range_name != "local" 67 ): 68 issues.append( 69 Issue( 70 code="S300", 71 message=f"""\ 72Allocation to reserved SID range, whereas no range is reserved for the rule. 73Consider using an sid in one of the following ranges: {SID_ALLOCATION["local"]}.\ 74""", 75 ), 76 ) 77 78 if prefix not in SID_ALLOCATION.keys() and range_name is None: 79 issues.append( 80 Issue( 81 code="S301", 82 message=f"""\ 83Allocation to unallocated SID range, whereas local range should be used. 84Consider using an sid in one of the following ranges: {SID_ALLOCATION["local"]}.\ 85""", 86 ), 87 ) 88 89 if prefix in SID_ALLOCATION.keys() and ( 90 range_name is not None 91 and not (prefix + " ").startswith(range_name + " ") 92 and not (range_name + " ").startswith(prefix + " ") 93 ): 94 issues.append( 95 Issue( 96 code="S302", 97 message=f"""\ 98Allocation to wrong reserved SID range, whereas another reserved range should be used. 99Consider using an sid in one of the following ranges: {SID_ALLOCATION[prefix]}.\ 100""", 101 ), 102 ) 103 104 if prefix in SID_ALLOCATION.keys() and range_name is None: 105 issues.append( 106 Issue( 107 code="S303", 108 message=f"""\ 109Allocation to unallocated SID range, whereas a reserved range should be used. 110Consider using an sid in one of the following ranges: {SID_ALLOCATION[prefix]}.\ 111""", 112 ), 113 ) 114 115 return issues 116 117 @staticmethod 118 def __in_range(sid: int, sid_range: Sequence[tuple[int, int]]) -> bool: 119 for start, end in sid_range: 120 if start <= sid <= end: 121 return True 122 123 return False 124 125 @staticmethod 126 def __get_range_name( 127 sid: int, 128 ranges: Mapping[str, Sequence[tuple[int, int]]], 129 ) -> Optional[str]: 130 for range_name, sid_range in ranges.items(): 131 for start, end in sid_range: 132 if start <= sid <= end: 133 _logger.debug("Detected sid from range: %s", range_name) 134 return range_name 135 return None 136 137 @staticmethod 138 def __get_msg_prefix(msg: str) -> str: 139 match = _MSG_PREFIX_REGEX.match(msg) 140 assert match is not None 141 142 parts = match.group(1).strip().split(" ") 143 prefix: str = "" 144 for i in list(reversed(range(len(parts)))): 145 prefix = " ".join(parts[: i + 1]) 146 if prefix in SID_ALLOCATION.keys() or " " not in prefix: 147 break 148 149 _logger.debug("Detected prefix: %s", prefix) 150 151 return prefix