Source code for suricata_check.checkers.styleguide.sid

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