diff --git a/detection_rules/beats.py b/detection_rules/beats.py index 01fb28fd8..88385a19e 100644 --- a/detection_rules/beats.py +++ b/detection_rules/beats.py @@ -83,12 +83,12 @@ def _flatten_schema(schema: list, prefix="") -> list: # we have what looks like zoom.zoom.*, but should actually just be zoom.*. # this is one quick heuristic to determine if a submodule nests fields at the parent. # it's probably not perfect, but we can fix other bugs as we run into them later - if len(schema) == 1 and nested_prefix == prefix + prefix: - nested_prefix = prefix + if len(schema) == 1 and nested_prefix.startswith(prefix + prefix): + nested_prefix = s["name"] + "." flattened.extend(_flatten_schema(s["fields"], prefix=nested_prefix)) elif "fields" in s: flattened.extend(_flatten_schema(s["fields"], prefix=prefix)) - elif "name" in s and "description" in s: + elif "name" in s: s = s.copy() # type is implicitly keyword if not defined # example: https://github.com/elastic/beats/blob/master/packetbeat/_meta/fields.common.yml#L7-L12 diff --git a/kql/parser.py b/kql/parser.py index 40f362423..b8e770a00 100644 --- a/kql/parser.py +++ b/kql/parser.py @@ -55,7 +55,15 @@ class BaseKqlParser(Interpreter): self.text = text self.lines = [t.rstrip("\r\n") for t in self.text.splitlines(True)] self.scoped_field = None - self.schema = schema + self.mapping_schema = schema + self.star_fields = [] + + if schema: + for field, field_type in schema.items(): + if "*" in field: + parts = field.split("*") + pattern = re.compile("^{regex}$".format(regex=".*?".join(re.escape(w) for w in parts))) + self.star_fields.append(pattern) def assert_lower_token(self, *tokens): for token in tokens: @@ -63,7 +71,7 @@ class BaseKqlParser(Interpreter): raise self.error(token, "Expected '{lower}' but got '{token}'".format(token=token, lower=str(token).lower())) def error(self, node, message, end=False, cls=KqlParseError, width=None, **kwargs): - """Generate.""" + """Generate an error exception but dont raise it.""" if kwargs: message = message.format(**kwargs) @@ -106,11 +114,13 @@ class BaseKqlParser(Interpreter): self.scoped_field = None def get_field_type(self, dotted_path, lark_tree=None): - if self.schema is not None: - if lark_tree is not None and dotted_path not in self.schema: + matches_pattern = any(regex.match(dotted_path) for regex in self.star_fields) + + if self.mapping_schema is not None: + if lark_tree is not None and dotted_path not in self.mapping_schema and not matches_pattern: raise self.error(lark_tree, "Unknown field") - return self.schema[dotted_path] + return self.mapping_schema.get(dotted_path) @staticmethod def get_literal_type(literal_value): @@ -192,17 +202,17 @@ class KqlParser(BaseKqlParser): @contextlib.contextmanager def nest(self, lark_tree): - schema = self.schema + schema = self.mapping_schema dotted_path = self.visit(lark_tree) if self.get_field_type(dotted_path, lark_tree) != "nested": raise self.error(lark_tree, "Expected a nested field") try: - self.schema = self.schema[dotted_path] + self.mapping_schema = self.mapping_schema[dotted_path] yield finally: - self.schema = schema + self.mapping_schema = schema def nested_query(self, tree): # field_tree, query_tree = tree.child_trees diff --git a/rules/microsoft-365/microsoft_365_exchange_dkim_signing_config_disabled.toml b/rules/microsoft-365/microsoft_365_exchange_dkim_signing_config_disabled.toml new file mode 100644 index 000000000..86f89d63d --- /dev/null +++ b/rules/microsoft-365/microsoft_365_exchange_dkim_signing_config_disabled.toml @@ -0,0 +1,38 @@ +[metadata] +creation_date = "2020/11/18" +maturity = "production" +updated_date = "2020/11/18" + +[rule] +author = ["Elastic"] +description = """ +Identifies when a DomainKeys Identified Mail (DKIM) signing configuration is disabled in Microsoft 365. With DKIM in +Microsoft 365, messages that are sent from Exchange Online will be cryptographically signed. This will allow the +receiving email system to validate that the messages were generated by a server that the organization authorized and not +being spoofed. +""" +false_positives = [ + """ + Disabling a DKIM configuration may be done by a system or network administrator. Verify that the configuration + change was expected. Exceptions can be added to this rule to filter expected behavior. + """, +] +from = "now-30m" +index = ["filebeat-*"] +language = "kuery" +license = "Elastic License" +name = "Microsoft 365 Exchange DKIM Signing Configuration Disabled" +note = "The Microsoft 365 Fleet integration or Filebeat module must be enabled to use this rule." +references = [ + "https://docs.microsoft.com/en-us/powershell/module/exchange/set-dkimsigningconfig?view=exchange-ps", +] +risk_score = 47 +rule_id = "514121ce-c7b6-474a-8237-68ff71672379" +severity = "medium" +tags = ["Elastic", "Cloud", "Microsoft 365", "Continuous Monitoring", "SecOps", "Data Protection"] +type = "query" + +query = ''' +event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:"Set-DkimSigningConfig" and o365.audit.Parameters.Enabled:False and event.outcome:success +''' +