]> git.ipfire.org Git - thirdparty/suricata-update.git/commitdiff
Disable ja3_hash rules if Suricata not configured for ja3.
authorJason Ish <jason.ish@oisf.net>
Wed, 2 Oct 2019 20:43:12 +0000 (14:43 -0600)
committerJason Ish <jason.ish@oisf.net>
Fri, 11 Oct 2019 15:17:04 +0000 (09:17 -0600)
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.

suricata/update/engine.py
suricata/update/main.py
suricata/update/rule.py
tests/test_rule.py

index 044f176bfb5d4b2379cbacced80dd76f762ad4d0..1abd1f7cf49a26919570c0f532f4bd7507f29566 100644 (file)
@@ -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(
index 3afe8fd17286913822adf6fda6b1358feb200450..e1138436ef498580e8235a87882eb7cb5aca56e1 100644 (file)
@@ -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)
 
index 7f6bab5552afd6065cbc6f1b1d00a35753623050..42c673e92d31eefa89fabae91a4d962bc9438e7d 100644 (file)
@@ -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"] = ""
 
index ef7ee6cd69dbe72e408c535c347053fd075dda8b..c5808d827534f95c8039b9ba346a2c15451685df 100644 (file)
@@ -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"])