From: Jason Ish Date: Wed, 2 Oct 2019 20:43:12 +0000 (-0600) Subject: Disable ja3_hash rules if Suricata not configured for ja3. X-Git-Tag: 1.1.0~8 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f948b1f25e7fc149977748ab133bd0ce4cc0bfdb;p=thirdparty%2Fsuricata-update.git Disable ja3_hash rules if Suricata not configured for ja3. Disable rules using ja3 keywords if Suricata is not configure for ja3, or does not support ja3. Rules using ja3 keywords with fail to load if Suricata is not built with NSS, or ja3 fingerprints are disabled. Take into account the Suricata version as well, as not defining the ja3_fingerprint configuration field in 5.0+ will leave it enabled, but in older versions, it will remain disabled if not defined. --- diff --git a/suricata/update/engine.py b/suricata/update/engine.py index 044f176..1abd1f7 100644 --- a/suricata/update/engine.py +++ b/suricata/update/engine.py @@ -64,8 +64,9 @@ def get_build_info(suricata): class Configuration: """An abstraction over the Suricata configuration file.""" - def __init__(self, conf): + def __init__(self, conf, build_info = {}): self.conf = conf + self.build_info = build_info def keys(self): return self.conf.keys() @@ -73,6 +74,9 @@ class Configuration: def has_key(self, key): return key in self.conf + def get(self, key): + return self.conf.get(key, None) + def is_true(self, key, truthy=[]): if not key in self.conf: logger.warning( diff --git a/suricata/update/main.py b/suricata/update/main.py index 3afe8fd..e113843 100644 --- a/suricata/update/main.py +++ b/suricata/update/main.py @@ -1062,6 +1062,49 @@ def check_output_directory(output_dir): "Failed to create directory %s: %s" % ( output_dir, err)) +# Check and disable ja3 rules if needed. +# +# Note: This is a bit of a quick fixup job for 5.0, but we should look +# at making feature handling more generic. +def disable_ja3(suriconf, rulemap, disabled_rules): + if suriconf and suriconf.build_info: + enabled = False + reason = None + logged = False + if "HAVE_NSS" not in suriconf.build_info["features"]: + reason = "Disabling ja3 rules as Suricata is built without libnss." + else: + # Check if disabled. Must be explicitly disabled, + # otherwise we'll keep ja3 rules enabled. + val = suriconf.get("app-layer.protocols.tls.ja3-fingerprints") + + # Prior to Suricata 5, leaving ja3-fingerprints undefined + # in the configuration disabled the feature. With 5.0, + # having it undefined will enable it as needed. + if not val: + if suriconf.build_info["version"].major < 5: + val = "no" + else: + val = "auto" + + if val and val.lower() not in ["1", "yes", "true", "auto"]: + reason = "Disabling ja3 rules as ja3 fingerprints are not enabled." + else: + enabled = True + + count = 0 + if not enabled: + for key, rule in rulemap.items(): + if "ja3" in rule["features"]: + if not logged: + logger.warn(reason) + logged = True + rule.enabled = False + disabled_rules.append(rule) + count += 1 + if count: + logger.info("%d ja3_hash rules disabled." % (count)) + def _main(): global args @@ -1416,6 +1459,12 @@ def _main(): rulemap[rule.id] = new_rule modify_count += 1 + # Check if we should disable ja3 rules. + try: + disable_ja3(suriconf, rulemap, disabled_rules) + except Exception as err: + logger.error("Failed to dynamically disable ja3 rules: %s" % (err)) + # Check rule vars, disabling rules that use unknown vars. check_vars(suriconf, rulemap) diff --git a/suricata/update/rule.py b/suricata/update/rule.py index 7f6bab5..42c673e 100644 --- a/suricata/update/rule.py +++ b/suricata/update/rule.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 Open Information Security Foundation +# Copyright (C) 2017-2019 Open Information Security Foundation # Copyright (c) 2011 Jason Ish # # You can copy, redistribute or modify this Program under the terms of @@ -76,6 +76,7 @@ class Rule(dict): - **classtype**: The classification type - **priority**: The rule priority, 0 if not provided - **noalert**: Is the rule a noalert rule + - **features**: Features required by this rule - **raw**: The raw rule as read from the file or buffer :param enabled: Optional parameter to set the enabled state of the rule @@ -106,6 +107,8 @@ class Rule(dict): self["priority"] = 0 self["noalert"] = False + self["features"] = [] + self["raw"] = None def __getattr__(self, name): @@ -286,6 +289,9 @@ def parse(buf, group=None): else: rule[name] = val + if name.startswith("ja3"): + rule["features"].append("ja3") + if rule["msg"] is None: rule["msg"] = "" diff --git a/tests/test_rule.py b/tests/test_rule.py index ef7ee6c..c5808d8 100644 --- a/tests/test_rule.py +++ b/tests/test_rule.py @@ -242,3 +242,15 @@ alert dnp3 any any -> any any (msg:"SURICATA DNP3 Request flood detected"; \ self.assertRaises( suricata.update.rule.BadSidError, suricata.update.rule.parse, rule_buf) + + def test_parse_feature_ja3(self): + """Test parsing rules that should set the ja3 feature.""" + rule_string = u"""alert tls any any -> any any (msg:"REQUIRES JA3"; ja3_hash; content:"61d50e7771aee7f2f4b89a7200b4d45"; sid:1; rev:1;)""" + rule = suricata.update.rule.parse(rule_string) + self.assertIsNotNone(rule) + self.assertTrue("ja3" in rule["features"]) + + rule_string = u"""alert tls any any -> any any (msg:"REQUIRES JA3"; ja3.hash; content:"61d50e7771aee7f2f4b89a7200b4d45"; sid:1; rev:1;)""" + rule = suricata.update.rule.parse(rule_string) + self.assertIsNotNone(rule) + self.assertTrue("ja3" in rule["features"])