2019-01-22 18:25:13 +00:00
#!/usr/bin/env python3
"""
Checks for noncompliance or common errors on all rules
2019-02-18 18:05:58 +00:00
Run using the command
2019-01-22 18:25:13 +00:00
# python -m unittest test_rules.py
"""
import os
import unittest
import yaml
2019-03-02 17:51:49 +00:00
import re
2020-01-30 07:37:47 +00:00
from colorama import init
from colorama import Fore
2019-01-22 18:25:13 +00:00
class TestRules ( unittest . TestCase ) :
2020-02-02 12:40:08 +00:00
MITRE_TECHNIQUES = [
2020-06-17 17:51:48 +00:00
" t1002 " ,
" t1003 " ,
" t1003.001 " ,
" t1003.002 " ,
" t1003.003 " ,
" t1003.004 " ,
" t1003.005 " ,
" t1003.006 " ,
" t1004 " ,
" t1005 " ,
" t1006 " ,
" t1007 " ,
" t1009 " ,
" t1011 " ,
" t1012 " ,
" t1015 " ,
" t1016 " ,
" t1018 " ,
" t1020 " ,
" t1021 " ,
" t1021.001 " ,
" t1021.002 " ,
" t1021.003 " ,
" t1021.006 " ,
" t1023 " ,
" t1027 " ,
" t1028 " ,
" t1031 " ,
" t1033 " ,
" t1035 " ,
" t1036 " ,
" t1036.005 " ,
" t1037 " ,
" t1037.001 " ,
" t1038 " ,
" t1040 " ,
" t1041 " ,
" t1042 " ,
" t1043 " ,
" t1046 " ,
" t1047 " ,
" t1048 " ,
" t1049 " ,
" t1050 " ,
" t1053 " ,
" t1053.002 " ,
" t1053.005 " ,
" t1054 " ,
" t1055 " ,
" t1056 " ,
" t1057 " ,
" t1058 " ,
" t1059 " ,
" t1059.001 " ,
" t1059.003 " ,
" t1059.004 " ,
" t1059.005 " ,
" t1059.006 " ,
" t1060 " ,
" t1064 " ,
" t1066 " ,
" t1067 " ,
" t1068 " ,
" t1069 " ,
" t1070 " ,
2020-07-13 14:02:00 +00:00
" t1070.001 " ,
" t1070.002 " ,
" t1070.003 " ,
" t1070.004 " ,
" t1070.005 " ,
" t1070.006 " ,
2020-06-17 17:51:48 +00:00
" t1071 " ,
" t1071.004 " ,
" t1073 " ,
" t1074 " ,
" t1075 " ,
" t1076 " ,
" t1077 " ,
" t1078 " ,
" t1081 " ,
" t1082 " ,
" t1083 " ,
" t1084 " ,
" t1085 " ,
" t1086 " ,
" t1087 " ,
" t1088 " ,
" t1089 " ,
" t1090 " ,
" t1091 " ,
" t1096 " ,
" t1098 " ,
" t1099 " ,
" t1100 " ,
" t1102 " ,
" t1103 " ,
" t1105 " ,
2020-07-01 07:57:35 +00:00
" t1106 " ,
2020-06-17 17:51:48 +00:00
" t1107 " ,
" t1110 " ,
" t1112 " ,
" t1114 " ,
" t1117 " ,
" t1118 " ,
" t1121 " ,
" t1122 " ,
" t1123 " ,
" t1124 " ,
2020-06-19 08:00:44 +00:00
" t1125 " ,
2020-06-17 17:51:48 +00:00
" t1127 " ,
" t1128 " ,
" t1130 " ,
" t1133 " ,
" t1134 " ,
" t1134.005 " ,
" t1135 " ,
" t1136 " ,
" t1137 " ,
" t1138 " ,
" t1139 " ,
" t1140 " ,
" t1145 " ,
" t1146 " ,
" t1156 " ,
" t1158 " ,
" t1168 " ,
" t1169 " ,
" t1170 " ,
" t1171 " ,
" t1175 " ,
" t1177 " ,
" t1178 " ,
" t1182 " ,
" t1183 " ,
" t1190 " ,
" t1191 " ,
" t1193 " ,
" t1195 " ,
" t1195.001 " ,
" t1196 " ,
" t1197 " ,
" t1200 " ,
" t1201 " ,
" t1202 " ,
" t1203 " ,
" t1204 " ,
" t1207 " ,
" t1208 " ,
" t1210 " ,
" t1211 " ,
" t1212 " ,
" t1218 " ,
" t1218.001 " ,
" t1218.005 " ,
" t1218.010 " ,
" t1218.011 " ,
" t1219 " ,
" t1220 " ,
" t1222 " ,
" t1223 " ,
" t1482 " ,
" t1485 " ,
" t1487 " ,
" t1488 " ,
" t1489 " ,
" t1490 " ,
" t1492 " ,
" t1493 " ,
" t1495 " ,
" t1499 " ,
" t1500 " ,
" t1501 " ,
" t1505 " ,
" t1505.003 " ,
" t1537 " ,
" t1542.003 " ,
" t1543.002 " ,
" t1543.003 " ,
" t1546.001 " ,
" t1546.003 " ,
" t1546.004 " ,
" t1546.007 " ,
" t1546.008 " ,
" t1546.009 " ,
" t1546.010 " ,
" t1546.011 " ,
" t1546.012 " ,
" t1546.015 " ,
" t1547.001 " ,
" t1547.004 " ,
" t1547.008 " ,
" t1547.009 " ,
" t1548.002 " ,
" t1550.002 " ,
" t1551 " ,
" t1551.003 " ,
" t1551.004 " ,
" t1551.006 " ,
" t1552.001 " ,
" t1552.003 " ,
" t1552.004 " ,
" t1553.004 " ,
" t1557.001 " ,
" t1558 " ,
" t1558.003 " ,
" t1559.001 " ,
" t1560 " ,
" t1561.001 " ,
" t1561.002 " ,
" t1562.001 " ,
" t1562.006 " ,
" t1564.001 " ,
" t1564.004 " ,
" t1565.001 " ,
" t1565.002 " ,
" t1566.001 " ,
" t1569.002 " ,
" t1571 " ,
" t1574.001 " ,
" t1574.002 " ,
" t1574.011 " ,
2020-02-02 12:40:08 +00:00
]
2019-01-22 18:25:13 +00:00
MITRE_TECHNIQUE_NAMES = [ " process_injection " , " signed_binary_proxy_execution " , " process_injection " ] # incomplete list
2020-03-25 13:04:22 +00:00
MITRE_TACTICS = [ " initial_access " , " execution " , " persistence " , " privilege_escalation " , " defense_evasion " , " credential_access " , " discovery " , " lateral_movement " , " collection " , " exfiltration " , " command_and_control " , " impact " , " launch " ]
2019-12-17 00:13:06 +00:00
MITRE_GROUPS = [ " g0001 " , " g0002 " , " g0003 " , " g0004 " , " g0005 " , " g0006 " , " g0007 " , " g0008 " , " g0009 " , " g0010 " , " g0011 " , " g0012 " , " g0013 " , " g0014 " , " g0015 " , " g0016 " , " g0017 " , " g0018 " , " g0019 " , " g0020 " , " g0021 " , " g0022 " , " g0023 " , " g0024 " , " g0025 " , " g0026 " , " g0027 " , " g0028 " , " g0029 " , " g0030 " , " g0031 " , " g0032 " , " g0033 " , " g0034 " , " g0035 " , " g0036 " , " g0037 " , " g0038 " , " g0039 " , " g0040 " , " g0041 " , " g0042 " , " g0043 " , " g0044 " , " g0045 " , " g0046 " , " g0047 " , " g0048 " , " g0049 " , " g0050 " , " g0051 " , " g0052 " , " g0053 " , " g0054 " , " g0055 " , " g0056 " , " g0057 " , " g0058 " , " g0059 " , " g0060 " , " g0061 " , " g0062 " , " g0063 " , " g0064 " , " g0065 " , " g0066 " , " g0067 " , " g0068 " , " g0069 " , " g0070 " , " g0071 " , " g0072 " , " g0073 " , " g0074 " , " g0075 " , " g0076 " , " g0077 " , " g0078 " , " g0079 " , " g0080 " , " g0081 " , " g0082 " , " g0083 " , " g0084 " , " g0085 " , " g0086 " , " g0087 " , " g0088 " , " g0089 " , " g0090 " , " g0091 " , " g0092 " , " g0093 " , " g0094 " , " g0095 " , " g0096 " ]
MITRE_SOFTWARE = [ " s0001 " , " s0002 " , " s0003 " , " s0004 " , " s0005 " , " s0006 " , " s0007 " , " s0008 " , " s0009 " , " s0010 " , " s0011 " , " s0012 " , " s0013 " , " s0014 " , " s0015 " , " s0016 " , " s0017 " , " s0018 " , " s0019 " , " s0020 " , " s0021 " , " s0022 " , " s0023 " , " s0024 " , " s0025 " , " s0026 " , " s0027 " , " s0028 " , " s0029 " , " s0030 " , " s0031 " , " s0032 " , " s0033 " , " s0034 " , " s0035 " , " s0036 " , " s0037 " , " s0038 " , " s0039 " , " s0040 " , " s0041 " , " s0042 " , " s0043 " , " s0044 " , " s0045 " , " s0046 " , " s0047 " , " s0048 " , " s0049 " , " s0050 " , " s0051 " , " s0052 " , " s0053 " , " s0054 " , " s0055 " , " s0056 " , " s0057 " , " s0058 " , " s0059 " , " s0060 " , " s0061 " , " s0062 " , " s0063 " , " s0064 " , " s0065 " , " s0066 " , " s0067 " , " s0068 " , " s0069 " , " s0070 " , " s0071 " , " s0072 " , " s0073 " , " s0074 " , " s0075 " , " s0076 " , " s0077 " , " s0078 " , " s0079 " , " s0080 " , " s0081 " , " s0082 " , " s0083 " , " s0084 " , " s0085 " , " s0086 " , " s0087 " , " s0088 " , " s0089 " , " s0090 " , " s0091 " , " s0092 " , " s0093 " , " s0094 " , " s0095 " , " s0096 " , " s0097 " , " s0098 " , " s0099 " , " s0100 " , " s0101 " , " s0102 " , " s0103 " , " s0104 " , " s0105 " , " s0106 " , " s0107 " , " s0108 " , " s0109 " , " s0110 " , " s0111 " , " s0112 " , " s0113 " , " s0114 " , " s0115 " , " s0116 " , " s0117 " , " s0118 " , " s0119 " , " s0120 " , " s0121 " , " s0122 " , " s0123 " , " s0124 " , " s0125 " , " s0126 " , " s0127 " , " s0128 " , " s0129 " , " s0130 " , " s0131 " , " s0132 " , " s0133 " , " s0134 " , " s0135 " , " s0136 " , " s0137 " , " s0138 " , " s0139 " , " s0140 " , " s0141 " , " s0142 " , " s0143 " , " s0144 " , " s0145 " , " s0146 " , " s0147 " , " s0148 " , " s0149 " , " s0150 " , " s0151 " , " s0152 " , " s0153 " , " s0154 " , " s0155 " , " s0156 " , " s0157 " , " s0158 " , " s0159 " , " s0160 " , " s0161 " , " s0162 " , " s0163 " , " s0164 " , " s0165 " , " s0166 " , " s0167 " , " s0168 " , " s0169 " , " s0170 " , " s0171 " , " s0172 " , " s0173 " , " s0174 " , " s0175 " , " s0176 " , " s0177 " , " s0178 " , " s0179 " , " s0180 " , " s0181 " , " s0182 " , " s0183 " , " s0184 " , " s0185 " , " s0186 " , " s0187 " , " s0188 " , " s0189 " , " s0190 " , " s0191 " , " s0192 " , " s0193 " , " s0194 " , " s0195 " , " s0196 " , " s0197 " , " s0198 " , " s0199 " , " s0200 " , " s0201 " , " s0202 " , " s0203 " , " s0204 " , " s0205 " , " s0206 " , " s0207 " , " s0208 " , " s0209 " , " s0210 " , " s0211 " , " s0212 " , " s0213 " , " s0214 " , " s0215 " , " s0216 " , " s0217 " , " s0218 " , " s0219 " , " s0220 " , " s0221 " , " s0222 " , " s0223 " , " s0224 " , " s0225 " , " s0226 " , " s0227 " , " s0228 " , " s0229 " , " s0230 " , " s0231 " , " s0232 " , " s0233 " , " s0234 " , " s0235 " , " s0236 " , " s0237 " , " s0238 " , " s0239 " , " s0240 " , " s0241 " , " s0242 " , " s0243 " , " s0244 " , " s0245 " , " s0246 " , " s0247 " , " s0248 " , " s0249 " , " s0250 " , " s0251 " , " s0252 " , " s0253 " , " s0254 " , " s0255 " , " s0256 " , " s0257 " , " s0258 " , " s0259 " , " s0260 " , " s0261 " , " s0262 " , " s0263 " , " s0264 " , " s0265 " , " s0266 " , " s0267 " , " s0268 " , " s0269 " , " s0270 " , " s0271 " , " s0272 " , " s0273 " , " s0274 " , " s0275 " , " s0276 " , " s0277 " , " s0278 " , " s0279 " , " s0280 " , " s0281 " , " s0282 " , " s0283 " , " s0284 " , " s0330 " , " s0331 " , " s0332 " , " s0333 " , " s0334 " , " s0335 " , " s0336 " , " s0337 " , " s0338 " , " s0339 " , " s0340 " , " s0341 " , " s0342 " , " s0343 " , " s0344 " , " s0345 " , " s0346 " , " s0347 " , " s0348 " , " s0349 " , " s0350 " , " s0351 " , " s0352 " , " s0353 " , " s0354 " , " s0355 " , " s0356 " , " s0357 " , " s0358 " , " s0359 " , " s0360 " , " s0361 " , " s0362 " , " s0363 " , " s0364 " , " s0365 " , " s0366 " , " s0367 " , " s0368 " , " s0369 " , " s0370 " , " s0371 " , " s0372 " , " s0373 " , " s0374 " , " s0375 " , " s0376 " , " s0377 " , " s0378 " , " s0379 " , " s0380 " , " s0381 " , " s0382 " , " s0383 " , " s0384 " , " s0385 " , " s0386 " , " s0387 " , " s0388 " , " s0389 " , " s0390 " , " s0391 " , " s0393 " , " s0394 " , " s0395 " , " s0396 " , " s0397 " , " s0398 " , " s0400 " , " s0401 " , " s0402 " , " s0404 " , " s0409 " , " s0410 " , " s0412 " , " s0413 " , " s0414 " , " s0415 " , " s0416 " , " s0417 " ]
2019-11-08 21:03:19 +00:00
MITRE_ALL = [ " attack. " + item for item in MITRE_TECHNIQUES + MITRE_TACTICS + MITRE_GROUPS + MITRE_SOFTWARE ]
2019-01-22 18:25:13 +00:00
2019-01-25 09:25:51 +00:00
path_to_rules = " rules "
2019-01-22 18:25:13 +00:00
# Helper functions
def yield_next_rule_file_path ( self , path_to_rules : str ) - > str :
for root , _ , files in os . walk ( path_to_rules ) :
for file in files :
yield os . path . join ( root , file )
def get_rule_part ( self , file_path : str , part_name : str ) :
yaml_dicts = self . get_rule_yaml ( file_path )
for yaml_part in yaml_dicts :
if part_name in yaml_part . keys ( ) :
return yaml_part [ part_name ]
return None
def get_rule_yaml ( self , file_path : str ) - > dict :
data = [ ]
with open ( file_path ) as f :
2019-04-22 21:21:08 +00:00
yaml_parts = yaml . safe_load_all ( f )
2019-01-22 18:25:13 +00:00
for part in yaml_parts :
data . append ( part )
return data
# Tests
def test_confirm_extension_is_yml ( self ) :
files_with_incorrect_extensions = [ ]
for file in self . yield_next_rule_file_path ( self . path_to_rules ) :
file_name_and_extension = os . path . splitext ( file )
if len ( file_name_and_extension ) == 2 :
extension = file_name_and_extension [ 1 ]
if extension != " .yml " :
files_with_incorrect_extensions . append ( file )
2020-01-30 07:50:22 +00:00
self . assertEqual ( files_with_incorrect_extensions , [ ] , Fore . RED +
2019-01-22 18:25:13 +00:00
" There are rule files with extensions other than .yml " )
def test_confirm_correct_mitre_tags ( self ) :
files_with_incorrect_mitre_tags = [ ]
for file in self . yield_next_rule_file_path ( self . path_to_rules ) :
tags = self . get_rule_part ( file_path = file , part_name = " tags " )
if tags :
for tag in tags :
if tag not in self . MITRE_ALL and tag . startswith ( " attack. " ) :
2020-01-30 07:50:22 +00:00
print ( Fore . RED + " Rule {} has the following incorrect tag {} " . format ( file , tag ) )
2019-01-22 18:25:13 +00:00
files_with_incorrect_mitre_tags . append ( file )
2019-02-18 18:05:58 +00:00
2020-01-30 07:50:22 +00:00
self . assertEqual ( files_with_incorrect_mitre_tags , [ ] , Fore . RED +
2020-02-01 14:43:11 +00:00
" There are rules with incorrect MITRE Tags. (please inform us about new tags that are not yet supported in our tests) Check the correct tags here: https://attack.mitre.org/ " )
2019-01-22 18:25:13 +00:00
2019-01-25 09:22:28 +00:00
def test_look_for_duplicate_filters ( self ) :
def check_list_or_recurse_on_dict ( item , depth : int ) - > None :
if type ( item ) == list :
check_if_list_contain_duplicates ( item , depth )
elif type ( item ) == dict and depth < = MAX_DEPTH :
for sub_item in item . values ( ) :
check_list_or_recurse_on_dict ( sub_item , depth + 1 )
def check_if_list_contain_duplicates ( item : list , depth : int ) - > None :
try :
if len ( item ) != len ( set ( item ) ) :
2020-01-30 07:50:22 +00:00
print ( Fore . RED + " Rule {} has duplicate filters " . format ( file ) )
2019-01-25 09:22:28 +00:00
files_with_duplicate_filters . append ( file )
except :
# unhashable types like dictionaries
for sub_item in item :
if type ( sub_item ) == dict and depth < = MAX_DEPTH :
check_list_or_recurse_on_dict ( sub_item , depth + 1 )
MAX_DEPTH = 3
files_with_duplicate_filters = [ ]
for file in self . yield_next_rule_file_path ( self . path_to_rules ) :
detection = self . get_rule_part ( file_path = file , part_name = " detection " )
check_list_or_recurse_on_dict ( detection , 1 )
2020-01-30 07:50:22 +00:00
self . assertEqual ( files_with_duplicate_filters , [ ] , Fore . RED +
2019-01-25 09:22:28 +00:00
" There are rules with duplicate filters " )
2019-02-13 18:27:27 +00:00
def test_single_named_condition_with_x_of_them ( self ) :
faulty_detections = [ ]
for file in self . yield_next_rule_file_path ( self . path_to_rules ) :
yaml = self . get_rule_yaml ( file_path = file )
detection = self . get_rule_part ( file_path = file , part_name = " detection " )
2019-02-18 18:05:58 +00:00
2019-02-13 18:27:27 +00:00
has_them_in_condition = " them " in detection [ " condition " ]
has_only_one_named_condition = len ( detection ) == 2
not_multipart_yaml_file = len ( yaml ) == 1
if has_them_in_condition and \
has_only_one_named_condition and \
not_multipart_yaml_file :
faulty_detections . append ( file )
2020-01-30 07:50:22 +00:00
self . assertEqual ( faulty_detections , [ ] , Fore . RED +
2019-02-13 18:27:27 +00:00
" There are rules using ' 1/all of them ' style conditions but only have one condition " )
2019-02-18 18:05:58 +00:00
def test_duplicate_titles ( self ) :
def compare_detections ( detection1 : dict , detection2 : dict ) - > bool :
# detections not the same length can't be the same
if len ( detection1 ) != len ( detection2 ) :
return False
for named_condition in detection1 :
# condition clause must be the same too
if named_condition == " condition " :
if detection1 [ " condition " ] != detection2 [ " condition " ] :
return False
else :
continue
# Named condition must exist in both rule files
if named_condition not in detection2 :
return False
if len ( detection1 [ named_condition ] ) != len ( detection2 [ named_condition ] ) :
return False
for condition in detection1 [ named_condition ] :
if type ( condition ) != str :
return False
if condition not in detection2 [ named_condition ] :
return False
condition_value1 = detection1 [ named_condition ] [ condition ]
condition_value2 = detection2 [ named_condition ] [ condition ]
if condition_value1 != condition_value2 :
return False
return True
faulty_detections = [ ]
files_and_their_detections = { }
for file in self . yield_next_rule_file_path ( self . path_to_rules ) :
detection = self . get_rule_part ( file_path = file , part_name = " detection " )
yaml = self . get_rule_yaml ( file_path = file )
is_multipart_yaml_file = len ( yaml ) != 1
if is_multipart_yaml_file :
continue
for key in files_and_their_detections :
if compare_detections ( detection , files_and_their_detections [ key ] ) :
faulty_detections . append ( ( key , file ) )
files_and_their_detections [ file ] = detection
2020-01-30 07:50:22 +00:00
self . assertEqual ( faulty_detections , [ ] , Fore . YELLOW +
2019-02-18 18:05:58 +00:00
" There are rule files with exactly the same detection logic. " )
def test_source_eventlog ( self ) :
faulty_detections = [ ]
for file in self . yield_next_rule_file_path ( self . path_to_rules ) :
detection = self . get_rule_part ( file_path = file , part_name = " detection " )
detection_str = str ( detection ) . lower ( )
if " ' source ' : ' eventlog ' " in detection_str :
faulty_detections . append ( file )
2020-01-30 07:50:22 +00:00
self . assertEqual ( faulty_detections , [ ] , Fore . YELLOW +
2019-02-18 18:05:58 +00:00
" There are detections with ' Source: Eventlog ' . This does not add value to the detection. " )
2019-03-09 18:00:11 +00:00
def test_event_id_instead_of_process_creation ( self ) :
2019-03-02 17:51:49 +00:00
faulty_detections = [ ]
for file in self . yield_next_rule_file_path ( self . path_to_rules ) :
with open ( file ) as f :
for line in f :
2019-03-09 16:23:50 +00:00
if re . search ( r ' .*EventID: (?:1|4688) \ s*$ ' , line ) and file not in faulty_detections :
2019-03-02 17:51:49 +00:00
faulty_detections . append ( file )
2020-01-30 07:50:22 +00:00
self . assertEqual ( faulty_detections , [ ] , Fore . YELLOW +
2019-03-09 18:00:11 +00:00
" There are rules still using Sysmon 1 or Event ID 4688. Please migrate to the process_creation category. " )
2019-03-02 17:51:49 +00:00
2020-01-30 15:08:24 +00:00
def test_missing_id ( self ) :
faulty_rules = [ ]
for file in self . yield_next_rule_file_path ( self . path_to_rules ) :
id = self . get_rule_part ( file_path = file , part_name = " id " )
if not id :
print ( Fore . YELLOW + " Rule {} has no field ' id ' . " . format ( file ) )
faulty_rules . append ( file )
elif len ( id ) != 36 :
print ( Fore . YELLOW + " Rule {} has a malformed ' id ' (not 36 chars). " . format ( file ) )
faulty_rules . append ( file )
self . assertEqual ( faulty_rules , [ ] , Fore . RED +
" There are rules with missing or malformed ' id ' fields. Create an id (e.g. here: https://www.uuidgenerator.net/version4) and add it to the reported rule(s). " )
2020-05-23 14:25:37 +00:00
def test_sysmon_rule_without_eventid ( self ) :
faulty_rules = [ ]
for file in self . yield_next_rule_file_path ( self . path_to_rules ) :
logsource = self . get_rule_part ( file_path = file , part_name = " logsource " )
service = logsource . get ( ' service ' , ' ' )
if service . lower ( ) == ' sysmon ' :
with open ( file ) as f :
found = False
for line in f :
if re . search ( r ' .*EventID:.*$ ' , line ) : # might be on a single line or in multiple lines
found = True
break
if not found :
faulty_rules . append ( file )
self . assertEqual ( faulty_rules , [ ] , Fore . RED +
" There are rules using sysmon events but with no EventID specified " )
2020-01-30 15:08:34 +00:00
def test_missing_date ( self ) :
faulty_rules = [ ]
for file in self . yield_next_rule_file_path ( self . path_to_rules ) :
datefield = self . get_rule_part ( file_path = file , part_name = " date " )
if not datefield :
print ( Fore . YELLOW + " Rule {} has no field ' date ' . " . format ( file ) )
faulty_rules . append ( file )
elif len ( datefield ) != 10 :
print ( Fore . YELLOW + " Rule {} has a malformed ' date ' (not 10 chars, should be YYYY/MM/DD). " . format ( file ) )
faulty_rules . append ( file )
self . assertEqual ( faulty_rules , [ ] , Fore . RED +
" There are rules with missing or malformed ' date ' fields. (create one, e.g. date: 2019/01/14) " )
2019-03-02 17:51:49 +00:00
2020-07-13 16:07:19 +00:00
def test_references ( self ) :
faulty_rules = [ ]
for file in self . yield_next_rule_file_path ( self . path_to_rules ) :
2020-07-13 16:08:00 +00:00
references = self . get_rule_part ( file_path = file , part_name = " references " )
2020-07-13 16:07:19 +00:00
if not references :
print ( Fore . YELLOW + " Rule {} has no field ' references ' . " . format ( file ) )
2020-07-13 16:16:32 +00:00
#faulty_rules.append(file)
2020-07-13 16:07:19 +00:00
elif not isinstance ( references , list ) :
print ( Fore . YELLOW + " Rule {} has a refences field that isn ' t a list. " . format ( file ) )
faulty_rules . append ( file )
self . assertEqual ( faulty_rules , [ ] , Fore . RED +
" There are rules with malformed ' references ' fields. (has to be a list of values even if it contains only a single value) " )
2020-01-30 16:26:21 +00:00
def test_title ( self ) :
faulty_rules = [ ]
2020-02-20 22:00:16 +00:00
allowed_lowercase_words = [
' the ' ,
' for ' ,
' in ' ,
' with ' ,
' via ' ,
' on ' ,
' to ' ,
' without ' ,
' of ' ,
' through ' ,
' from ' ,
' by ' ,
' as ' ,
' a ' ,
' or ' ,
' at ' ,
' and ' ,
' an ' ,
' over ' ,
' new ' ,
]
2020-01-30 16:26:21 +00:00
for file in self . yield_next_rule_file_path ( self . path_to_rules ) :
title = self . get_rule_part ( file_path = file , part_name = " title " )
if not title :
print ( Fore . RED + " Rule {} has no field ' title ' . " . format ( file ) )
faulty_rules . append ( file )
continue
elif len ( title ) > 70 :
print ( Fore . YELLOW + " Rule {} has a title field with too many characters (>70) " . format ( file ) )
faulty_rules . append ( file )
if title . startswith ( " Detects " ) :
print ( Fore . RED + " Rule {} has a title that starts with ' Detects ' " . format ( file ) )
faulty_rules . append ( file )
wrong_casing = [ ]
for word in title . split ( " " ) :
2020-05-23 14:03:13 +00:00
if word . islower ( ) and not word . lower ( ) in allowed_lowercase_words and not " . " in word and not " / " in word and not word [ 0 ] . isdigit ( ) :
2020-01-30 16:26:21 +00:00
wrong_casing . append ( word )
if len ( wrong_casing ) > 0 :
print ( Fore . RED + " Rule {} has a title that has not title capitalization. Words: ' {} ' " . format ( file , " , " . join ( wrong_casing ) ) )
faulty_rules . append ( file )
self . assertEqual ( faulty_rules , [ ] , Fore . RED +
" There are rules with non-conform ' title ' fields. Please check: https://github.com/Neo23x0/sigma/wiki/Rule-Creation-Guide#title " )
2019-03-02 17:51:49 +00:00
2019-01-22 18:25:13 +00:00
if __name__ == " __main__ " :
2020-01-30 07:37:47 +00:00
init ( autoreset = True )
2019-01-23 22:31:36 +00:00
unittest . main ( )