diff --git a/CHANGELOG.md b/CHANGELOG.md index c42f6de7a..51a2eee48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,9 @@ from version 0.14.0. ## Unreleased -Changes from this section will be contained in the next release. +### Added + +* sigma-uuid tool ### Changed diff --git a/tools/setup.py b/tools/setup.py index 8059c88e6..ac00e33b3 100644 --- a/tools/setup.py +++ b/tools/setup.py @@ -34,7 +34,14 @@ setup( 'Environment :: Console', ], keywords='security monitoring siem logging signatures elasticsearch splunk ids sysmon', - packages=['sigma', 'sigma.backends', 'sigma.config', 'sigma.parser', 'sigma.parser.modifiers'], + packages=[ + 'sigma', + 'sigma.backends', + 'sigma.config', + 'sigma.parser', + 'sigma.parser.modifiers', + 'sigma.output', + ], python_requires='~=3.6', install_requires=['PyYAML', 'pymisp', 'progressbar2'], extras_require={ @@ -71,5 +78,6 @@ setup( 'merge_sigma', 'sigma2misp', 'sigma-similarity', + 'sigma-uuid', ] ) diff --git a/tools/sigma-uuid b/tools/sigma-uuid new file mode 100755 index 000000000..002105500 --- /dev/null +++ b/tools/sigma-uuid @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +# Assign UUIDs to Sigma rules and verify UUID assignment for a Sigma rule repository + +from argparse import ArgumentParser +from pathlib import Path +from uuid import uuid4, UUID +import yaml +from sigma.output import SigmaYAMLDumper + +argparser = ArgumentParser(description="Assign and verfify UUIDs of Sigma rules") +argparser.add_argument("--verify", "-V", action="store_true", help="Verify existence and uniqueness of UUID assignments. Exits with error code if verification fails.") +argparser.add_argument("--verbose", "-v", action="store_true", help="Be verbose.") +argparser.add_argument("--recursive", "-r", action="store_true", help="Recurse into directories.") +argparser.add_argument("--error", "-e", action="store_true", help="Exit with error code 10 on verification failures.") +argparser.add_argument("inputs", nargs="+", help="Sigma rule files or repository directories") +args = argparser.parse_args() + +if args.recursive: + paths = [ p for pathname in args.inputs for p in Path(pathname).glob("**/*") if p.is_file() ] +else: + paths = [ Path(pathname) for pathname in args.inputs ] + +def print_verbose(*arg, **kwarg): + if args.verbose: + print(*arg, **kwarg) + +# Define order-preserving representer from dicts/maps +def yaml_preserve_order(self, dict_data): + return self.represent_mapping("tag:yaml.org,2002:map", dict_data.items()) + +yaml.add_representer(dict, yaml_preserve_order) + +uuids = set() +passed = True +for path in paths: + print_verbose("Rule {}".format(str(path))) + with path.open("r") as f: + rules = list(yaml.safe_load_all(f)) + + if args.verify: + i = 1 + for rule in rules: + if "title" in rule: # Rule with a title should also have a UUID + try: + UUID(rule["id"]) + except ValueError: # id is not a valid UUID + print("Rule {} in file {} has a malformed UUID '{}'.".format(i, str(path), rule["id"])) + passed = False + except KeyError: # rule has no id + print("Rule {} in file {} has no UUID.".format(i, str(path))) + passed = False + i += 1 + else: + newrules = list() + changed = False + i = 1 + for rule in rules: + if "title" in rule and "id" not in rule: # only assign id to rules that have a title and no id + newrule = dict() + changed = True + for k, v in rule.items(): + newrule[k] = v + if k == "title": # insert id after title + uuid = uuid4() + newrule["id"] = str(uuid) + print("Assigned UUID '{}' to rule {} in file {}.".format(uuid, i, str(path))) + newrules.append(newrule) + else: + newrules.append(rule) + i += 1 + + if changed: + with path.open("w") as f: + yaml.dump_all(newrules, f, Dumper=SigmaYAMLDumper, indent=4, width=160, default_flow_style=False) + +if args.error and not passed: + exit(10) diff --git a/tools/sigma/output.py b/tools/sigma/output.py new file mode 100644 index 000000000..bb7320d9d --- /dev/null +++ b/tools/sigma/output.py @@ -0,0 +1,22 @@ +# Sigma output library +# Copyright 2016-2019 Thomas Patzke, Florian Roth + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. + +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . + +import yaml + +class SigmaYAMLDumper(yaml.Dumper): + """YAML dumper that increases amount of indentation, e.g. for lists""" + def increase_indent(self, flow=False, indentless=False): + return super().increase_indent(flow, False) diff --git a/tools/sigma2genericsigma b/tools/sigma2genericsigma index 0d5d6cb14..0dbc26b5e 100755 --- a/tools/sigma2genericsigma +++ b/tools/sigma2genericsigma @@ -5,6 +5,7 @@ from argparse import ArgumentParser import yaml import sys from pathlib import Path +from sigma.output import SigmaYAMLDumper class Output(object): """Output base class""" @@ -75,11 +76,6 @@ def get_output(output): else: return SingleFileOutput(output) -class SigmaYAMLDumper(yaml.Dumper): - """YAML dumper that increases amount of indentation, e.g. for lists""" - def increase_indent(self, flow=False, indentless=False): - return super().increase_indent(flow, False) - class AmbiguousRuleException(TypeError): def __init__(self, ids): super().__init__()