diff options
Diffstat (limited to 'tests/test_main.py')
-rw-r--r-- | tests/test_main.py | 279 |
1 files changed, 279 insertions, 0 deletions
diff --git a/tests/test_main.py b/tests/test_main.py new file mode 100644 index 0000000..919b88b --- /dev/null +++ b/tests/test_main.py @@ -0,0 +1,279 @@ +# Copyright (C) 2017 Open Information Security Foundation +# Copyright (c) 2015 Jason Ish +# +# You can copy, redistribute or modify this Program under the terms of +# the GNU General Public License version 2 as published by the Free +# Software Foundation. +# +# 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# version 2 along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. + +from __future__ import print_function + +import os +import io +import unittest + +import suricata.update.rule +from suricata.update import main +import suricata.update.extract +from suricata.update import matchers as matchers_mod + +class TestRulecat(unittest.TestCase): + + def test_extract_tar(self): + files = suricata.update.extract.extract_tar( + "tests/emerging.rules.tar.gz") + self.assertTrue(len(files) > 0) + + def test_extract_zip(self): + files = suricata.update.extract.extract_zip( + "tests/emerging.rules.zip") + self.assertTrue(len(files) > 0) + + def test_try_extract(self): + files = suricata.update.extract.try_extract( + "tests/emerging.rules.zip") + self.assertTrue(len(files) > 0) + + files = suricata.update.extract.try_extract( + "tests/emerging.rules.tar.gz") + self.assertTrue(len(files) > 0) + + files = suricata.update.extract.try_extract( + "tests/emerging-current_events.rules") + self.assertIsNone(files) + +class TestFetch(unittest.TestCase): + + def test_check_checksum(self): + """Test that we detect when the checksum are the same. This is mainly + to catch issues between Python 2 and 3. + """ + fetch = main.Fetch() + url = "file://%s/emerging.rules.tar.gz" % ( + os.path.dirname(os.path.realpath(__file__))) + local_file = "%s/emerging.rules.tar.gz" % ( + os.path.dirname(os.path.realpath(__file__))) + + # The URL passed to check_checksum is actually a tuple: + # (url, custom-header, has checksum url) + net_arg = (url, None, True) + + r = fetch.check_checksum(local_file, net_arg) + self.assertTrue(r) + +class ThresholdProcessorTestCase(unittest.TestCase): + + processor = main.ThresholdProcessor() + + def test_extract_regex(self): + processor = main.ThresholdProcessor() + + line = "suppress re:java" + self.assertEqual("java", processor.extract_regex(line)) + + line = 'suppress re:"vulnerable java version"' + self.assertEqual( + "vulnerable java version", processor.extract_regex(line)) + + line = "suppress re:java, track <by_src|by_dst>, ip <ip|subnet>" + self.assertEqual("java", processor.extract_regex(line)) + + line = 'suppress re:"vulnerable java version", track <by_src|by_dst>, ip <ip|subnet>' + self.assertEqual( + "vulnerable java version", processor.extract_regex(line)) + + line = 'threshold re:"vulnerable java version", type threshold, track by_dst, count 1, seconds 10' + self.assertEqual( + "vulnerable java version", processor.extract_regex(line)) + + def test_replace(self): + rule_string = """alert http $EXTERNAL_NET any -> $HOME_NET any (msg:"ET MALWARE Windows executable sent when remote host claims to send an image 2"; flow: established,from_server; content:"|0d 0a|Content-Type|3a| image/jpeg|0d 0a 0d 0a|MZ"; fast_pattern:12,20; classtype:trojan-activity; sid:2020757; rev:2;)""" + rule = suricata.update.rule.parse(rule_string) + + line = "suppress re:windows" + self.assertEqual( + "suppress gen_id 1, sig_id 2020757", + self.processor.replace(line, rule)) + + bad_line = "nothing to match" + self.assertEqual( + "nothing to match", + self.processor.replace(bad_line, rule) + ) + + line = 'threshold re:"ET MALWARE Windows", type threshold, ' \ + 'track by_dst, count 1, seconds 10' + self.assertEqual("threshold gen_id 1, sig_id 2020757, type threshold, track by_dst, count 1, seconds 10", self.processor.replace(line, rule)) + + line = 'threshold re:malware, type threshold, track by_dst, count 1, ' \ + 'seconds 10' + self.assertEqual( + "threshold gen_id 1, sig_id 2020757, type threshold, " + "track by_dst, count 1, seconds 10", + self.processor.replace(line, rule)) + +class ModifyRuleFilterTestCase(unittest.TestCase): + + rule_string = """alert http $EXTERNAL_NET any -> $HOME_NET any (msg:"ET MALWARE Windows executable sent when remote host claims to send an image 2"; flow: established,from_server; content:"|0d 0a|Content-Type|3a| image/jpeg|0d 0a 0d 0a|MZ"; fast_pattern:12,20; classtype:trojan-activity; sid:2020757; rev:2;)""" + + def test_id_match(self): + rule0 = suricata.update.rule.parse(self.rule_string) + line = r'2020757 "\|0d 0a\|" "|ff ff|"' + rule_filter = matchers_mod.ModifyRuleFilter.parse(line) + self.assertTrue(rule_filter != None) + self.assertTrue(rule_filter.match(rule0)) + rule1 = rule_filter.run(rule0) + self.assertEqual( + str(rule1), + """alert http $EXTERNAL_NET any -> $HOME_NET any (msg:"ET MALWARE Windows executable sent when remote host claims to send an image 2"; flow: established,from_server; content:"|ff ff|Content-Type|3a| image/jpeg|0d 0a 0d 0a|MZ"; fast_pattern:12,20; classtype:trojan-activity; sid:2020757; rev:2;)""") + + def test_re_match(self): + rule0 = suricata.update.rule.parse(self.rule_string) + line = r're:classtype:trojan-activity "\|0d 0a\|" "|ff ff|"' + rule_filter = matchers_mod.ModifyRuleFilter.parse(line) + self.assertTrue(rule_filter != None) + self.assertTrue(rule_filter.match(rule0)) + rule1 = rule_filter.run(rule0) + self.assertEqual( + str(rule1), + """alert http $EXTERNAL_NET any -> $HOME_NET any (msg:"ET MALWARE Windows executable sent when remote host claims to send an image 2"; flow: established,from_server; content:"|ff ff|Content-Type|3a| image/jpeg|0d 0a 0d 0a|MZ"; fast_pattern:12,20; classtype:trojan-activity; sid:2020757; rev:2;)""") + + def test_re_backref_one(self): + rule0 = suricata.update.rule.parse(self.rule_string) + line = 're:classtype:trojan-activity "(alert)(.*)" "drop\\2"' + rule_filter = matchers_mod.ModifyRuleFilter.parse(line) + self.assertTrue(rule_filter != None) + self.assertTrue(rule_filter.match(rule0)) + rule1 = rule_filter.run(rule0) + expected = """drop http $EXTERNAL_NET any -> $HOME_NET any (msg:"ET MALWARE Windows executable sent when remote host claims to send an image 2"; flow: established,from_server; content:"|0d 0a|Content-Type|3a| image/jpeg|0d 0a 0d 0a|MZ"; fast_pattern:12,20; classtype:trojan-activity; sid:2020757; rev:2;)""" + self.assertEqual(str(rule1), expected) + + def test_re_backref_two(self): + rule0 = suricata.update.rule.parse(self.rule_string) + line = 're:classtype:trojan-activity "(alert)(.*)(from_server)(.*)" "drop\\2to_client\\4"' + rule_filter = matchers_mod.ModifyRuleFilter.parse(line) + self.assertTrue(rule_filter != None) + self.assertTrue(rule_filter.match(rule0)) + rule1 = rule_filter.run(rule0) + expected = """drop http $EXTERNAL_NET any -> $HOME_NET any (msg:"ET MALWARE Windows executable sent when remote host claims to send an image 2"; flow: established,to_client; content:"|0d 0a|Content-Type|3a| image/jpeg|0d 0a 0d 0a|MZ"; fast_pattern:12,20; classtype:trojan-activity; sid:2020757; rev:2;)""" + self.assertEqual(str(rule1), expected) + + def test_drop_to_alert(self): + rule_in = suricata.update.rule.parse(self.rule_string) + self.assertIsNotNone(rule_in) + + f = matchers_mod.ModifyRuleFilter.parse( + 'group:emerging-trojan.rules "^alert" "drop"') + self.assertIsNotNone(f) + + rule_out = f.run(rule_in) + self.assertTrue(rule_out.format().startswith("drop")) + + def test_oinkmaster_backticks(self): + f = matchers_mod.ModifyRuleFilter.parse( + '* "^drop(.*)noalert(.*)" "alert${1}noalert${2}"') + rule_in ="""drop http $EXTERNAL_NET any -> $HOME_NET any (msg:"ET MALWARE Windows executable sent when remote host claims to send an image 2"; flow: established,to_client; content:"|0d 0a|Content-Type|3a| image/jpeg|0d 0a 0d 0a|MZ"; fast_pattern:12,20; noalert; classtype:trojan-activity; sid:2020757; rev:2;)""" + rule_out = f.run(suricata.update.rule.parse(rule_in)) + self.assertEqual("""alert http $EXTERNAL_NET any -> $HOME_NET any (msg:"ET MALWARE Windows executable sent when remote host claims to send an image 2"; flow: established,to_client; content:"|0d 0a|Content-Type|3a| image/jpeg|0d 0a 0d 0a|MZ"; fast_pattern:12,20; noalert; classtype:trojan-activity; sid:2020757; rev:2;)""", rule_out.format()) + + def test_oinkmaster_backticks_not_noalert(self): + f = matchers_mod.ModifyRuleFilter.parse( + 'modifysid * "^drop(.*)noalert(.*)" | "alert${1}noalert${2}"') + rule_in ="""drop http $EXTERNAL_NET any -> $HOME_NET any (msg:"ET MALWARE Windows executable sent when remote host claims to send an image 2"; flow: established,to_client; content:"|0d 0a|Content-Type|3a| image/jpeg|0d 0a 0d 0a|MZ"; fast_pattern:12,20; classtype:trojan-activity; sid:2020757; rev:2;)""" + rule_out = f.run(suricata.update.rule.parse(rule_in)) + self.assertEqual(rule_in, rule_out.format()) + + def test_oinkmaster_modify_group_name(self): + """Test an Oinkmaster style modification line using a group name.""" + f = matchers_mod.ModifyRuleFilter.parse( + 'modifysid botcc.rules "^alert" | "drop"') + rule_in ="""alert http $EXTERNAL_NET any -> $HOME_NET any (msg:"ET MALWARE Windows executable sent when remote host claims to send an image 2"; flow: established,to_client; content:"|0d 0a|Content-Type|3a| image/jpeg|0d 0a 0d 0a|MZ"; fast_pattern:12,20; classtype:trojan-activity; sid:2020757; rev:2;)""" + rule = suricata.update.rule.parse(rule_in, "rules/botcc.rules") + rule_out = f.run(rule) + self.assertTrue(rule_out.format().startswith("drop")) + +class DropRuleFilterTestCase(unittest.TestCase): + + rule_string = """alert http $EXTERNAL_NET any -> $HOME_NET any (msg:"ET MALWARE Windows executable sent when remote host claims to send an image 2"; flow: established,from_server; content:"|0d 0a|Content-Type|3a| image/jpeg|0d 0a 0d 0a|MZ"; fast_pattern:12,20; classtype:trojan-activity; sid:2020757; rev:2;)""" + + def test_enabled_rule(self): + rule0 = suricata.update.rule.parse(self.rule_string, "rules/malware.rules") + id_matcher = matchers_mod.IdRuleMatcher.parse("2020757") + self.assertTrue(id_matcher.match(rule0)) + + drop_filter = matchers_mod.DropRuleFilter(id_matcher) + rule1 = drop_filter.run(rule0) + self.assertEqual("drop", rule1.action) + self.assertTrue(rule1.enabled) + self.assertTrue(str(rule1).startswith("drop")) + + def test_disabled_rule(self): + rule0 = suricata.update.rule.parse( + "# " + self.rule_string, "rules/malware.rules") + id_matcher = matchers_mod.IdRuleMatcher.parse("2020757") + self.assertTrue(id_matcher.match(rule0)) + + drop_filter = matchers_mod.DropRuleFilter(id_matcher) + rule1 = drop_filter.run(rule0) + self.assertEqual("drop", rule1.action) + self.assertFalse(rule1.enabled) + self.assertTrue(str(rule1).startswith("# drop")) + + def test_drop_noalert(self): + """ Test the rules with "noalert" are not marked as drop. """ + + rule_without_noalert = """alert tcp $HOME_NET any -> $EXTERNAL_NET any (msg:"ET TROJAN [CrowdStrike] ANCHOR PANDA Torn RAT Beacon Message Header Local"; flow:established, to_server; dsize:16; content:"|00 00 00 11 c8 00 00 00 00 00 00 00 00 00 00 00|"; depth:16; flowbits:set,ET.Torn.toread_header; reference:url,blog.crowdstrike.com/whois-anchor-panda/index.html; classtype:trojan-activity; sid:2016659; rev:2; metadata:created_at 2013_03_22, updated_at 2013_03_22;)""" + + rule_with_noalert = """alert tcp $HOME_NET any -> $EXTERNAL_NET any (msg:"ET TROJAN [CrowdStrike] ANCHOR PANDA Torn RAT Beacon Message Header Local"; flow:established, to_server; dsize:16; content:"|00 00 00 11 c8 00 00 00 00 00 00 00 00 00 00 00|"; depth:16; flowbits:set,ET.Torn.toread_header; flowbits: noalert; reference:url,blog.crowdstrike.com/whois-anchor-panda/index.html; classtype:trojan-activity; sid:2016659; rev:2; metadata:created_at 2013_03_22, updated_at 2013_03_22;)""" + + rule = suricata.update.rule.parse(rule_without_noalert) + matcher = matchers_mod.IdRuleMatcher.parse("2016659") + rule_filter = matchers_mod.DropRuleFilter(matcher) + self.assertTrue(rule_filter.match(rule)) + + rule = suricata.update.rule.parse(rule_with_noalert) + matcher = matchers_mod.IdRuleMatcher.parse("2016659") + rule_filter = matchers_mod.DropRuleFilter(matcher) + self.assertFalse(rule_filter.match(rule)) + + +class DummySuriConf(dict): + def __getattr__(self, val): + return self[val] + + +class ClassificationConfigMergeTestCase(unittest.TestCase): + test_fname1 = "tests/classification1.config" + test_fname2 = "tests/classification2.config" + + def test_merge_classification_files(self): + """ Test if the two files get merged properly and priority is maintained""" + suriconf = DummySuriConf() + suriconf["build_info"] = {} + with open(self.test_fname1) as fp: + test_file1 = fp.read() + with open(self.test_fname2) as fp: + test_file2 = fp.read() + files = [("test_file1", test_file1.encode()), + ("test_file2", test_file2.encode())] + cdict = main.load_classification(suriconf, files) + + # Number of classifications in classification1.config: 42 + # Number of classifications in classification2.config: 44 (2 new) + self.assertEqual(44, len(cdict)) + + # classification1.config: + # config classification: misc-attack,Misc Attack,2 + # + # classification2.config: + # config classification: misc-attack,Misc Attack,5 + self.assertEqual("5", cdict["misc-attack"][1]) |