Files
sigma-rules/kql/evaluator.py
T
2021-03-03 22:12:11 -09:00

156 lines
5.0 KiB
Python

# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
# or more contributor license agreements. Licensed under the Elastic License
# 2.0; you may not use this file except in compliance with the Elastic License
# 2.0.
import operator
import re
import eql.ast
from eql import Walker, EqlCompileError, utils
from eql.functions import CidrMatch
from .errors import KqlRuntimeError, KqlCompileError
class FilterGenerator(Walker):
__cidr_cache = {}
def _walk_default(self, node, *args, **kwargs):
raise KqlCompileError("Unable to convert {}".format(node))
@classmethod
def equals(cls, term, value):
if utils.is_string(term) and utils.is_string(value):
if CidrMatch.ip_compiled.match(term) and CidrMatch.cidr_compiled.match(value):
# check for an ipv4 cidr
if value not in cls.__cidr_cache:
cls.__cidr_cache[value] = CidrMatch.get_callback(None, eql.ast.String(value))
return cls.__cidr_cache[value](term)
return term == value
@classmethod
def get_terms(cls, document, path):
if isinstance(document, (tuple, list)):
for d in document:
for term in cls.get_terms(d, path):
yield term
elif isinstance(document, dict):
document = document.get(path[0])
path = path[1:]
if len(path) > 0:
for term in cls.get_terms(document, path):
yield term
elif isinstance(document, (tuple, list)):
for term in document:
yield term
elif document is not None:
yield document
def _walk_value(self, tree, compare_function=None):
value = tree.value
compare_function = compare_function or self.equals
def check_value(term):
if term is None:
return False
if isinstance(term, list):
return any(check_value(t) for t in term)
if isinstance(term, (bool, float, int)) or utils.is_string(term):
v = value
if utils.is_string(v) and isinstance(term, (bool, int, float)):
if isinstance(v, bool):
v = v == "false"
if isinstance(term, int):
v = int(v)
elif isinstance(v, float):
v = float(v)
elif utils.is_string(term) and isinstance(v, (bool, int, float)):
v = utils.to_unicode(v)
return compare_function(term, v)
else:
raise KqlRuntimeError("Cannot compare value {}".format(term))
return check_value
def _walk_exists(self, _):
return lambda terms: any(t is not None for t in terms)
def _walk_wildcard(self, tree):
pattern = tree.value
regex = re.compile(".*?".join(map(re.escape, pattern.split("*"))), re.UNICODE | re.DOTALL)
return lambda terms: any(t is not None and regex.fullmatch(t) for t in terms)
def _walk_field(self, field):
path = field.name.split(".")
get_terms = self.get_terms
def callback(document):
terms = get_terms(document, path)
terms = list(terms)
return terms
return callback
def _walk_field_range(self, tree):
field = self.walk(tree.field)
operators = {"<": operator.lt, "<=": operator.le, ">=": operator.ge, ">": operator.gt}
check_range = self.walk(tree.value, operators[tree.operator])
return lambda doc: check_range(field(doc))
def _walk_nested_query(self, tree):
field = self.walk(tree.field)
expr = self.walk(tree.expr)
def check_nested(doc):
doc = field(doc)
if isinstance(doc, dict):
return expr(doc)
elif isinstance(doc, (list, tuple)):
return any(expr(d) for d in doc)
return check_nested
def _walk_list(self, trees, reduce_function, *args, **kwargs):
walked = [self.walk(item, *args, **kwargs) for item in trees.items]
return lambda x: reduce_function(item(x) for item in walked)
def _walk_not_expr(self, tree):
expr = self.walk(tree.expr)
return lambda doc: not expr(doc)
def _walk_and_expr(self, tree):
return self._walk_list(tree, all)
def _walk_or_expr(self, tree):
return self._walk_list(tree, any)
def _walk_and_values(self, tree):
return self._walk_list(tree, all)
def _walk_or_values(self, tree):
return self._walk_list(tree, any)
def _walk_not_value(self, tree):
expr = self.walk(tree.value)
return lambda value: not expr(value)
def _walk_field_comparison(self, tree):
field = self.walk(tree.field)
value = self.walk(tree.value)
return lambda doc: value(field(doc))
@classmethod
def filter(cls, expression):
return cls().walk(expression)