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()
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(
"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
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)
-# 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
- **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
self["priority"] = 0
self["noalert"] = False
+ self["features"] = []
+
self["raw"] = None
def __getattr__(self, name):
else:
rule[name] = val
+ if name.startswith("ja3"):
+ rule["features"].append("ja3")
+
if rule["msg"] is None:
rule["msg"] = ""
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"])