diff --git a/Kunlun_M/const.py b/Kunlun_M/const.py index a632c521..7176acb2 100644 --- a/Kunlun_M/const.py +++ b/Kunlun_M/const.py @@ -86,11 +86,11 @@ } VENDOR_ECOSYSTEM = { - "java": {"depsdev": "maven"}, - 'golang': {"depsdev": "go"}, - 'python': {"ossindex": "pypi"}, - 'php': {"ossindex": "composer"}, - 'nodejs': {"depsdev": "npm"}, + "java": {"depsdev": "maven", "murphysec": "java"}, + 'golang': {"depsdev": "go", "murphysec": "golang"}, + 'python': {"ossindex": "pypi", "murphysec": "python"}, + 'php': {"ossindex": "composer", "murphysec": "php"}, + 'nodejs': {"depsdev": "npm", "murphysec": "js"}, } VENDOR_VUL_LEVEL = ['None', 'low', 'low', 'low', 'medium', 'medium', 'medium', 'medium', 'high', 'high', 'high'] diff --git a/Kunlun_M/settings.py.bak b/Kunlun_M/settings.py.bak index d7573af6..7089be85 100644 --- a/Kunlun_M/settings.py.bak +++ b/Kunlun_M/settings.py.bak @@ -206,3 +206,6 @@ REMOTE_URL_APITOKEN = "secret_api_token_in_server" # vendor vuln scan WITH_VENDOR = True ACTIVE_SCA_SYSTEM = ['depsdev', 'ossindex'] +# ['depsdev', 'ossindex', 'murphysec'] +MURPHYSEC_TOKEN = "" + diff --git a/README.md b/README.md index 70d89f83..14d8b1f2 100644 --- a/README.md +++ b/README.md @@ -329,4 +329,5 @@ KunLun-M 是 404Team [星链计划](https://github.com/knownsec/404StarLink-Proj 次要贡献者: - Dubhe [Sndav](https://github.com/Sndav) - [#jax777](https://github.com/jax777) +- [lavon321](https://github.com/lavon321) - [akkuman](https://github.com/akkuman) diff --git a/core/core_engine/php/parser.py b/core/core_engine/php/parser.py index ac8db078..7c894c93 100644 --- a/core/core_engine/php/parser.py +++ b/core/core_engine/php/parser.py @@ -32,6 +32,7 @@ is_repair_functions = [] # 修复函数初始化 is_controlled_params = [] scan_chain = [] # 回溯链变量 +all_nodes = [] BASE_FUNCTIONCALL_LIST = ['FunctionCall', 'MethodCall', 'StaticMethodCall', 'ObjectProperty'] SPECIAL_FUNCTIONCALL_LIST = ['Eval', 'Echo', 'Print', 'Return', 'Break', 'Include', 'Require', 'Exit', 'Throw', 'Unset', 'Continue', 'Yield', 'Silence'] @@ -428,6 +429,9 @@ def is_controllable(expr, flag=None): # 获取表达式中的变量,看是否 if isinstance(expr, php.Variable): expr = expr.name + if isinstance(expr, php.Cast): + expr = expr.expr.name + if expr in controlled_params: # 当为可控变量时 返回1 logger.debug('[AST] is_controllable --> {expr}'.format(expr=expr)) if flag: @@ -511,6 +515,26 @@ def function_back(param, nodes, function_params, vul_function=None, file_path=No vul_function=vul_function, file_path=file_path, isback=isback, parent_node=parent_node) + return is_co, cp, expr_lineno + + if isinstance(node, php.Class): + class_nodes = node.nodes + + for class_node in class_nodes: + if isinstance(class_node, php.Method) and class_node.name == function_name: + method_nodes = class_node.nodes + + for method_node in method_nodes: + + if isinstance(method_node, php.Return): + return_node = method_node.node + # return_param = return_node.node + + is_co, cp, expr_lineno = parameters_back(return_node, method_nodes, function_params, + vul_function=vul_function, file_path=file_path, + isback=isback, parent_node=parent_node) + return is_co, cp, expr_lineno + return is_co, cp, expr_lineno @@ -643,7 +667,12 @@ def new_class_back(param, nodes, vul_function=None, file_path=None, isback=None) :return: """ param = param.name - param_name = param.name + if hasattr(param, "name"): + # param_name = param.name + param_name = get_node_name(param) + else: + param_name = param + param_params = param.params is_co = -1 @@ -753,7 +782,7 @@ def parameters_back(param, nodes, function_params=None, lineno=0, cp = param return is_co, cp, expr_lineno - if param_name == param_node and not isinstance(param_expr, list): # 找到变量的来源,开始继续分析变量的赋值表达式是否可控 + if param_name == param_node and not isinstance(param_expr, list) and not isinstance(param_expr, php.TernaryOp): # 找到变量的来源,开始继续分析变量的赋值表达式是否可控 logger.debug( "[AST] Find {}={} in line {}, start ast for param {}".format(param_name, param_expr, expr_lineno, param_expr)) @@ -777,6 +806,21 @@ def parameters_back(param, nodes, function_params=None, lineno=0, param = node.expr else: param = php.Variable(param_expr) # 每次找到一个污点的来源时,开始跟踪新污点,覆盖旧污点 + param_name = param_expr + + if param_name == param_node and isinstance(param_expr, php.TernaryOp): + terna1 = param_expr.iftrue + terna2 = param_expr.iffalse + param_ex = param_expr.expr + logger.debug("[AST] Find {} from TernaryOp from ?{}:{} in line {}.".format(param_name, terna1, terna2, + node.lineno)) + + file_path = os.path.normpath(file_path) + code = "{}={}?{}:{}".format(param_name, param_ex, terna1, terna2) + scan_chain.append(('TernaryOp', code, file_path, node.lineno)) + + param = node.expr + is_co = 3 if param_name == param_node and isinstance(node.expr, php.FunctionCall): # 当变量来源是函数时,处理函数内容 function_name = node.expr.name @@ -1213,6 +1257,42 @@ def parameters_back(param, nodes, function_params=None, lineno=0, code = "New {} param back from For".format(param) scan_chain.append(('NewForBack', code, file_path, node.lineno)) + elif isinstance(node, php.Foreach): + if param_name == node.valvar.name.name: + if isinstance(node.expr, php.ArrayOffset): + param_expr = node.expr.node + else: + param_expr = node.expr.name + expr_lineno = node.lineno + # 找到变量的来源,开始继续分析变量的赋值表达式是否可控 + logger.debug( + "[AST] Find foreach {} as {} in line {}, start ast for param {}".format(param_expr, param_name, expr_lineno,param_expr)) + + file_path = os.path.normpath(file_path) + code = "foreach ({} as {})".format(param_expr, param_name) + scan_chain.append(('Foreach', code, file_path, node.lineno)) + param = php.Variable(param_expr) # 每次找到一个污点的来源时,开始跟踪新污点,覆盖旧污点 + param_name = param_expr + else: + foreach_nodes = node.node.nodes + foreach_node_lineno = node.node.lineno + logger.debug("[AST] Find foreach, start ast in foreach") + + is_co, cp, expr_lineno = parameters_back(param, foreach_nodes, function_params, foreach_node_lineno, + function_flag=1, vul_function=vul_function, file_path=file_path, + isback=isback, parent_node=node) + function_flag = 0 + + if is_co in [-1, 1, 2]: # 目标确定直接返回 + return is_co, cp, expr_lineno, param_name, param + + if _is_co == 3 and cp != param: + param = _cp + + file_path = os.path.normpath(file_path) + code = "New {} param back from Foreach".format(param) + scan_chain.append(('NewForBack', code, file_path, node.lineno)) + if is_co == 3 or int(lineno) == node.lineno: # 当is_co为True时找到可控,停止递归 is_co, cp, expr_lineno = parameters_back(param, nodes[:-1], function_params, lineno, function_flag=function_flag, vul_function=vul_function, @@ -2030,6 +2110,39 @@ def analysis(nodes, vul_function, back_node, vul_lineno, file_path=None, functio nodes = get_silence_params(node) analysis(nodes, vul_function, back_node, vul_lineno, file_path) + elif isinstance(node, php.AssignOp): + if isinstance(node.right, php.FunctionCall) or isinstance(node.right, php.MethodCall) or isinstance(node.right, + php.StaticMethodCall): + anlysis_function(node.right, back_node, vul_function, function_params, vul_lineno, file_path=file_path) + + if isinstance(node.right, php.Eval): + analysis_eval(node.right, vul_function, back_node, vul_lineno, function_params, file_path=file_path) + + if isinstance(node.right, php.Silence): + buffer_.append(node.right) + analysis(buffer_, vul_function, back_node, vul_lineno, file_path, function_params) + + elif isinstance(node, php.BinaryOp): + if isinstance(node.left, php.FunctionCall) or isinstance(node.right, php.MethodCall) or isinstance(node.right, php.StaticMethodCall): + anlysis_function(node.left, back_node, vul_function, function_params, vul_lineno, file_path=file_path) + + if isinstance(node.left, php.Eval): + analysis_eval(node.left, vul_function, back_node, vul_lineno, function_params, file_path=file_path) + + if isinstance(node.left, php.Silence): + buffer_.append(node.left) + analysis(buffer_, vul_function, back_node, vul_lineno, file_path, function_params) + + if isinstance(node.right, php.FunctionCall) or isinstance(node.right, php.MethodCall) or isinstance(node.right, php.StaticMethodCall): + anlysis_function(node.right, back_node, vul_function, function_params, vul_lineno, file_path=file_path) + + if isinstance(node.right, php.Eval): + analysis_eval(node.right, vul_function, back_node, vul_lineno, function_params, file_path=file_path) + + if isinstance(node.right, php.Silence): + buffer_.append(node.right) + analysis(buffer_, vul_function, back_node, vul_lineno, file_path, function_params) + elif isinstance(node, php.Eval): analysis_eval(node, vul_function, back_node, vul_lineno, function_params, file_path=file_path) @@ -2039,7 +2152,7 @@ def analysis(nodes, vul_function, back_node, vul_lineno, file_path=None, functio elif isinstance(node, php.If): # 函数调用在if-else语句中时 analysis_if_else(node, back_node, vul_function, vul_lineno, function_params, file_path=file_path) - elif isinstance(node, php.While) or isinstance(node, php.DoWhile) or isinstance(node, php.For): # 函数调用在循环中 + elif isinstance(node, php.While) or isinstance(node, php.DoWhile) or isinstance(node, php.For) or isinstance(node, php.Foreach): # 函数调用在循环中 if isinstance(node.node, php.Block): analysis(node.node.nodes, vul_function, back_node, vul_lineno, file_path, function_params) @@ -2056,7 +2169,7 @@ def analysis(nodes, vul_function, back_node, vul_lineno, file_path=None, functio analysis(node.nodes, vul_function, function_body, vul_lineno, function_params=function_params, file_path=file_path) - elif isinstance(node, php.Class): + elif isinstance(node, php.Class) or isinstance(node, php.Trait): analysis(node.nodes, vul_function, back_node, vul_lineno, file_path, function_params) elif node_typename in FUNCTIONCALL_LIST: diff --git a/core/engine.py b/core/engine.py index fa2eaaad..8640826b 100644 --- a/core/engine.py +++ b/core/engine.py @@ -893,25 +893,32 @@ def scan(self): controlled_params=self.controlled_list, svid=self.cvi) logger.debug('[AST] [RET] {c}'.format(c=result)) if len(result) > 0: - if result[0]['code'] == 1: # 函数参数可控 - return True, 'Function-param-controllable', result[0]['chain'] + result_code_list = [] - elif result[0]['code'] == 2: # 漏洞修复 - return False, 'Function-param-controllable but fixed', result[0]['chain'] + for r in result: + result_code_list.append(r['code']) - elif result[0]['code'] == 3: # 疑似漏洞 - if self.is_unconfirm: - return True, 'Unconfirmed Function-param-controllable', result[0]['chain'] - else: - return False, 'Unconfirmed Function-param-controllable', result[0]['chain'] + if r['code'] == 1: # 函数参数可控 + return True, 'Function-param-controllable', r['chain'] - elif result[0]['code'] == -1: # 函数参数不可控 - return False, 'Function-param-uncon', result[0]['chain'] + for r in result: + if r['code'] == 4: # 新规则生成 + return False, 'New Core', r['source'] - elif result[0]['code'] == 4: # 新规则生成 - return False, 'New Core', result[0]['source'] + for r in result: + if r['code'] == 3: # 疑似漏洞 + if self.is_unconfirm: + return True, 'Unconfirmed Function-param-controllable', r['chain'] + else: + return False, 'Unconfirmed Function-param-controllable', r['chain'] - logger.debug('[AST] [CODE] {code}'.format(code=result[0]['code'])) + elif r['code'] == 2: # 漏洞修复 + return False, 'Function-param-controllable but fixed', r['chain'] + + else: # 函数参数不可控 + return False, 'Function-param-uncon', r['chain'] + + logger.debug('[AST] [CODE] {code}'.format(code=result_code_list)) else: logger.debug( '[AST] Parser failed / vulnerability parameter is not controllable {r}'.format( diff --git a/core/vendors.py b/core/vendors.py index da7ccb50..06215b68 100644 --- a/core/vendors.py +++ b/core/vendors.py @@ -139,7 +139,7 @@ def check_and_save_result(task_id, language, vendor_name, vendor_version): cvi_id=VENDOR_CVIID, language=language, vulfile_path="VendorVul:{}".format(vv.id), - source_code="{}".format(vv.reference), + source_code="{}".format(vv.reference[:180]), result_type=vendor_source_match, is_unconfirm=False, is_active=True @@ -150,7 +150,7 @@ def check_and_save_result(task_id, language, vendor_name, vendor_version): if sr: node_source = vv.description rf = ResultFlow(vul_id=sr.id, node_type='sca_scan', - node_content=vv.title, node_path=vv.reference, + node_content=vv.title, node_path=vv.reference[:280], node_source=node_source, node_lineno=0) rf.save() @@ -169,7 +169,7 @@ def get_and_save_vendor_vuls(task_id, vendor_name, vendor_version, language, ext if not settings.WITH_VENDOR and task_id: return False - logger.info("[Vendor Vuls] Spider {} Vendor {} Vul.".format(language, vendor_name)) + logger.info("[Vendor Vuls] Spider {} Vendor {} Version {} Vul.".format(language, vendor_name, vendor_version)) _vendor = {"name": vendor_name, "version": vendor_version} for vuln in get_vulns(language, _vendor["name"], _vendor["version"]): diff --git a/core/vuln_apis/depsdev.py b/core/vuln_apis/depsdev.py index fcaf3a8a..0a8c93a5 100644 --- a/core/vuln_apis/depsdev.py +++ b/core/vuln_apis/depsdev.py @@ -59,4 +59,4 @@ def __get_affected_versions(package_name, source, source_id): if len(pkg["versionsAffected"]) > 0: for version in pkg["versionsAffected"]: result.append(version["version"]) - return result \ No newline at end of file + return result diff --git a/core/vuln_apis/murphysec.py b/core/vuln_apis/murphysec.py new file mode 100644 index 00000000..9166c524 --- /dev/null +++ b/core/vuln_apis/murphysec.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +# encoding: utf-8 +''' +@author: LoRexxar +@contact: lorexxar@gmail.com +@file: mofei.py +@time: 2021/9/27 11:47 +@desc: + +''' + +import json +import requests + +from Kunlun_M.settings import MURPHYSEC_TOKEN +from utils.log import logger + +__MURPHYSECAPI = "https://api.murphysec.com/cert/v1/check" +__MURPHYSECVULAPI = "https://api.murphysec.com/cert/v1/latest" + + +def get_vulns_from_murphysec(language, package_name, version): + datas = { + "comp_name": package_name, + "version": version, + "language": language, + "filter":{ + "level": "严重|高危|中危" + } + } + + headers = { + "Authorization": "Bearer {}".format(MURPHYSEC_TOKEN), + "Content-Type": "application/json" + } + result = [] + + r = requests.post(url=__MURPHYSECAPI, headers=headers, data=json.dumps(datas)) + + if r.status_code == 200: + data = json.loads(r.content) + + if data['code'] == 400: + logger.warning("[Vendor][Murphysec Scan] QPS limit.") + return result + + elif data['code'] == 401: + logger.error("[Vendor][Murphysec Scan] Api Token error.") + + else: + vuls = data['data'] + + for vul in vuls: + vuln = {} + vuln["vuln_id"] = vul['no'] + vuln["title"] = vul['title'] + # reference + urls = [] + for u in vul['references']: + urls.append(u["url"]) + + vuln["reference"] = json.dumps(urls) + vuln["description"] = vul['description'] + # get cve + cves = [vul['cve_id'], vul['cnvd_id']] + vuln["cves"] = json.dumps(cves) + # get severity + vuln["severity"] = int(vul['cvss']) + + # affected_versions + # affected_versions = [] + # for av in vul['effect']: + # affected_versions.append(av['version_end_excluding']) + + vuln["affected_versions"] = [version] + + result.append(vuln) + + return result diff --git a/templates/dashboard/projects/project_detail.html b/templates/dashboard/projects/project_detail.html index f9a3c3b6..0404e879 100644 --- a/templates/dashboard/projects/project_detail.html +++ b/templates/dashboard/projects/project_detail.html @@ -117,7 +117,11 @@