From 52b0f77f99dff6cacab02e1422e6a1f038cff91a Mon Sep 17 00:00:00 2001 From: LoRexxar Date: Fri, 14 Aug 2020 15:00:25 +0800 Subject: [PATCH] add config load & recover --- .gitignore | 4 +- core/__init__.py | 53 +++-- core/__version__.py | 6 +- core/cli.py | 14 +- core/detection.py | 6 +- core/engine.py | 12 +- core/rule.py | 192 +++++++++++++++++- db/kunlun.db | Bin 135168 -> 212992 bytes kunlun.py | 9 + tests/test_dependencies.py | 6 +- tests/test_detection.py | 28 +-- tests/test_directory.py | 6 +- tests/test_export.py | 18 +- tests/test_file.py | 4 +- tests/test_parser.py | 8 +- tests/test_scan.py | 6 +- utils/export.py | 8 +- utils/utils.py | 2 +- .../0002_remove_rules_vulnerability.py | 17 ++ .../migrations/0003_auto_20200813_1513.py | 23 +++ .../migrations/0004_auto_20200813_1813.py | 38 ++++ web/index/models.py | 14 +- 22 files changed, 383 insertions(+), 91 deletions(-) create mode 100644 web/index/migrations/0002_remove_rules_vulnerability.py create mode 100644 web/index/migrations/0003_auto_20200813_1513.py create mode 100644 web/index/migrations/0004_auto_20200813_1813.py diff --git a/.gitignore b/.gitignore index 38456adc..1e511665 100644 --- a/.gitignore +++ b/.gitignore @@ -111,4 +111,6 @@ settings.py # webdriver bin/* ghostdriver.log -debug.log \ No newline at end of file +debug.log + +# \ No newline at end of file diff --git a/core/__init__.py b/core/__init__.py index 6423571e..e1b5c921 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -41,14 +41,14 @@ def main(): try: # arg parse t1 = time.time() - parser = argparse.ArgumentParser(prog=__title__, description=__introduction__, epilog=__epilog__, formatter_class=argparse.RawDescriptionHelpFormatter, usage=argparse.SUPPRESS) + parser = argparse.ArgumentParser(prog=__title__, description=__introduction__.format(detail="Main Program"), epilog=__epilog__, formatter_class=argparse.RawDescriptionHelpFormatter, usage=argparse.SUPPRESS) subparsers = parser.add_subparsers() - parser_group_core = subparsers.add_parser('config', help='config for rule&tamper', description='config for rule&tamper', usage=argparse.SUPPRESS, add_help=True) - parser_group_core.add_argument('load', action='store_true', default=False, help='load rule&tamper') + parser_group_core = subparsers.add_parser('config', help='config for rule&tamper', description=__introduction__.format(detail='config for rule&tamper'), formatter_class=argparse.RawDescriptionHelpFormatter, usage=argparse.SUPPRESS, add_help=True) + parser_group_core.add_argument('load', choices=['load', 'recover'], default=False, help='operate for rule&tamper') - parser_group_scan = subparsers.add_parser('scan', help='scan target path', description='scan target path', epilog=__scan_epilog__, formatter_class=argparse.RawDescriptionHelpFormatter, add_help=True) + parser_group_scan = subparsers.add_parser('scan', help='scan target path', description=__introduction__.format(detail='scan target path'), epilog=__scan_epilog__, formatter_class=argparse.RawDescriptionHelpFormatter, add_help=True) parser_group_scan.add_argument('-t', '--target', dest='target', action='store', default='', metavar='', help='file, folder, compress, or repository address') parser_group_scan.add_argument('-f', '--format', dest='format', action='store', default='csv', metavar='', choices=['html', 'json', 'csv', 'xml'], help='vulnerability output format (formats: %(choices)s)') parser_group_scan.add_argument('-o', '--output', dest='output', action='store', default='', metavar='', help='vulnerability output STREAM, FILE') @@ -61,10 +61,10 @@ def main(): parser_group_scan.add_argument('-d', '--debug', dest='debug', action='store_true', default=False, help='open debug mode') - parser_group_scan.add_argument('-uc', '--unconfirm', dest='unconfirm', action='store_true', default=False, help='show unconfirmed vuls') - parser_group_scan.add_argument('-upc', '--unprecom', dest='unprecom', action='store_true', default=False, help='without Precompiled') + parser_group_scan.add_argument('-uc', '--unconfirm', dest='unconfirm', action='store_false', default=False, help='show unconfirmed vuls') + parser_group_scan.add_argument('-upc', '--unprecom', dest='unprecom', action='store_false', default=False, help='without Precompiled') - parser_group_show = subparsers.add_parser('show', help='show rule&tamper', description='show rule&tamper', usage=argparse.SUPPRESS, add_help=True) + parser_group_show = subparsers.add_parser('show', help='show rule&tamper', description=__introduction__.format(detail='show rule&tamper'), formatter_class=argparse.RawDescriptionHelpFormatter, usage=argparse.SUPPRESS, add_help=True) parser_group_show.add_argument('-list', '--list', dest='list', action='store', default=None, help='show all rules') parser_group_show.add_argument('-listt', '--listtamper', dest='listtamper', action='store', default=None, @@ -73,36 +73,47 @@ def main(): args = parser.parse_args() # log - if args.log: + if hasattr(args, "log") and args.log: log(logging.INFO, args.log) else: log(logging.INFO, str(time.time())) - if args.debug: + if hasattr(args, "debug") and args.debug: logger.setLevel(logging.DEBUG) logger.debug('[INIT] set logging level: debug') - RuleCheck().run() - if args.load: - logger.info("[INIT] RuleCheck finished.") - exit() + if hasattr(args, "load"): + if args.load == "load": + logger.info("[INIT] RuleCheck start.") + RuleCheck().load() - if args.list or args.listtamper: - if args.list: - logger.info("Show List:\n{}".format(show_info('rule', args.list.strip("")))) + logger.info("[INIT] RuleCheck finished.") + exit() - if args.listtamper: - logger.info("Show Tamper List:\n{}".format(show_info('tamper', args.listtamper.strip("")))) + elif args.load == "recover": + logger.info("[INIT] RuleRecover start.") + RuleCheck().recover() - exit() + logger.info("[INIT] RuleRecover finished.") + exit() + + if hasattr(args, "list"): + if args.list or args.listtamper: + if args.list: + logger.info("Show List:\n{}".format(show_info('rule', args.list.strip("")))) + + if args.listtamper: + logger.info("Show Tamper List:\n{}".format(show_info('tamper', args.listtamper.strip("")))) + + exit() - if args.target == '' and args.output == '': + if (not hasattr(args, "target") or args.target == '') or (not hasattr(args, "output") or args.output == ''): parser.print_help() exit() logger.debug('[INIT] start scanning...') - if args.sid: + if hasattr(args, "sid") and args.sid: a_sid = args.sid else: a_sid = get_sid(args.target, True) diff --git a/core/__version__.py b/core/__version__.py index be0c246a..b757f16b 100644 --- a/core/__version__.py +++ b/core/__version__.py @@ -22,7 +22,11 @@ GitHub: https://github.com/LoRexxar/Kunlun-M -KunLun-M is a static code analysis system that automates the detecting vulnerabilities and security issue.""".format(version=__version__) +KunLun-M is a static code analysis system that automates the detecting vulnerabilities and security issue. + +{{detail}} + +""".format(version=__version__) __epilog__ = """Usage: python {m} scan -t {td} python {m} scan -t {td} -r 1000, 1001 diff --git a/core/cli.py b/core/cli.py index 42e1696f..f22b43cf 100644 --- a/core/cli.py +++ b/core/cli.py @@ -23,7 +23,7 @@ from utils.file import Directory from utils.utils import ParseArgs from utils.utils import md5, random_generator -from Kunlun_M.settings import rules_path +from Kunlun_M.settings import RULES_PATH def get_sid(target, is_a_sid=False): @@ -151,32 +151,32 @@ def list_parse(rules_path, istamp=False): if type == "rule": - rule_lan_list = list_parse(rules_path) + rule_lan_list = list_parse(RULES_PATH) rule_dict = {} if key == "all": # show all for lan in rule_lan_list: info_dict[lan] = [] - rule_lan_path = os.path.join(rules_path, lan) + rule_lan_path = os.path.join(RULES_PATH, lan) info_dict[lan] = list_parse(rule_lan_path) elif key in rule_lan_list: info_dict[key] = [] - rule_lan_path = os.path.join(rules_path, key) + rule_lan_path = os.path.join(RULES_PATH, key) info_dict[key] = list_parse(rule_lan_path) elif str(int(key)) == key: for lan in rule_lan_list: info_dict[lan] = [] - rule_lan_path = os.path.join(rules_path, lan) + rule_lan_path = os.path.join(RULES_PATH, lan) info_dict[lan] = list_parse(rule_lan_path) for lan in info_dict: if "CVI_{}.py".format(key) in info_dict[lan]: - f = codecs.open(os.path.join(rules_path, lan, "CVI_{}.py".format(key)), encoding='utf-8', errors="ignore") + f = codecs.open(os.path.join(RULES_PATH, lan, "CVI_{}.py".format(key)), encoding='utf-8', errors="ignore") return f.read() logger.error('[Show] no CVI id {}.'.format(key)) @@ -214,7 +214,7 @@ def list_parse(rules_path, istamp=False): table.align = 'l' i = 0 - tamp_path = os.path.join(rules_path, 'tamper/') + tamp_path = os.path.join(RULES_PATH, 'tamper/') tamp_list = list_parse(tamp_path, True) if key == "all": diff --git a/core/detection.py b/core/detection.py index c5dd00b2..e2202b3d 100644 --- a/core/detection.py +++ b/core/detection.py @@ -16,7 +16,7 @@ import xml.etree.ElementTree as eT from .dependencies import Dependencies from utils.log import logger -from Kunlun_M.settings import rules_path +from Kunlun_M.settings import RULES_PATH try: # for pip >= 10 from pip._internal.req import parse_requirements @@ -41,7 +41,7 @@ def __init__(self, target_directory, files): self.frame_data = {} self.language_data = {} self.project_data = [] - self.rules_path = rules_path + self.rules_path = RULES_PATH @property def language(self): @@ -200,7 +200,7 @@ def _read_xml(self, filename): @staticmethod def rule(): - framework_path = os.path.join(rules_path, 'frameworks.xml') + framework_path = os.path.join(RULES_PATH, 'frameworks.xml') tree = eT.ElementTree(file=framework_path) return tree diff --git a/core/engine.py b/core/engine.py index ccd805f4..2ca3ea1e 100644 --- a/core/engine.py +++ b/core/engine.py @@ -29,7 +29,7 @@ from rules.autorule import autorule from Kunlun_M import const -from Kunlun_M.settings import running_path +from Kunlun_M.settings import RUNNING_PATH from Kunlun_M.const import ext_dict from Kunlun_M.const import VulnerabilityResult @@ -48,7 +48,7 @@ def init_list(self, data=None): :param data: list or a string :return: """ - file_path = os.path.join(running_path, '{sid}_list'.format(sid=self.sid)) + file_path = os.path.join(RUNNING_PATH, '{sid}_list'.format(sid=self.sid)) if not os.path.exists(file_path): if isinstance(data, list): with open(file_path, 'w') as f: @@ -66,7 +66,7 @@ def init_list(self, data=None): })) def list(self, data=None): - file_path = os.path.join(running_path, '{sid}_list'.format(sid=self.sid)) + file_path = os.path.join(RUNNING_PATH, '{sid}_list'.format(sid=self.sid)) if data is None: with open(file_path, 'r') as f: portalocker.lock(f, portalocker.LOCK_EX) @@ -86,7 +86,7 @@ def list(self, data=None): f.write(json.dumps(result)) def status(self, data=None): - file_path = os.path.join(running_path, '{sid}_status'.format(sid=self.sid)) + file_path = os.path.join(RUNNING_PATH, '{sid}_status'.format(sid=self.sid)) if data is None: with open(file_path) as f: portalocker.lock(f, portalocker.LOCK_EX) @@ -100,7 +100,7 @@ def status(self, data=None): def data(self, data=None): - file_path = os.path.abspath(running_path + '/{sid}_data'.format(sid=self.sid)) + file_path = os.path.abspath(RUNNING_PATH + '/{sid}_data'.format(sid=self.sid)) if data is None: with open(file_path) as f: @@ -118,7 +118,7 @@ def is_file(self, is_data=False): ext = 'data' else: ext = 'status' - file_path = os.path.join(running_path, '{sid}_{ext}'.format(sid=self.sid, ext=ext)) + file_path = os.path.join(RUNNING_PATH, '{sid}_{ext}'.format(sid=self.sid, ext=ext)) return os.path.isfile(file_path) diff --git a/core/rule.py b/core/rule.py index b58d9e4a..5ae82b17 100644 --- a/core/rule.py +++ b/core/rule.py @@ -12,9 +12,13 @@ :copyright: Copyright (c) 2017 LoRexxar. All rights reserved """ import os -from Kunlun_M.settings import rules_path +import inspect +import codecs +from Kunlun_M.settings import RULES_PATH from utils.log import logger +from web.index.models import Rules + def block(index): default_index_reverse = 'in-function' @@ -53,7 +57,7 @@ def __init__(self, lans=["php"]): # 逐个处理每一种lan for lan in lans: - self.rules_path = rules_path + "/" + lan + self.rules_path = RULES_PATH + "/" + lan if not os.path.exists(self.rules_path): logger.error("[INIT][RULE] language {} can't found rules".format(self.rules_path)) os.mkdir(self.rules_path) @@ -113,7 +117,14 @@ class RuleCheck: def __init__(self): self.rule_dict = {} - self.rule_base_path = rules_path + self.rule_base_path = RULES_PATH + + self.CONFIG_LIST = ["vulnerability", "language", "author", "description", "status", "match_mode", + "match", "vul_function", "main_function"] + + self.SOLIDITY_CONFIG_LIST = ['match_name', 'black_list', 'unmatch'] + self.REGEX_CONFIG_LIST = ['unmatch'] + self.CHROME_CONFIG_LIST = ['keyword', 'unmatch'] def list_parse(self, rules_path, istamp=False): @@ -138,7 +149,178 @@ def list_parse(self, rules_path, istamp=False): return result - def run(self): - print(self.list_parse(self.rule_base_path)) + def get_all_rules(self): + rule_lan_list = self.list_parse(self.rule_base_path) + + for lan in rule_lan_list: + self.rule_dict[lan] = [] + rule_lan_path = os.path.join(self.rule_base_path, lan) + + self.rule_dict[lan] = self.list_parse(rule_lan_path) + + def load_rules(self, ruleclass): + + main_function_content = inspect.getsourcelines(ruleclass.main) + match_name = "" + black_list = "" + unmatch = "" + keyword = "" + + if ruleclass.match_mode == "regex-return-regex": + match_name = ruleclass.match_name + black_list = ruleclass.black_list + unmatch = ruleclass.unmatch + elif ruleclass.match_mode == "only-regex": + unmatch = ruleclass.unmatch + elif ruleclass.match_mode == "special-crx-keyword-match": + unmatch = ruleclass.unmatch + keyword = ruleclass.keyword + + r = Rules(rule_name=ruleclass.vulnerability, svid=ruleclass.svid, + language=ruleclass.language.lower(), author=ruleclass.author, + description=ruleclass.description, status=ruleclass.status, + match_mode=ruleclass.match_mode, match=ruleclass.match, + match_name=match_name, black_list=black_list, unmatch=unmatch, keyword=keyword, + vul_function=ruleclass.vul_function, main_function=main_function_content) + + r.save() + + return True + + def check_and_update_rule_database(self, ruleconfig_content, nowrule, config): + + svid = nowrule.svid + ruleconfig_content = str(ruleconfig_content).lower() + + if ruleconfig_content != str(getattr(nowrule, config)).lower(): + logger.warning("[INIT][Rule Check] CVI_{} config {} has changed:".format(svid, config)) + logger.warning("[INIT][Rule Check] {} in Rule File is {}".format(config, ruleconfig_content)) + logger.warning("[INIT][Rule Check] {} in Database is {}".format(config, getattr(nowrule, config))) + + logger.warning("[INIT][Rule Check] whether load new {} from Rule File(Y/N):".format(config)) + if input().lower() != 'n': + setattr(nowrule, config, ruleconfig_content) + + nowrule.save() + return True + + def check_rules(self, ruleclass, nowrule): + + for config in self.CONFIG_LIST: + if config != "main_function": + if config == "vulnerability": + config1 = "rule_name" + else: + config1 = config + + self.check_and_update_rule_database(getattr(ruleclass, config), nowrule, config1) + + else: + main_function_content = inspect.getsource(ruleclass.main) + config1 = "main_function" + + self.check_and_update_rule_database(main_function_content, nowrule, config1) + + # for special match_mode + if ruleclass.match_mode == "regex-return-regex": + for config in self.SOLIDITY_CONFIG_LIST: + self.check_and_update_rule_database(getattr(ruleclass, config), nowrule, config) + elif ruleclass.match_mode == "only-regex": + for config in self.REGEX_CONFIG_LIST: + self.check_and_update_rule_database(getattr(ruleclass, config), nowrule, config) + elif ruleclass.match_mode == "special-crx-keyword-match": + for config in self.CHROME_CONFIG_LIST: + self.check_and_update_rule_database(getattr(ruleclass, config), nowrule, config) + + nowrule.save() + return True + + def load(self): + """ + load rule from file to database + :return: + """ + + self.get_all_rules() + i = 0 + + for lan in self.rule_dict: + for rule in self.rule_dict[lan]: + i += 1 + rulename = rule.split('.')[0] + rulefile = "rules." + lan + "." + rulename + + rule_obj = __import__(rulefile, fromlist=rulename) + p = getattr(rule_obj, rulename) + + ruleclass = p() + + r = Rules.objects.filter(svid=ruleclass.svid).first() + + if not r: + + logger.info("[INIT][Load Rules] New Rule CVI_{} {}".format(ruleclass.svid, ruleclass.vulnerability)) + self.load_rules(ruleclass) + + else: + logger.info("[INIT][Load Rules] Check Rule CVI_{} {}".format(ruleclass.svid, ruleclass.vulnerability)) + + self.check_rules(ruleclass, r) return True + + def recover(self): + """ + recover rule from database to file + :return: + """ + rules = Rules.objects.all() + + for rule in rules: + lan = rule.language + + if not os.path.isdir(os.path.join(RULES_PATH, lan)): + os.mkdir(os.path.join(RULES_PATH, lan)) + + rule_lan_path = os.path.join(RULES_PATH, lan) + svid = rule.svid + + rule_path = os.path.join(rule_lan_path, "CVI_{}.py".format(svid)) + + if os.path.exists(rule_path): + logger.warning("[INIT][Recover] Rule file CVI_{}.py exist. whether overwrite file? (Y/N)".format(svid)) + + if input().lower() == 'n': + continue + + logger.info("[INIT][Recover] Recover new Rule file CVI_{}.py".format(svid)) + + template_file = codecs.open(os.path.join(RULES_PATH, 'rule.template'), 'rb+', encoding='utf-8', errors='ignore') + template_file_content = template_file.read() + template_file.close() + + rule_file = codecs.open(rule_path, "wb+", encoding='utf-8', errors='ignore') + + rule_name = rule.rule_name + svid = rule.svid + language = rule.language + author = rule.author + description = rule.description + status = "True" if rule.status else "False" + match_mode = rule.match_mode + match = '"{}"'.format(rule.match) if rule.match and "[" != rule.match[0] else rule.match + match_name = '"{}"'.format(rule.match_name) if rule.match_name and "[" != rule.match_name[0] else rule.match_name + black_list = '"{}"'.format(rule.black_list) if rule.black_list and "[" != rule.black_list[0] else rule.black_list + keyword = '"{}"'.format(rule.keyword) if rule.keyword and "[" != rule.keyword[0] else rule.keyword + unmatch = '"{}"'.format(rule.unmatch) if rule.unmatch and "[" != rule.unmatch[0] else rule.unmatch + vul_function = rule.vul_function if rule.vul_function else "None" + main_function = rule.main_function + + rule_file.write(template_file_content.format(rule_name=rule_name, svid=svid, language=language, + author=author, description=description, status=status, + match_mode=match_mode, match=match, match_name=match_name, + black_list=black_list, keyword=keyword, unmatch=unmatch, + vul_function=vul_function, main_function=main_function)) + + rule_file.close() + diff --git a/db/kunlun.db b/db/kunlun.db index 6a25166e6dcc42ddf9806407e3c580cec64d801a..ece8831473810636c1d222ccde7bda93818cce3f 100644 GIT binary patch literal 212992 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 b83_;KRIo5P7)b~D;Wd=bCW-Y1)ZqUgEepbT delta 401 zcmYjLKS%;$9Q}Uxao**rpBAB}krtGYV&PbXe~21_sKx^G&qtk{r|1>6X<<`TBJnj< z5Dg7dal-{JK~qCSdtd}jL3>*^1U-20c<;e`m5Q};E*^OtE`*T3!#_;cgAs7uuAULm zZS9!E5@-na0p0K8EPBBEifufpA<`l>@=2QHRCg51q)`jc^^s27EoO5mb*+%h6jY6> zT2k9CQlx)|ePb6=#jZ=m?`LeoQ%s0LzZrYDJlvo!v|O);*m4vi224X68hP%;m`cEd zH-vkU9bF@_DZH8P_;-E+AL13Ribv43tzeB?W~D`oWN!Vw#{-RVdv$nsJ|sAYhG36s zskB06HA}aWr45Ca|4D&8tizLab3;*SDV3HLS=(l93U1a)Ll5iAftg*&Fu@)(Kp0EG zAp4-5fX{)KePrO+)wPbg7e)ces%hNxJC7nklXJ$;916IMlPr|6aS(+YPC^KloYk%v b?1b5U41BB^2a&ZC;4$_Sz=P4s!Sv-1#b|Lx diff --git a/kunlun.py b/kunlun.py index 1c024993..f4fb4984 100644 --- a/kunlun.py +++ b/kunlun.py @@ -1,13 +1,22 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import os import re import sys +# for django +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Kunlun_M.settings') + +import django + +django.setup() + from core import main if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) sys.exit(main()) diff --git a/tests/test_dependencies.py b/tests/test_dependencies.py index 6396736e..1717207d 100644 --- a/tests/test_dependencies.py +++ b/tests/test_dependencies.py @@ -15,11 +15,11 @@ import os import xml.etree.ElementTree as eT from core.dependencies import Dependencies -from Kunlun_M.settings import project_directory +from Kunlun_M.settings import PROJECT_DIRECTORY -requirements = project_directory+'/tests/vulnerabilities/requirements.txt' -pom = project_directory+'/tests/vulnerabilities/pom.xml' +requirements = PROJECT_DIRECTORY+'/tests/vulnerabilities/requirements.txt' +pom = PROJECT_DIRECTORY+'/tests/vulnerabilities/pom.xml' def test_find_file(): diff --git a/tests/test_detection.py b/tests/test_detection.py index 1c9ff500..cb61ec36 100644 --- a/tests/test_detection.py +++ b/tests/test_detection.py @@ -14,11 +14,11 @@ """ import xml.etree.ElementTree as eT from core.detection import Detection -from Kunlun_M.settings import project_directory +from Kunlun_M.settings import PROJECT_DIRECTORY -vul_path = project_directory+'/tests/vulnerabilities/' -examples_path = project_directory+'/tests/examples' +vul_path = PROJECT_DIRECTORY+'/tests/vulnerabilities/' +EXAMPLES_PATH = PROJECT_DIRECTORY+'/tests/examples' def test_framework(): @@ -28,7 +28,7 @@ def test_framework(): def test_param_xml(): - detection = Detection(examples_path, '.') + detection = Detection(EXAMPLES_PATH, '.') frame_data = {} language_data = {} tree = detection.rule() @@ -39,14 +39,14 @@ def test_param_xml(): def test_rule(): - detection = Detection(examples_path, '.') - root = eT.ElementTree(file=examples_path+'/param_xml.xml') + detection = Detection(EXAMPLES_PATH, '.') + root = eT.ElementTree(file=EXAMPLES_PATH+'/param_xml.xml') tree = detection.rule() assert type(root) is type(tree) def test_get_dict(): - detection = Detection(examples_path, '.') + detection = Detection(EXAMPLES_PATH, '.') extension = ['php', 'js', 'java'] type_num = {} type_num = detection.get_dict(extension, type_num) @@ -56,30 +56,30 @@ def test_get_dict(): def test_project_information(): extension = ['php', 'js', 'java'] - allfiles = Detection.project_information(examples_path, extension) - assert examples_path+'/cloc.html' in allfiles + allfiles = Detection.project_information(EXAMPLES_PATH, extension) + assert EXAMPLES_PATH+'/cloc.html' in allfiles def test_count_py_line(): - count = Detection.count_py_line(examples_path+'/cloc.py') + count = Detection.count_py_line(EXAMPLES_PATH+'/cloc.py') type_count = ['count_blank', 'count_code', 'count_pound'] assert count['count_code'] == 5 def test_count_php_line(): - count = Detection.count_php_line(examples_path+'/cloc.php') + count = Detection.count_php_line(EXAMPLES_PATH+'/cloc.php') type_count = ['count_blank', 'count_code', 'count_pound'] assert count['count_code'] == 2 def test_count_java_line(): - count = Detection.count_java_line(examples_path+'/cloc.java') + count = Detection.count_java_line(EXAMPLES_PATH+'/cloc.java') type_count = ['count_blank', 'count_code', 'count_pound'] assert count['count_code'] == 1 def test_count_data_line(): - count = Detection.count_data_line(examples_path+'/param_xml.xml') + count = Detection.count_data_line(EXAMPLES_PATH+'/param_xml.xml') type_count = ['count_blank', 'count_code', 'count_pound'] assert count['count_code'] == 81 @@ -112,4 +112,4 @@ def test_count_total_num(): def test_cloc(): - assert Detection(examples_path, '.').cloc() + assert Detection(EXAMPLES_PATH, '.').cloc() diff --git a/tests/test_directory.py b/tests/test_directory.py index 017a1995..c5559d10 100644 --- a/tests/test_directory.py +++ b/tests/test_directory.py @@ -12,12 +12,12 @@ :copyright: Copyright (c) 2017 Feei. All rights reserved """ import os -from Kunlun_M.settings import project_directory +from Kunlun_M.settings import PROJECT_DIRECTORY from utils.file import Directory def test_file(): - absolute_path = os.path.join(project_directory, 'kunlun.py') + absolute_path = os.path.join(PROJECT_DIRECTORY, 'kunlun.py') files, file_sum, time_consume = Directory(absolute_path).collect_files() ext, ext_info = files[0] assert '.py' == ext @@ -28,6 +28,6 @@ def test_file(): def test_directory(): - absolute_path = project_directory + absolute_path = PROJECT_DIRECTORY files, file_sum, time_consume = Directory(absolute_path).collect_files() assert len(files) > 1 diff --git a/tests/test_export.py b/tests/test_export.py index 93c96929..b5ced8ce 100644 --- a/tests/test_export.py +++ b/tests/test_export.py @@ -14,10 +14,10 @@ import json import os -from Kunlun_M.settings import running_path, export_path +from Kunlun_M.settings import RUNNING_PATH, EXPORT_PATH from utils.export import write_to_file, dict_to_pretty_table -scan_data_file = os.path.join(running_path, 'abcdefg_data') +scan_data_file = os.path.join(RUNNING_PATH, 'abcdefg_data') if not os.path.exists(scan_data_file): with open(scan_data_file, 'w') as f: scan_data = r"""{"code": 1001, "msg": "scan finished", "result": {"extension": 18, "file": 132, "framework": "Unknown Framework", "language": "python", "push_rules": 43, "target_directory": "/tmp/core/git/shadowsocks/shadowsocks/", "trigger_rules": 1, "vulnerabilities": [{"code_content": " assert '127.0.1.1' not in ip_network", "commit_author": "Sunny", "commit_time": "2015-01-31 19:50:10", "file_path": "shadowsocks/common.py", "id": "130005", "language": "*", "level": "4", "line_number": "294", "match_result": null, "rule_name": "\u786c\u7f16\u7801IP", "solution": "## \u5b89\u5168\u98ce\u9669\n \u786c\u7f16\u7801IP\n\n ## \u4fee\u590d\u65b9\u6848\n \u79fb\u5230\u914d\u7f6e\u6587\u4ef6\u4e2d"}, {"code_content": " assert '192.168.1.2' not in ip_network", "commit_author": "Sunny", "commit_time": "2015-01-31 19:50:10", "file_path": "shadowsocks/common.py", "id": "130005", "language": "*", "level": "4", "line_number": "300", "match_result": null, "rule_name": "\u786c\u7f16\u7801IP", "solution": "## \u5b89\u5168\u98ce\u9669\n \u786c\u7f16\u7801IP\n\n ## \u4fee\u590d\u65b9\u6848\n \u79fb\u5230\u914d\u7f6e\u6587\u4ef6\u4e2d"}, {"code_content": " assert '192.0.2.1' in ip_network", "commit_author": "Sunny", "commit_time": "2015-02-01 00:17:03", "file_path": "shadowsocks/common.py", "id": "130005", "language": "*", "level": "4", "line_number": "301", "match_result": null, "rule_name": "\u786c\u7f16\u7801IP", "solution": "## \u5b89\u5168\u98ce\u9669\n \u786c\u7f16\u7801IP\n\n ## \u4fee\u590d\u65b9\u6848\n \u79fb\u5230\u914d\u7f6e\u6587\u4ef6\u4e2d"}, {"code_content": " assert '192.0.3.1' in ip_network # 192.0.2.0 is treated as 192.0.2.0/23", "commit_author": "Sunny", "commit_time": "2015-02-01 00:17:03", "file_path": "shadowsocks/common.py", "id": "130005", "language": "*", "level": "4", "line_number": "302", "match_result": null, "rule_name": "\u786c\u7f16\u7801IP", "solution": "## \u5b89\u5168\u98ce\u9669\n \u786c\u7f16\u7801IP\n\n ## \u4fee\u590d\u65b9\u6848\n \u79fb\u5230\u914d\u7f6e\u6587\u4ef6\u4e2d"}, {"code_content": " IPNetwork(config.get('forbidden_ip', '127.0.0.0/8,::1/128'))", "commit_author": "loggerhead", "commit_time": "2016-11-20 14:59:32", "file_path": "shadowsocks/shell.py", "id": "130005", "language": "*", "level": "4", "line_number": "146", "match_result": null, "rule_name": "\u786c\u7f16\u7801IP", "solution": "## \u5b89\u5168\u98ce\u9669\n \u786c\u7f16\u7801IP\n\n ## \u4fee\u590d\u65b9\u6848\n \u79fb\u5230\u914d\u7f6e\u6587\u4ef6\u4e2d"}]}}""" @@ -28,9 +28,9 @@ def test_export_to_json(): write_to_file(target=target, sid='abcdefg', output_format='json', filename='test.json') - assert os.path.exists(os.path.join(export_path, 'test.json')) + assert os.path.exists(os.path.join(EXPORT_PATH, 'test.json')) - with open(os.path.join(export_path, 'test.json')) as f: + with open(os.path.join(EXPORT_PATH, 'test.json')) as f: json_string = f.read() # JSON format assert isinstance(json.loads(json_string), dict) @@ -47,14 +47,14 @@ def test_export_to_json(): # rule_name assert "硬编码IP" in json_string - os.remove(os.path.join(export_path, 'test.json')) + os.remove(os.path.join(EXPORT_PATH, 'test.json')) def test_export_to_xml(): write_to_file(target=target, sid='abcdefg', output_format='xml', filename='test.xml') - assert os.path.exists(os.path.join(export_path, 'test.xml')) + assert os.path.exists(os.path.join(EXPORT_PATH, 'test.xml')) - with open(os.path.join(export_path, 'test.xml')) as f: + with open(os.path.join(EXPORT_PATH, 'test.xml')) as f: xml_string = f.read() # XML tag assert "