Source code for suricata_check.checkers.styleguide.msg

  1"""`MsgChecker`."""
  2
  3import logging
  4
  5import idstools.rule
  6
  7from suricata_check.checkers.interface import CheckerInterface
  8from suricata_check.utils.checker import (
  9    get_rule_option,
 10    is_rule_option_equal_to_regex,
 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 import get_regex_provider
 16
 17_regex_provider = get_regex_provider()
 18
 19_S400_REGEX = _regex_provider.compile(r"""^"[A-Z0-9 ]+ [A-Z0-9_]+ .*"$""")
 20_MALWARE_REGEX = _regex_provider.compile(r"^.*(malware).*$", _regex_provider.IGNORECASE)
 21_S401_REGEX = _regex_provider.compile(r"""^".* [a-zA-Z0-9]+/[a-zA-Z0-9]+ .*"$""")
 22_VAGUE_KEYWORDS = ("possible", "unknown")
 23_S402_REGEX = _regex_provider.compile(
 24    r"^.*({}).*$".format("|".join(_VAGUE_KEYWORDS)), _regex_provider.IGNORECASE
 25)
 26_UNDESIRABLE_DATE_REGEXES = (
 27    _regex_provider.compile(r"^.*(\d{4}/\d{2}/\d{2}).*$", _regex_provider.IGNORECASE),
 28    _regex_provider.compile(r"^.*(\d{4}-[2-9]\d-\d{2}).*$", _regex_provider.IGNORECASE),
 29)  # Desirable format is ISO (YYYY-MM-DD)
 30_S404_REGEX = _regex_provider.compile(
 31    r"^.*(C2|C&C|Command and Control|Command & Control).*$", _regex_provider.IGNORECASE
 32)
 33_S405_REGEX = _regex_provider.compile(
 34    r"^.*(Go|MSIL|ELF64|MSIL|JS|Win32|DOS|Amiga|C64|Plan9).*$",
 35    _regex_provider.IGNORECASE,
 36)
 37_S406_REGEX = _regex_provider.compile(
 38    r"^.*((\w+\.)+[a-z]{2,}).*$",
 39    _regex_provider.IGNORECASE,
 40)
 41_S407_REGEX = _regex_provider.compile(
 42    r"^.*((\w+\[\.\])+[a-z]{2,}).*$",
 43    _regex_provider.IGNORECASE,
 44)
 45_S408_REGEX = _regex_provider.compile(
 46    r"^.*((\w+\. )+[a-z]{2,}).*$",
 47    _regex_provider.IGNORECASE,
 48)
 49
 50_logger = logging.getLogger(__name__)
 51
 52
[docs] 53class MsgChecker(CheckerInterface): 54 """The `MsgChecker` contains several checks based for the Msg option in Suricata rules. 55 56 Codes S400-S410 report on non-standard `msg` fields. 57 """ 58 59 codes = { 60 "S400": {"severity": logging.INFO}, 61 "S401": {"severity": logging.INFO}, 62 "S402": {"severity": logging.INFO}, 63 "S403": {"severity": logging.INFO}, 64 "S404": {"severity": logging.INFO}, 65 "S405": {"severity": logging.INFO}, 66 "S406": {"severity": logging.WARNING}, 67 "S407": {"severity": logging.INFO}, 68 "S408": {"severity": logging.INFO}, 69 "S409": {"severity": logging.INFO}, 70 } 71 72 def _check_rule( # noqa: C901 73 self: "MsgChecker", 74 rule: idstools.rule.Rule, 75 ) -> ISSUES_TYPE: 76 issues: ISSUES_TYPE = [] 77 78 if is_rule_option_set(rule, "msg") and not is_rule_option_equal_to_regex( 79 rule, "msg", _S400_REGEX 80 ): 81 issues.append( 82 Issue( 83 code="S400", 84 message="""\ 85The rule has a non-standard format for the msg field. 86Consider changing the msg field to `RULESET CATEGORY Description`.\ 87""", 88 ), 89 ) 90 91 if ( 92 is_rule_option_set(rule, "msg") 93 and self.__desribes_malware(rule) 94 and not is_rule_option_equal_to_regex(rule, "msg", _S401_REGEX) 95 ): 96 issues.append( 97 Issue( 98 code="S401", 99 message="""\ 100The rule describes malware but does not specify the paltform or malware family in the msg field. 101Consider changing the msg field to include `Platform/malfamily`.\ 102""", 103 ), 104 ) 105 106 if not ( 107 is_rule_option_set(rule, "noalert") 108 or is_rule_suboption_set(rule, "flowbits", "noalert") 109 ) and is_rule_option_equal_to_regex(rule, "msg", _S402_REGEX): 110 issues.append( 111 Issue( 112 code="S402", 113 message="""\ 114The rule uses vague keywords such as possible or unknown in the msg field. 115Consider rephrasing to provide a more clear message for interpreting generated alerts.\ 116""", 117 ), 118 ) 119 120 for regex in _UNDESIRABLE_DATE_REGEXES: 121 if is_rule_option_equal_to_regex(rule, "msg", regex): 122 issues.append( 123 Issue( 124 code="S403", 125 message="""\ 126The rule uses a non-ISO date in the msg field. 127Consider reformatting the date to ISO format (YYYY-MM-DD).\ 128""", 129 ), 130 ) 131 break 132 133 if is_rule_option_equal_to_regex(rule, "msg", _S404_REGEX): 134 issues.append( 135 Issue( 136 code="S404", 137 message="""\ 138The rule uses a different way of writing CnC (Command & Control) in the msg field. 139Consider writing CnC instead.\ 140""", 141 ), 142 ) 143 144 if self.__desribes_malware(rule) and not is_rule_option_equal_to_regex( 145 rule, "msg", _S405_REGEX 146 ): 147 issues.append( 148 Issue( 149 code="S405", 150 message="""\ 151The rule likely detects malware but does not specify the file type in the msg field. 152Consider specifying a file type such as `DOS` or `ELF64`.\ 153""", 154 ), 155 ) 156 157 if is_rule_option_equal_to_regex(rule, "msg", _S406_REGEX): 158 issues.append( 159 Issue( 160 code="S406", 161 message="""\ 162The rule specifies a domain name without escaping the label seperators. 163Consider escaping the domain names by putting a space before the dot like `foo .bar` to prevent information leaks.\ 164""", 165 ), 166 ) 167 168 if is_rule_option_equal_to_regex(rule, "msg", _S407_REGEX): 169 issues.append( 170 Issue( 171 code="S407", 172 message="""\ 173The rule specifies a domain name and escapes it in a non-standard way in the msg field. 174Consider escaping the domain names by putting a space before the dot like `foo .bar`.\ 175""", 176 ), 177 ) 178 179 if is_rule_option_equal_to_regex(rule, "msg", _S408_REGEX): 180 issues.append( 181 Issue( 182 code="S408", 183 message="""\ 184The rule specifies a domain name and escapes it in a non-standard way in the msg field. 185Consider escaping the domain names by putting a space before the dot like `foo .bar`.\ 186""", 187 ), 188 ) 189 190 # Note that all characters under 128 are ASCII 191 if is_rule_option_set(rule, "msg") and any( 192 ord(c) > 128 # noqa: PLR2004 193 for c in get_rule_option(rule, "msg") # type: ignore reportOptionalIterable 194 ): 195 issues.append( 196 Issue( 197 code="S409", 198 message="""\ 199The rule uses non-ASCII characters in the msg field. 200Consider removing non-ASCII characters.\ 201""", 202 ), 203 ) 204 205 return issues 206 207 @staticmethod 208 def __desribes_malware(rule: idstools.rule.Rule) -> bool: 209 if is_rule_suboption_set(rule, "metadata", "malware_family"): 210 return True 211 212 if is_rule_option_equal_to_regex(rule, "msg", _MALWARE_REGEX): 213 return True 214 215 _logger.debug("Rule does not describe malware: %s", rule["raw"]) 216 217 return False