Source code for suricata_check.checkers.styleguide._msg

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