diff --git a/README.md b/README.md index 02695b64..c2374830 100644 --- a/README.md +++ b/README.md @@ -5,20 +5,43 @@ [![license](https://img.shields.io/github/license/mashape/apistatus.svg?maxAge=2592000)](https://github.com/wufeifei/cobra/blob/master/LICENSE) 当前版本非正式版本,正式版本正在做最后的内测中,建议等正式版本出来后再使用,敬请期待! -[![asciicast](https://asciinema.org/a/132572.png)](https://asciinema.org/a/132572) +[![asciicast](https://raw.githubusercontent.com/wufeifei/cobra/master/docs/report_03.jpg)](https://asciinema.org/a/132572) ## Introduction(介绍) -Cobra是一款**源代码安全审计**工具,支持检测多种开发语言源代码中的数十种漏洞和风险点。 +Cobra是一款**源代码安全审计**工具,支持检测多种开发语言源代码中的大部分显著的安全问题和漏洞。 ## Features(特点) -#### [Multi-language support(支持多种开发语言)](https://github.com/wufeifei/cobra/blob/master/rules/languages.xml) +#### Multi-language support(支持多种开发语言) > 支持PHP、Java等开发语言,并支持数十种类型文件。 -#### [Supported Multi-Vulnerabilities(支持多种漏洞类型)](https://github.com/wufeifei/cobra/blob/master/rules/vulnerabilities.xml) +#### Supported Multi-Vulnerabilities(支持多种漏洞类型) > 首批开放数万条不安全的依赖检查规则和数十条代码安全扫描规则,后续将持续开放更多扫描规则。 -#### CLI、API(命令行模式和API模式) -> 提供本地Server服务,可支持本地API接口,方便和其它系统(发布系统、CI等)对接扩展 +#### GUI、CLI、API(命令行模式和API模式) +> 提供本地Web Server服务,可使用GUI可视化操作,也可支持本地API接口,方便和其它系统(发布系统、CI等)对接扩展。 + +## Snapshot(截图) +[![report01](https://raw.githubusercontent.com/wufeifei/cobra/master/docs/report_01.jpg)](https://wufeifei.github.io/cobra/api) +[![report02](https://raw.githubusercontent.com/wufeifei/cobra/master/docs/report_02.jpg)](https://wufeifei.github.io/cobra/api) ## Documents(文档) -- [Cobra文档](https://wufeifei.github.io/cobra/) \ No newline at end of file +- 安装 + - [Cobra安装](https://wufeifei.github.io/cobra/installation) +- 基础使用 + - [CLI模式使用方法](https://wufeifei.github.io/cobra/cli) + - [API模式使用方法](https://wufeifei.github.io/cobra/api) +- 进阶使用 + - [高级功能配置](https://wufeifei.github.io/cobra/config) + - [升级框架和规则源](https://wufeifei.github.io/cobra/upgrade) +- 规则开发规范 + - [规则模板](https://wufeifei.github.io/cobra/rule_template) + - [规则样例](https://wufeifei.github.io/cobra/rule_demo) + - [规则开发流程](https://wufeifei.github.io/cobra/rule_flow) +- 框架引擎 + - [开发语言和文件类型定义](https://wufeifei.github.io/cobra/languages) + - [漏洞类型定义](https://wufeifei.github.io/cobra/labels) + - [危害等级定义](https://wufeifei.github.io/cobra/level) + - [程序目录结构](https://wufeifei.github.io/cobra/tree) +- 贡献代码 + - [单元测试](https://wufeifei.github.io/cobra/test) + - [贡献者](https://wufeifei.github.io/cobra/contributors) \ No newline at end of file diff --git a/cobra/__init__.py b/cobra/__init__.py index 803f963f..8c34c662 100644 --- a/cobra/__init__.py +++ b/cobra/__init__.py @@ -16,10 +16,12 @@ import time import argparse import logging +import traceback from .log import logger from . import cli, api, config from .cli import get_sid from .engine import Running +from .utils import unhandled_exception_message, create_github_issue from .__version__ import __title__, __introduction__, __url__, __version__ from .__version__ import __author__, __author_email__, __license__ @@ -33,52 +35,58 @@ def main(): - # arg parse - t1 = time.clock() - parser = argparse.ArgumentParser(prog=__title__, description=__introduction__, epilog=__epilog__, formatter_class=argparse.RawDescriptionHelpFormatter) - - parser_group_scan = parser.add_argument_group('Scan') - 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='json', 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, HTTP API URL, MAIL') - parser_group_scan.add_argument('-r', '--rule', dest='special_rules', action='store', default=None, metavar='', help='specifies rules e.g: CVI-100001,cvi-190001') - parser_group_scan.add_argument('-d', '--debug', dest='debug', action='store_true', default=False, help='open debug mode') - parser_group_scan.add_argument('-sid', '--sid', dest='sid', action='store', default=None, help='scan id(API)') - - parser_group_server = parser.add_argument_group('RESTful') - parser_group_server.add_argument('-H', '--host', dest='host', action='store', default=None, metavar='', help='REST-JSON API Service Host') - parser_group_server.add_argument('-P', '--port', dest='port', action='store', default=None, metavar='', help='REST-JSON API Service Port') - - args = parser.parse_args() - - if args.debug: - logger.setLevel(logging.DEBUG) - logger.debug('[INIT] set logging level: debug') - - if args.host is None and args.port is None and args.target is '' and args.output is '': - parser.print_help() - exit() - - if args.host is not None and args.port is not None: - logger.debug('[INIT] start RESTful Server...') - api.start(args.host, args.port, args.debug) - else: - logger.debug('[INIT] start scanning...') - - # Native CLI mode - if args.sid is None: - a_sid = get_sid(args.target, True) - data = { - 'status': 'running', - 'report': '' - } - Running(a_sid).status(data) + try: + # arg parse + t1 = time.clock() + parser = argparse.ArgumentParser(prog=__title__, description=__introduction__, epilog=__epilog__, formatter_class=argparse.RawDescriptionHelpFormatter) + + parser_group_scan = parser.add_argument_group('Scan') + 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='json', 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, HTTP API URL, MAIL') + parser_group_scan.add_argument('-r', '--rule', dest='special_rules', action='store', default=None, metavar='', help='specifies rules e.g: CVI-100001,cvi-190001') + parser_group_scan.add_argument('-d', '--debug', dest='debug', action='store_true', default=False, help='open debug mode') + parser_group_scan.add_argument('-sid', '--sid', dest='sid', action='store', default=None, help='scan id(API)') + + parser_group_server = parser.add_argument_group('RESTful') + parser_group_server.add_argument('-H', '--host', dest='host', action='store', default=None, metavar='', help='REST-JSON API Service Host') + parser_group_server.add_argument('-P', '--port', dest='port', action='store', default=None, metavar='', help='REST-JSON API Service Port') + + args = parser.parse_args() + + if args.debug: + logger.setLevel(logging.DEBUG) + logger.debug('[INIT] set logging level: debug') + + if args.host is None and args.port is None and args.target is '' and args.output is '': + parser.print_help() + exit() + + if args.host is not None and args.port is not None: + logger.debug('[INIT] start RESTful Server...') + api.start(args.host, args.port, args.debug) else: - # API call CLI mode - a_sid = args.sid - cli.start(args.target, args.format, args.output, args.special_rules, a_sid) - t2 = time.clock() - logger.info('[INIT] Done! Consume Time:{ct}s'.format(ct=t2 - t1)) + logger.debug('[INIT] start scanning...') + + # Native CLI mode + if args.sid is None: + a_sid = get_sid(args.target, True) + data = { + 'status': 'running', + 'report': '' + } + Running(a_sid).status(data) + else: + # API call CLI mode + a_sid = args.sid + cli.start(args.target, args.format, args.output, args.special_rules, a_sid) + t2 = time.clock() + logger.info('[INIT] Done! Consume Time:{ct}s'.format(ct=t2 - t1)) + except Exception as e: + err_msg = unhandled_exception_message() + exc_msg = traceback.format_exc() + logger.warning(exc_msg) + create_github_issue(err_msg, exc_msg) if __name__ == '__main__': diff --git a/cobra/__version__.py b/cobra/__version__.py index 4ea84f02..c8f90d20 100644 --- a/cobra/__version__.py +++ b/cobra/__version__.py @@ -1,7 +1,13 @@ +import sys +import platform + __title__ = 'cobra' __description__ = 'Code Security Audit' __url__ = 'https://github.com/wufeifei/cobra' -__version__ = '2.0.0' +__issue_page__ = 'https://github.com/wufeifei/cobra/issues/new' +__python_version__ = sys.version.split()[0] +__platform__ = platform.platform() +__version__ = '2.0.0-beta.4' __author__ = 'Feei' __author_email__ = 'feei@feei.cn' __license__ = 'MIT License' @@ -21,5 +27,5 @@ {m} -t {td} -f json -o /tmp/report.json {m} -t {tg} -f json -o feei@feei.cn {m} -t {tg} -f json -o http://push.to.com/api - {m} -H 127.0.0.1 -P 80 -""".format(m='./cobra.py', td='tests/vulnerabilities', tg='https://github.com/wufeifei/vc.git') + sudo {m} -H 127.0.0.1 -P 80 +""".format(m='./cobra.py', td='tests/vulnerabilities', tg='https://github.com/ethicalhack3r/DVWA') diff --git a/cobra/api.py b/cobra/api.py index 0aecbb96..2c93f9db 100644 --- a/cobra/api.py +++ b/cobra/api.py @@ -11,26 +11,27 @@ :license: MIT, see LICENSE for more details. :copyright: Copyright (c) 2017 Feei. All rights reserved """ -import socket import errno -import time -import os -import re import json -import requests import multiprocessing +import os +import re +import socket import subprocess import threading +import time import traceback + +import requests from flask import Flask, request, render_template from flask_restful import Api, Resource -from werkzeug.utils import secure_filename + from . import cli from .cli import get_sid +from .config import Config, running_path, code_path, package_path from .engine import Running from .log import logger -from .config import Config, running_path, code_path, package_path -from .utils import allowed_file +from .utils import allowed_file, secure_filename, PY2 try: # Python 3 @@ -88,7 +89,7 @@ def post(): running = Running(a_sid) # Write a_sid running data - running.init_list() + running.init_list(data=target) # Write a_sid running status data = { @@ -104,15 +105,17 @@ def post(): producer(task=arg) result = { - "msg": "Add scan job successfully.", - "sid": a_sid, + 'msg': 'Add scan job successfully.', + 'sid': a_sid, + 'total_target_num': len(target), } else: arg = (target, formatter, output, rule, a_sid) producer(task=arg) result = { - "msg": "Add scan job successfully.", - "sid": a_sid, + 'msg': 'Add scan job successfully.', + 'sid': a_sid, + 'total_target_num': 1, } return {"code": 1001, "result": result} @@ -147,8 +150,8 @@ def post(): return data else: result = running.status() + r_data = running.list() if result['status'] == 'running': - r_data = running.list() ret = True result['still_running'] = dict() for s_sid, git in r_data['sids'].items(): @@ -163,7 +166,10 @@ def post(): 'sid': sid, 'status': result.get('status'), 'report': result.get('report'), - 'still_running': result.get('still_running') + 'still_running': result.get('still_running'), + 'total_target_num': r_data.get('total_target_num'), + 'not_finished': int(r_data.get('total_target_num')) - len(r_data.get('sids')) + + len(result.get('still_running')), } return {"code": 1001, "result": data} @@ -258,20 +264,29 @@ def post(): file_path = data.get('file_path') if target.startswith('http'): - target = re.findall(r'(.*?\.git)', target)[0] repo_user = target.split('/')[-2] - repo_name = target.split('/')[-1].replace('.git', '') - # repo_directory = os.path.join(os.path.join(os.path.join(code_path, 'git'), repo_user), repo_name) + + split_target = target.split(':') + if len(split_target) == 3: + repo_name = split_target[1].split('/')[-1].replace('.git', '') + elif len(split_target) == 2: + repo_name = split_target[1].split('/')[-1].replace('.git', '') + else: + return {'code': 1002, 'msg': 'Invalid target.'} repo_directory = os.path.join(code_path, 'git', repo_user, repo_name) - file_path = map(secure_filename, file_path.split('/')) + if PY2: + file_path = map(secure_filename, [path.decode('utf-8') for path in file_path.split('/')]) + else: + file_path = map(secure_filename, [path for path in file_path.split('/')]) + # 循环生成路径,避免文件越级读取 file_name = repo_directory for _dir in file_path: file_name = os.path.join(file_name, _dir) - print(file_name) if os.path.exists(file_name): + extension = guess_type(file_name) if is_text(file_name): with open(file_name, 'r') as f: file_content = f.read() @@ -280,7 +295,8 @@ def post(): else: return {'code': 1002, 'msg': 'No such file.'} - return {'code': 1001, 'result': file_content} + return {'code': 1001, 'result': {'file_content': file_content, + 'extension': extension}} @app.route('/', methods=['GET', 'POST']) @@ -328,11 +344,24 @@ def summary(): total_vul_number, critical_vul_number, high_vul_number, medium_vul_number, low_vul_number = 0, 0, 0, 0, 0 rule_filter = dict() targets = list() - for s_sid in scan_list.keys(): + + for s_sid, target_str in scan_list.items(): target_info = dict() + + # 分割项目地址与分支,默认 master + split_target = target_str.split(':') + if len(split_target) == 3: + target, branch = '{p}:{u}'.format(p=split_target[0], u=split_target[1]), split_target[-1] + elif len(split_target) == 2: + target, branch = target_str, 'master' + else: + logger.critical('Target url exception: {u}'.format(u=target_str)) + target, branch = target_str, 'master' + target_info.update({ 'sid': s_sid, - 'target': scan_list.get(s_sid), + 'target': target, + 'branch': branch, }) s_sid_file = os.path.join(running_path, '{sid}_data'.format(sid=s_sid)) with open(s_sid_file, 'r') as f: @@ -395,6 +424,26 @@ def is_text(fn): return 'text' in msg.decode('utf-8') +def guess_type(fn): + import mimetypes + extension = mimetypes.guess_type(fn)[0] + if extension: + """text/x-python or text/x-java-source""" + # extension = extension.split('/')[1] + extension = extension.replace('-source', '') + else: + extension = fn.split('/')[-1].split('.')[-1] + + custom_ext = { + 'html': 'htmlmixed', + 'md': 'markdown', + } + if custom_ext.get(extension) is not None: + extension = custom_ext.get(extension) + + return extension.lower() + + def start(host, port, debug): logger.info('Start {host}:{port}'.format(host=host, port=port)) api = Api(app) diff --git a/cobra/cast.py b/cobra/cast.py index 8f1281e6..13897195 100644 --- a/cobra/cast.py +++ b/cobra/cast.py @@ -283,7 +283,7 @@ def is_controllable_param(self): return False, self.data logger.debug("[AST] Is assign out data: `No`") return True, self.data - logger.critical("[AST] Not Java/PHP, can't parse") + logger.debug("[AST] Not Java/PHP, can't parse ({l})".format(l=self.language)) return False, self.data else: logger.warning("[AST] Can't get `param`, check built-in rule") diff --git a/cobra/cli.py b/cobra/cli.py index a8fbedb6..03d3c5d4 100644 --- a/cobra/cli.py +++ b/cobra/cli.py @@ -47,6 +47,7 @@ def start(target, formatter, output, special_rules, a_sid=None): s_sid = get_sid(target) r = Running(a_sid) data = (s_sid, target) + r.init_list(data=target) r.list(data) report = '?sid={a_sid}'.format(a_sid=a_sid) @@ -81,17 +82,17 @@ def start(target, formatter, output, special_rules, a_sid=None): # scan scan(target_directory=target_directory, a_sid=a_sid, s_sid=s_sid, special_rules=pa.special_rules, language=main_language, framework=main_framework, file_count=file_count, extension_count=len(files)) - except PickupException as e: + except PickupException: result = { 'code': 1002, 'msg': 'Repository not exist!' } Running(s_sid).data(result) - return - except Exception as e: + raise + except Exception: result = { 'code': 1002, 'msg': 'Exception' } Running(s_sid).data(result) - return + raise diff --git a/cobra/config.py b/cobra/config.py index 3b9f08d1..2eb44474 100644 --- a/cobra/config.py +++ b/cobra/config.py @@ -33,6 +33,10 @@ source_path = os.path.join(code_path, 'git') if os.path.isdir(source_path) is not True: os.mkdir(source_path) +issue_path = os.path.join(code_path, 'issue') +if os.path.isdir(issue_path) is not True: + os.mkdir(issue_path) +issue_history_path = os.path.join(issue_path, 'history') cobra_main = os.path.join(project_directory, 'cobra.py') core_path = os.path.join(project_directory, 'cobra') tests_path = os.path.join(project_directory, 'tests') diff --git a/cobra/engine.py b/cobra/engine.py index d36bc9d6..b4c16c32 100644 --- a/cobra/engine.py +++ b/cobra/engine.py @@ -25,6 +25,7 @@ from .config import running_path from .result import VulnerabilityResult from .cast import CAST +from .parser import scan_parser from prettytable import PrettyTable @@ -32,15 +33,35 @@ class Running: def __init__(self, sid): self.sid = sid - def init_list(self): + def init_list(self, data=None): + """ + Initialize asid_list file. + :param data: list or a string + :return: + """ file_path = os.path.join(running_path, '{sid}_list'.format(sid=self.sid)) - with open(file_path, 'w') as f: - fcntl.flock(f, fcntl.LOCK_EX) - f.write(json.dumps({ - 'sids': {} - })) + if not os.path.exists(file_path): + if isinstance(data, list): + with open(file_path, 'w') as f: + fcntl.flock(f, fcntl.LOCK_EX) + f.write(json.dumps({ + 'sids': {}, + 'total_target_num': len(data), + })) + else: + with open(file_path, 'w') as f: + fcntl.flock(f, fcntl.LOCK_EX) + f.write(json.dumps({ + 'sids': {}, + 'total_target_num': 1, + })) def list(self, data=None): + """ + Update asid_list file. + :param data: tuple (s_sid, target) + :return: + """ file_path = os.path.join(running_path, '{sid}_list'.format(sid=self.sid)) if data is None: with open(file_path, 'r') as f: @@ -48,7 +69,7 @@ def list(self, data=None): result = f.readline() return json.loads(result) else: - with open(file_path, 'w+') as f: + with open(file_path, 'r+') as f: # w+ causes a file reading bug fcntl.flock(f, fcntl.LOCK_EX) result = f.read() if result == '': @@ -116,8 +137,8 @@ def score2level(score): def scan_single(target_directory, single_rule): try: return SingleRule(target_directory, single_rule).process() - except Exception as e: - traceback.print_exc() + except Exception: + raise def scan(target_directory, a_sid=None, s_sid=None, special_rules=None, language=None, framework=None, file_count=0, @@ -137,32 +158,37 @@ def store(result): else: logger.debug('[SCAN] [STORE] Not found vulnerabilities on this rule!') - pool = multiprocessing.Pool() - if len(rules) == 0: - logger.critical('no rules!') - return False - logger.info('[PUSH] {rc} Rules'.format(rc=len(rules))) - for idx, single_rule in enumerate(rules): - if single_rule['status'] is False: - logger.info('[CVI-{cvi}] [STATUS] OFF, CONTINUE...'.format(cvi=single_rule['id'])) - continue - # SR(Single Rule) - logger.debug("""[PUSH] [CVI-{cvi}] {idx}.{name}({language})""".format( - cvi=single_rule['id'], - idx=idx, - name=single_rule['name'], - language=single_rule['language'] - )) - if single_rule['language'] in languages: - single_rule['extensions'] = languages[single_rule['language']]['extensions'] - pool.apply_async(scan_single, args=(target_directory, single_rule), callback=store) - else: - logger.critical('unset language, continue...') - continue - pool.close() - pool.join() + try: + pool = multiprocessing.Pool() + if len(rules) == 0: + logger.critical('no rules!') + return False + logger.info('[PUSH] {rc} Rules'.format(rc=len(rules))) + push_rules = [] + for idx, single_rule in enumerate(rules): + if single_rule['status'] is False: + logger.info('[CVI-{cvi}] [STATUS] OFF, CONTINUE...'.format(cvi=single_rule['id'])) + continue + # SR(Single Rule) + logger.debug("""[PUSH] [CVI-{cvi}] {idx}.{name}({language})""".format( + cvi=single_rule['id'], + idx=idx, + name=single_rule['name'], + language=single_rule['language'] + )) + if single_rule['language'] in languages: + single_rule['extensions'] = languages[single_rule['language']]['extensions'] + push_rules.append(single_rule['id']) + pool.apply_async(scan_single, args=(target_directory, single_rule), callback=store) + else: + logger.critical('unset language, continue...') + continue + pool.close() + pool.join() + except Exception: + raise - # print + # print data = [] table = PrettyTable(['#', 'CVI', 'VUL', 'Rule(ID/Name)', 'Lang', 'Level-Score', 'Target-File:Line-Number', 'Commit(Author/Time)', 'Source Code Content']) table.align = 'l' @@ -186,11 +212,14 @@ def store(result): if x.id not in trigger_rules: logger.debug(' > trigger rule (CVI-{cvi})'.format(cvi=x.id)) trigger_rules.append(x.id) + diff_rules = list(set(push_rules) - set(trigger_rules)) vn = len(find_vulnerabilities) if vn == 0: logger.info('[SCAN] Not found vulnerability!') else: logger.info("[SCAN] Trigger Rules: {tr} Vulnerabilities ({vn})\r\n{table}".format(tr=len(trigger_rules), vn=len(find_vulnerabilities), table=table)) + if len(diff_rules) > 0: + logger.info('[SCAN] Not Trigger Rules ({l}): {r}'.format(l=len(diff_rules), r=','.join(diff_rules))) # completed running data if s_sid is not None: @@ -245,6 +274,8 @@ def origin_results(self): match = const.fpc_multi.replace('[f]', self.sr['match']) else: match = const.fpc_single.replace('[f]', self.sr['match']) + else: + logger.warning('Exception match mode: {m}'.format(m=self.sr['match-mode'])) filters = [] for e in self.sr['extensions']: @@ -303,8 +334,8 @@ def process(self): self.rule_vulnerabilities.append(vulnerability) else: logger.debug('Not vulnerability: {code}'.format(code=status_code)) - except Exception as e: - traceback.print_exc() + except Exception: + raise logger.debug('[CVI-{cvi}] {vn} Vulnerabilities: {count}'.format(cvi=self.sr['id'], vn=self.sr['name'], count=len(self.rule_vulnerabilities))) return self.rule_vulnerabilities @@ -321,7 +352,7 @@ def parse_match(self, single_match): try: mr.line_number, mr.code_content = re.findall(r':(\d+):(.*)', single_match)[0] mr.file_path = single_match.split(u':{n}:'.format(n=mr.line_number))[0] - except Exception as e: + except Exception: logger.warning('match line parse exception') mr.file_path = '' mr.code_content = '' @@ -558,10 +589,39 @@ def scan(self): # logger.debug('[CVI-{cvi}] match-mode {mm}'.format(cvi=self.cvi, mm=self.rule_match_mode)) found_vul = False - if self.is_can_parse(): + result = [] + if self.file_path[-3:].lower() == 'php': try: ast = CAST(self.rule_match, self.target_directory, self.file_path, self.line_number, self.code_content) # Match2 + if self.rule_match_mode == const.mm_function_param_controllable: + rule_match = self.rule_match.strip('()').split('|') + logger.debug('[RULE_MATCH] {r}'.format(r=rule_match)) + try: + with open(self.file_path, 'r') as fi: + code_contents = fi.read() + result = scan_parser(code_contents, rule_match, self.line_number) + logger.debug('[AST] [RET] {c}'.format(c=result)) + if len(result) > 0: + if result[0]['code'] == 1: # 函数参数可控 + return True, 1001 + + if result[0]['code'] == 2: # 函数为敏感函数 + return True, 1001 + + if result[0]['code'] == 0: # 漏洞修复 + return False, 1002 + + if result[0]['code'] == -1: # 函数参数不可控 + return False, 1002 + + logger.debug('[AST] [CODE] {code}'.format(code=result[0]['code'])) + else: + logger.warning('[AST] Parser failed / vulnerability parameter is not controllable {r}'.format(r=result)) + except Exception as e: + logger.warning(traceback.format_exc()) + raise + if self.rule_match2 is not None: is_match, data = ast.match(self.rule_match2, self.rule_match2_block) if is_match: @@ -587,7 +647,7 @@ def scan(self): logger.debug('[CVI-{cvi}] [PARAM-CONTROLLABLE] Param Not Controllable'.format(cvi=self.cvi)) return False, 4002 except Exception as e: - traceback.print_exc() + logger.debug(traceback.format_exc()) return False, 4004 if found_vul: diff --git a/cobra/parser.py b/cobra/parser.py index 228c5465..3ef65e23 100644 --- a/cobra/parser.py +++ b/cobra/parser.py @@ -14,9 +14,10 @@ from phply.phplex import lexer # 词法分析 from phply.phpparse import make_parser # 语法分析 from phply import phpast as php -from log import logger +from .log import logger with_line = True +scan_results = [] # 结果存放列表初始化 def export(items): @@ -47,23 +48,76 @@ def export_list(params, export_params): def get_all_params(nodes): # 用来获取调用函数的参数列表,nodes为参数列表 + """ + 获取函数结构的所有参数 + :param nodes: + :return: + """ params = [] export_params = [] # 定义空列表,用来给export_list中使用 for node in nodes: - if isinstance(node.node, php.Variable): - params.append(node.node.name) + if isinstance(node.node, php.FunctionCall): # 函数参数来自另一个函数的返回值 + params = get_all_params(node.node.params) + + else: + if isinstance(node.node, php.Variable): + params.append(node.node.name) + + if isinstance(node.node, php.BinaryOp): + params = get_binaryop_params(node.node) + params = export_list(params, export_params) + + if isinstance(node.node, php.ArrayOffset): + param = get_node_name(node.node.node) + params.append(param) - if isinstance(node.node, php.BinaryOp): - params = get_binaryop_params(node.node) - params = export_list(params, export_params) + if isinstance(node.node, php.Cast): + param = get_cast_params(node.node.expr) + params.append(param) - if isinstance(node.node, php.ArrayOffset): - params = get_node_name(node.node.node) + if isinstance(node.node, php.Silence): + param = get_silence_params(node.node) + params.append(param) return params +def get_silence_params(node): + """ + 用来提取Silence类型中的参数 + :param node: + :return: + """ + param = [] + if isinstance(node.expr, php.Variable): + param = get_node_name(node.expr) + + if isinstance(node.expr, php.FunctionCall): + param.append(node.expr) + + return param + + +def get_cast_params(node): + """ + 用来提取Cast类型中的参数 + :param node: + :return: + """ + param = [] + if isinstance(node, php.Silence): + param = get_node_name(node.expr) + + return param + + def get_binaryop_params(node): # 当为BinaryOp类型时,分别对left和right进行处理,取出需要的变量 + """ + 用来提取Binaryop中的参数 + :param node: + :return: + """ + logger.debug('[AST] Binaryop --> {node}'.format(node=node)) params = [] if isinstance(node.left, php.Variable) and isinstance(node.right, php.Variable): # left, right都为变量直接取值 params.append(node.left.name) @@ -99,6 +153,11 @@ def get_binaryop_deep_params(node, params): # 取出right,left不为变量时 def get_expr_name(node): # expr为'expr'中的值 + """ + 获取赋值表达式的表达式部分中的参数名-->返回用来进行回溯 + :param node: + :return: + """ param_lineno = 0 is_re = False if isinstance(node, php.ArrayOffset): # 当赋值表达式为数组 @@ -121,26 +180,15 @@ def get_expr_name(node): # expr为'expr'中的值 def get_node_name(node): # node为'node'中的元组 + """ + 获取Variable类型节点的name + :param node: + :return: + """ if isinstance(node, php.Variable): return node.name # 返回此节点中的变量名 -def get_functioncall_params(node): # functioncall为'functioncall'中的字典{'lineno', 'name':函数名, 'params':参数列表} - all_params = [] - if 'params' in node: - params = node['params'] # params包含所有参数的一个列表 - for method, value in params: # param为每一个参数的元组 - if method == 'Parameter': - param = get_node_name(value['node']) - all_params.append(param) - return all_params - - -def get_assignment_params(node): # assignment为'Assignment'中的字典{'expr':表达式为赋值来源, 'is_ref', 'lineno', 'node'} - if 'expr' in node: - pass - - def is_repair(expr): """ 判断赋值表达式是否出现过滤函数,如果已经过滤,停止污点回溯,判定漏洞已修复 @@ -153,6 +201,23 @@ def is_repair(expr): return is_re +def is_sink_function(param_expr, function_params): + """ + 判断自定义函数的入参-->判断此函数是否是危险函数 + :param param_expr: + :param function_params: + :return: + """ + is_co = -1 + cp = None + for function_param in function_params: + if param_expr == function_param: + is_co = 2 + cp = function_param + logger.debug('[AST] is_sink_function --> {function_param}'.format(function_param=cp)) + return is_co, cp + + def is_controllable(expr): # 获取表达式中的变量,看是否在用户可控变量列表中 """ 判断赋值表达式是否是用户可控的 @@ -174,49 +239,78 @@ def is_controllable(expr): # 获取表达式中的变量,看是否在用户 '$HTTP_GET_VARS' ] if expr in controlled_params: - return True, expr - return False, None + logger.debug('[AST] is_controllable --> {expr}'.format(expr=expr)) + return 1, expr + return -1, None -def parameters_back(param, nodes): # 用来得到回溯过程中的被赋值的变量是否与敏感函数变量相等,param是当前需要跟踪的污点 +def parameters_back(param, nodes, function_params=None, flag=0): # 用来得到回溯过程中的被赋值的变量是否与敏感函数变量相等,param是当前需要跟踪的污点 """ 递归回溯敏感函数的赋值流程,param为跟踪的污点,当找到param来源时-->分析复制表达式-->获取新污点;否则递归下一个节点 :param param: :param nodes: + :param function_params: + :param flag: :return: """ - is_co = False + is_co = -1 cp = None + expr_lineno = 0 + if len(nodes) != 0: node = nodes[len(nodes) - 1] + if isinstance(node, php.Assignment): # 回溯的过程中,对出现赋值情况的节点进行跟踪 param_node = get_node_name(node.node) # param_node为被赋值的变量 param_expr, expr_lineno, is_re = get_expr_name(node.expr) # param_expr为赋值表达式,param_expr为变量或者列表 + if param == param_node and is_re is True: - return is_co, cp + is_co = 0 + cp = None + return is_co, cp, expr_lineno if param == param_node and not isinstance(param_expr, list): # 找到变量的来源,开始继续分析变量的赋值表达式是否可控 - is_co, cp = is_controllable(param_expr) # 开始判断变量是否可控 + if flag == 0: + is_co, cp = is_controllable(param_expr) # 开始判断变量是否可控 + + elif flag == 1: + is_co, cp = is_sink_function(param_expr, function_params) + param = param_expr # 每次找到一个污点的来源时,开始跟踪新污点,覆盖旧污点 - if is_co is True: - logger.warning("[USAGE-CONTROLLABLE] {cp} in line {expr_lineno}".format(cp=cp, - expr_lineno=expr_lineno)) if param == param_node and isinstance(param_expr, list): for expr in param_expr: param = expr - _is_co, _cp = parameters_back(param, nodes[:-1]) # 分别对函数参数递归,获取其返回值,返回值为每一个参数的检测情况 - if _is_co is True: # 当参数可控时,值赋给is_co 和 cp,有一个参数可控,则认定这个函数可能可控 + is_co, cp = is_controllable(expr) + + if is_co == 1: + return is_co, cp, expr_lineno + + _is_co, _cp, expr_lineno = parameters_back(param, nodes[:-1], function_params, flag) + + if _is_co != -1: # 当参数可控时,值赋给is_co 和 cp,有一个参数可控,则认定这个函数可能可控 is_co = _is_co cp = _cp - if is_co is False: # 当is_co为True时找到可控,停止递归 - is_co, cp = parameters_back(param, nodes[:-1]) # 找到可控的输入时,停止递归 + if is_co == -1: # 当is_co为True时找到可控,停止递归 + is_co, cp, expr_lineno = parameters_back(param, nodes[:-1], function_params, flag) # 找到可控的输入时,停止递归 - return is_co, cp + elif len(nodes) == 0: + if flag == 1: + for function_param in function_params: + if function_param == param: + is_co = 2 + cp = function_param + + return is_co, cp, expr_lineno def get_function_params(nodes): + """ + 获取用户自定义函数的所有入参 + :param nodes: 自定义函数的参数部分 + :return: 以列表的形式返回所有的入参 + """ params = [] for node in nodes: @@ -226,135 +320,331 @@ def get_function_params(nodes): return params -def anlysis_function(node): +def anlysis_function(node, back_node, vul_function, function_params, vul_lineno): """ 对用户自定义的函数进行分析-->获取函数入参-->入参用经过赋值流程,进入sink函数-->此自定义函数为危险函数 :param node: + :param back_node: + :param vul_function: + :param function_params: + :param vul_lineno: :return: """ - function_params = get_function_params(node.params) - print function_params + flag = 1 + global scan_results + + try: + if node.name == vul_function and int(node.lineno) == int(vul_lineno): # 函数体中存在敏感函数,开始对敏感函数前的代码进行检测 + logger.debug('[AST] vul_function:{v}'.format(v=vul_function)) + logger.debug('{l}:{r}'.format(l=node.name, r=vul_function)) + params = get_all_params(node.params) + function_lineno = function_params[len(function_params) - 1] # 获取自定义函数的行号 + + for param in params: + is_co, cp, expr_lineno = parameters_back(param, back_node, function_params[:-1], flag) + expr_lineno = function_lineno # expr_lineno为自定义函数行号 + set_scan_results(is_co, cp, expr_lineno, node.name, param, node.lineno) + except Exception as e: + logger.debug(e) -def analysis_functioncall(node, back_node, vul_function): + +def analysis_functioncall(node, back_node, vul_function, vul_lineno): """ 调用FunctionCall-->判断调用Function是否敏感-->get params获取所有参数-->开始递归判断 :param node: :param back_node: :param vul_function: + :param vul_lineno :return: """ - if node.name == vul_function: # 定位到敏感函数 - params = get_all_params(node.params) # 开始取敏感函数中的参数列表 - for param in params: - is_co, cp = parameters_back(param, back_node) - if is_co is True: - logger.warning("[line] The parameters '{param}' from function '{function}' are controllable " - "in line {line}".format(param=param, function=node.name, line=node.lineno)) + global scan_results + try: + if node.name == vul_function and int(node.lineno) == int(vul_lineno): # 定位到敏感函数 + logger.debug('[VUL:VUL] {l}:{r}'.format(l=node.name, r=vul_function)) + logger.debug('[LINENO] {l}:{r}'.format(l=node.lineno, r=vul_lineno)) + params = get_all_params(node.params) # 开始取敏感函数中的参数列表 + + for param in params: + is_co, cp = is_controllable(param) + expr_lineno = node.lineno + + if is_co == -1: + is_co, cp, expr_lineno = parameters_back(param, back_node) + + set_scan_results(is_co, cp, expr_lineno, node.name, param, node.lineno) + + except Exception as e: + logger.debug(e) + + +def analysis_binaryop_node(node, back_node, vul_function, vul_lineno): + """ + 处理BinaryOp类型节点-->取出参数-->回溯判断参数是否可控-->输出结果 + :param node: + :param back_node: + :param vul_function: + :param vul_lineno: + :return: + """ + logger.debug('[AST] vul_function:{v}'.format(v=vul_function)) + params = get_binaryop_params(node) + params = export_list(params, export_params=[]) + + for param in params: + is_co, cp, expr_lineno = parameters_back(param, back_node) + set_scan_results(is_co, cp, expr_lineno, vul_function, param, vul_lineno) - else: - logger.warning("[line] The function '{function}' --> '{param}' uncontrollable or vulnerability " - "is repaired".format(function=node.name, param=param)) +def analysis_arrayoffset_node(node, vul_function, vul_lineno): + """ + 处理ArrayOffset类型节点-->取出参数-->回溯判断参数是否可控-->输出结果 + :param node: + :param vul_function: + :param vul_lineno: + :return: + """ + logger.debug('[AST] vul_function:{v}'.format(v=vul_function)) + param = get_node_name(node.node) + expr_lineno = node.lineno + is_co, cp = is_controllable(param) + + set_scan_results(is_co, cp, expr_lineno, vul_function, param, vul_lineno) + + +def analysis_functioncall_node(node, back_node, vul_function, vul_lineno): + """ + 处理FunctionCall类型节点-->取出参数-->回溯判断参数是否可控-->输出结果 + :param node: + :param back_node: + :param vul_function: + :param vul_lineno: + :return: + """ + logger.debug('[AST] vul_function:{v}'.format(v=vul_function)) + params = get_all_params(node.params) + for param in params: + is_co, cp, expr_lineno = parameters_back(param, back_node) + set_scan_results(is_co, cp, expr_lineno, vul_function, param, vul_lineno) + + +def analysis_variable_node(node, back_node, vul_function, vul_lineno): + """ + 处理Variable类型节点-->取出参数-->回溯判断参数是否可控-->输出结果 + :param node: + :param back_node: + :param vul_function: + :param vul_lineno: + :return: + """ + logger.debug('[AST] vul_function:{v}'.format(v=vul_function)) + params = get_node_name(node) + is_co, cp, expr_lineno = parameters_back(params, back_node) + set_scan_results(is_co, cp, expr_lineno, vul_function, params, vul_lineno) + + +def analysis_echo_print(node, back_node, vul_function, vul_lineno): + """ + 处理echo/print类型节点-->判断节点类型-->不同If分支回溯判断参数是否可控-->输出结果 + :param node: + :param back_node: + :param vul_function: + :param vul_lineno: + :return: + """ + global scan_results + + if int(vul_lineno) == int(node.lineno): + if isinstance(node, php.Print): + if isinstance(node.node, php.FunctionCall): + analysis_functioncall_node(node.node, back_node, vul_function, vul_lineno) + + if isinstance(node.node, php.Variable) and vul_function == 'print': # 直接输出变量信息 + analysis_variable_node(node.node, back_node, vul_function, vul_lineno) + + if isinstance(node.node, php.BinaryOp) and vul_function == 'print': + analysis_binaryop_node(node.node, back_node, vul_function, vul_lineno) + + if isinstance(node.node, php.ArrayOffset) and vul_function == 'print': + analysis_arrayoffset_node(node.node, vul_function, vul_lineno) + + elif isinstance(node, php.Echo): + for k_node in node.nodes: + if isinstance(k_node, php.FunctionCall): # 判断节点中是否有函数调用节点 + analysis_functioncall_node(k_node, back_node, vul_function, vul_lineno) # 将含有函数调用的节点进行分析 + + if isinstance(k_node, php.Variable) and vul_function == 'echo': + analysis_variable_node(k_node, back_node, vul_function, vul_lineno) + + if isinstance(k_node, php.BinaryOp) and vul_function == 'echo': + analysis_binaryop_node(k_node, back_node, vul_function, vul_lineno) + + if isinstance(k_node, php.ArrayOffset) and vul_function == 'echo': + analysis_arrayoffset_node(k_node, vul_function, vul_lineno) -def analysis(nodes, vul_function, back_node): + +def analysis_eval(node, vul_function, back_node, vul_lineno): + """ + 处理eval类型节点-->判断节点类型-->不同If分支回溯判断参数是否可控-->输出结果 + :param node: + :param vul_function: + :param back_node: + :param vul_lineno: + :return: + """ + global scan_results + + if vul_function == 'eval' and int(node.lineno) == int(vul_lineno): + if isinstance(node.expr, php.Variable): + analysis_variable_node(node.expr, back_node, vul_function, vul_lineno) + + if isinstance(node.expr, php.FunctionCall): + analysis_functioncall_node(node.expr, back_node, vul_function, vul_lineno) + + if isinstance(node.expr, php.BinaryOp): + analysis_binaryop_node(node.expr, back_node, vul_function, vul_lineno) + + if isinstance(node.expr, php.ArrayOffset): + analysis_arrayoffset_node(node.expr, vul_function, vul_lineno) + + +def analysis_file_inclusion(node, vul_function, back_node, vul_lineno): + """ + 处理include/require类型节点-->判断节点类型-->不同If分支回溯判断参数是否可控-->输出结果 + :param node: + :param vul_function: + :param back_node: + :param vul_lineno: + :return: + """ + global scan_results + include_fs = ['include', 'include_once', 'require', 'require_once'] + + if vul_function in include_fs and int(node.lineno) == int(vul_lineno): + logger.debug('[AST-INCLUDE] {l}-->{r}'.format(l=vul_function, r=vul_lineno)) + + if isinstance(node.expr, php.Variable): + analysis_variable_node(node.expr, back_node, vul_function, vul_lineno) + + if isinstance(node.expr, php.FunctionCall): + analysis_functioncall_node(node.expr, back_node, vul_function, vul_lineno) + + if isinstance(node.expr, php.BinaryOp): + analysis_binaryop_node(node.expr, back_node, vul_function, vul_lineno) + + if isinstance(node.expr, php.ArrayOffset): + analysis_arrayoffset_node(node.expr, vul_function, vul_lineno) + + +def set_scan_results(is_co, cp, expr_lineno, sink, param, vul_lineno): + """ + 获取结果信息-->输出结果 + :param is_co: + :param cp: + :param expr_lineno: + :param sink: + :param param: + :param vul_lineno: + :return: + """ + results = [] + global scan_results + + result = { + 'code': is_co, + 'source': cp, + 'source_lineno': expr_lineno, + 'sink': sink, + 'sink_param:': param, + 'sink_lineno': vul_lineno + } + results.append(result) + scan_results += results + + +def analysis(nodes, vul_function, back_node, vul_lineo, flag=0, function_params=None): """ 调用FunctionCall-->analysis_functioncall分析调用函数是否敏感 :param nodes: 所有节点 :param vul_function: 要判断的敏感函数名 :param back_node: 各种语法结构里面的语句 + :param vul_lineo: 漏洞函数所在行号 + :param flag: flag用来判断此时nodes来源是否是自定义函数体 + :param function_params: 自定义函数的所有参数列表 :return: """ for node in nodes: if isinstance(node, php.FunctionCall): # 函数直接调用,不进行赋值 - analysis_functioncall(node, back_node, vul_function) + if flag == 1: + anlysis_function(node, back_node, vul_function, function_params, vul_lineo) + + else: + analysis_functioncall(node, back_node, vul_function, vul_lineo) elif isinstance(node, php.Assignment): # 函数调用在赋值表达式中 if isinstance(node.expr, php.FunctionCall): - analysis_functioncall(node.expr, back_node, vul_function) + if flag == 1: + anlysis_function(node.expr, back_node, vul_function, function_params, vul_lineo) + + else: + analysis_functioncall(node.expr, back_node, vul_function, vul_lineo) + + elif isinstance(node, php.Print) or isinstance(node, php.Echo): + analysis_echo_print(node, back_node, vul_function, vul_lineo) + + elif isinstance(node, php.Silence): + nodes = get_silence_params(node) + analysis(nodes, vul_function, back_node, vul_lineo) + + elif isinstance(node, php.Eval): + analysis_eval(node, vul_function, back_node, vul_lineo) + + elif isinstance(node, php.Include) or isinstance(node, php.Require): + analysis_file_inclusion(node, vul_function, back_node, vul_lineo) elif isinstance(node, php.If): # 函数调用在if-else语句中时 if isinstance(node.node, php.Block): # if语句中的sink点以及变量 - analysis(node.node.nodes, vul_function, back_node) + analysis(node.node.nodes, vul_function, back_node, vul_lineo) if node.else_ is not None: # else语句中的sink点以及变量 if isinstance(node.else_.node, php.Block): - analysis(node.else_.node.nodes, vul_function, back_node) + analysis(node.else_.node.nodes, vul_function, back_node, vul_lineo) if len(node.elseifs) != 0: # elseif语句中的sink点以及变量 for i_node in node.elseifs: if i_node.node is not None: - analysis(i_node.node.nodes, vul_function, back_node) + analysis(i_node.node.nodes, vul_function, back_node, vul_lineo) elif isinstance(node, php.While) or isinstance(node, php.For): # 函数调用在循环中 if isinstance(node.node, php.Block): - analysis(node.node.nodes, vul_function, back_node) + analysis(node.node.nodes, vul_function, back_node, vul_lineo) elif isinstance(node, php.Function): - anlysis_function(node) - # analysis(node.nodes, vul_function, back_node) + function_body = [] + function_params = get_function_params(node.params) + function_params.append(node.lineno) # function_params为列表,放自定义函数参数和自定义函数行号 + analysis(node.nodes, vul_function, function_body, vul_lineo, flag=1, function_params=function_params) back_node.append(node) -def scan(code_content, sensitive_func): +def scan_parser(code_content, sensitive_func, vul_lineno): """ 开始检测函数 :param code_content: 要检测的文件内容 :param sensitive_func: 要检测的敏感函数,传入的为函数列表 + :param vul_lineno: 漏洞函数所在行号 :return: """ - back_node = [] - parser = make_parser() - all_nodes = parser.parse(code_content, debug=False, lexer=lexer.clone(), tracking=with_line) - for func in sensitive_func: # 循环判断代码中是否存在敏感函数,若存在,递归判断参数是否可控 - analysis(all_nodes, func, back_node) - - -# code_contents = """ -""" - -F_EXECS = [ # 命令执行的敏感函数 - 'backticks', - 'exec', - 'expect_popen', - 'passthru', - 'pcntl_exec', - 'popen', - 'proc_open', - 'shell_exec', - 'system', - 'mail', - 'mb_send_mail', - 'w32api_invoke_function', - 'w32api_register_function', -] - -scan(code_contents, F_EXECS) + try: + global scan_results + scan_results = [] + parser = make_parser() + all_nodes = parser.parse(code_content, debug=False, lexer=lexer.clone(), tracking=with_line) + for func in sensitive_func: # 循环判断代码中是否存在敏感函数,若存在,递归判断参数是否可控;对文件内容循环判断多次 + back_node = [] + analysis(all_nodes, func, back_node, int(vul_lineno), flag=0, function_params=None) + except SyntaxError as e: + logger.debug(e) + + return scan_results diff --git a/cobra/rule.py b/cobra/rule.py index 98847984..3e427f62 100644 --- a/cobra/rule.py +++ b/cobra/rule.py @@ -143,9 +143,12 @@ def rules(self, rules=None): files = os.listdir(self.rules_path) for vulnerability_name in files: # VN: CVI-190001.xml - v_path = os.path.join(self.rules_path, vulnerability_name) - if os.path.isfile(v_path) is not True or 'cvi-template' in v_path.lower() or vulnerability_name.lower()[0:7] == 'cvi-999' or 'cvi' not in v_path.lower() or '.xml' not in v_path.lower(): - logger.debug('Not regular rule file {f}'.format(f=v_path)) + v_path = os.path.join(self.rules_path, vulnerability_name.replace('cvi', 'CVI')) + if vulnerability_name.lower()[0:7] == 'cvi-999' or 'cvi' not in v_path.lower(): + logger.debug('filter dep rules') + continue + if os.path.isfile(v_path) is not True or '.xml' not in v_path.lower(): + logger.warning('Not regular rule file {f}'.format(f=v_path)) continue # rule information diff --git a/cobra/templates/asset/css/report.css b/cobra/templates/asset/css/report.css index f84a8b24..506aa0f5 100644 --- a/cobra/templates/asset/css/report.css +++ b/cobra/templates/asset/css/report.css @@ -83,6 +83,10 @@ min-height: 75px; } +.vulnerabilities_list > li.critical { + border-left: 5px solid red; +} + .vulnerabilities_list > li.high { border-left: 5px solid #ee9336; } diff --git a/cobra/templates/asset/js/report.js b/cobra/templates/asset/js/report.js index d8987a1e..557926b9 100644 --- a/cobra/templates/asset/js/report.js +++ b/cobra/templates/asset/js/report.js @@ -27,7 +27,7 @@ var score2level = { $(function () { var current_tab = ''; var c_tab = getParameterByName('t'); - if (c_tab !== null && c_tab !== '' && ['inf', 'vul', 'ext'].indexOf(c_tab) >= 0) { + if (c_tab !== null && c_tab !== '' && ['inf', 'tar', 'vul'].indexOf(c_tab) >= 0) { current_tab = c_tab; $(".nav-tabs li").removeClass('active'); $("a[data-id=" + c_tab + "]").parent('li').addClass('active'); @@ -83,10 +83,11 @@ $(function () { dataType: 'json', success: function (result) { if (result.code === 1001) { - data.code_content = result.result; - // 对无代码内容的漏洞进行处理,避免 widget 的 bug - if (data.code_content === "") { - data.code_content = data.file_path; + data.code_content = result.result.file_content; + data.language = result.result.extension; + // 对二进制文件情况进行处理,将行数置为 1 + if (data.code_content === "This is a binary file.") { + data.line_number = 1; } $('#code').val(data.code_content); // Highlighting param diff --git a/cobra/templates/summary.html b/cobra/templates/summary.html index f1f28f80..34564d4d 100644 --- a/cobra/templates/summary.html +++ b/cobra/templates/summary.html @@ -91,11 +91,13 @@
Number of vulnerabilities

Vulnerability distribution statistics

+ {% if vuls | length == 0 %}

Wow, no vulnerability was detected :)

+ {% endif %}
@@ -112,6 +114,7 @@

Targets Information

Target + Branch / Tag Language Framework Files @@ -124,8 +127,11 @@

Targets Information

{% for target_info in targets %} - {{ target_info.get('target') }} + + {{ target_info.get('target') }} + + {{ target_info.get('branch') }} {{ target_info.get('language') }} {{ target_info.get('framework') }} {{ target_info.get('file') }} @@ -289,8 +295,10 @@

Targets Information

} ] }; + {% if vuls | length > 0 %} var vul_distributing = echarts.init(document.getElementById('vul_distributing')); vul_distributing.setOption(option); + {% endif %} @@ -337,6 +345,7 @@

Targets Information

+ diff --git a/cobra/utils.py b/cobra/utils.py index 5e062cdd..25a73dd5 100644 --- a/cobra/utils.py +++ b/cobra/utils.py @@ -11,16 +11,24 @@ :license: MIT, see LICENSE for more details. :copyright: Copyright (c) 2017 Feei. All rights reserved """ +import hashlib +import json import os -import sys +import random import re -import time import string -import random -import hashlib +import sys +import time +import urllib +import requests +import json + from .log import logger -from .config import Config +from .config import Config, issue_history_path +from .__version__ import __version__, __python_version__, __platform__, __url__ +from .config import Config, issue_history_path from .exceptions import PickupException, NotExistException, AuthFailedException +from .log import logger from .pickup import Git, NotExistError, AuthError, Decompress TARGET_MODE_GIT = 'git' @@ -32,6 +40,7 @@ OUTPUT_MODE_API = 'api' OUTPUT_MODE_FILE = 'file' OUTPUT_MODE_STREAM = 'stream' +PY2 = sys.version_info[0] == 2 class ParseArgs(object): @@ -58,7 +67,8 @@ def __init__(self, target, formatter, output, special_rules=None, a_sid=None): special_rules += extension self.special_rules = [special_rules] else: - logger.critical('[PARSE-ARGS] Exception special rule name(e.g: CVI-110001): {sr}'.format(sr=special_rules)) + logger.critical( + '[PARSE-ARGS] Exception special rule name(e.g: CVI-110001): {sr}'.format(sr=special_rules)) else: self.special_rules = None self.sid = a_sid @@ -117,8 +127,13 @@ def target_directory(self, target_mode): if target_mode == TARGET_MODE_GIT: logger.debug('GIT Project') # branch or tag - branch_tag = r"(.*?.git):(\w+|[\w\d\.-]*)$" - target, branch = re.findall(branch_tag, self.target)[0] if re.findall(branch_tag, self.target) else (self.target, "master") + split_target = self.target.split(':') + if len(split_target) == 3: + target, branch = '{p}:{u}'.format(p=split_target[0], u=split_target[1]), split_target[-1] + elif len(split_target) == 2: + target, branch = self.target, 'master' + else: + logger.critical('Target url exception: {u}'.format(u=self.target)) if 'gitlab' in target: username = Config('git', 'username').value password = Config('git', 'password').value @@ -317,6 +332,67 @@ def random_generator(size=6, chars=string.ascii_uppercase + string.digits): return ''.join(random.choice(chars) for _ in range(size)) +def is_list(value): + """ + Returns True if the given value is a list-like instance + + >>> is_list([1, 2, 3]) + True + >>> is_list(u'2') + False + """ + + return isinstance(value, (list, tuple, set)) + + +def get_unicode(value, encoding=None, none_to_null=False): + """ + Return the unicode representation of the supplied value: + + >>> get_unicode(u'test') + u'test' + >>> get_unicode('test') + u'test' + >>> get_unicode(1) + u'1' + """ + + if none_to_null and value is None: + return None + if str(type(value)) == "": + value = value.encode('utf8') + return value + elif str(type(value)) == "": + return value + elif is_list(value): + value = list(get_unicode(_, encoding, none_to_null) for _ in value) + return value + else: + try: + return value.encode('utf8') + except UnicodeDecodeError: + return value.encode('utf8', errors="ignore") + + +def get_safe_ex_string(ex, encoding=None): + """ + Safe way how to get the proper exception represtation as a string + (Note: errors to be avoided: 1) "%s" % Exception(u'\u0161') and 2) "%s" % str(Exception(u'\u0161')) + + >>> get_safe_ex_string(Exception('foobar')) + u'foobar' + """ + + ret = ex + + if getattr(ex, "message", None): + ret = ex.message + elif getattr(ex, "msg", None): + ret = ex.msg + + return get_unicode(ret or "", encoding=encoding).strip() + + class Tool: def __init__(self): # `grep` (`ggrep` on Mac) @@ -345,3 +421,122 @@ def __init__(self): sys.exit(0) else: self.find = gfind + + +def secure_filename(filename): + _filename_utf8_strip_re = re.compile(u"[^\u4e00-\u9fa5A-Za-z0-9_.-]") + _windows_device_files = ('CON', 'AUX', 'COM1', 'COM2', 'COM3', 'COM4', 'LPT1', 'LPT2', 'LPT3', 'PRN', 'NUL') + + if PY2: + text_type = unicode + else: + text_type = str + + if isinstance(filename, text_type): + from unicodedata import normalize + filename = normalize('NFKD', filename).encode('utf-8', 'ignore') + if not PY2: + filename = filename.decode('utf-8') + for sep in os.path.sep, os.path.altsep: + if sep: + filename = filename.replace(sep, ' ') + if PY2: + filename = filename.decode('utf-8') + filename = _filename_utf8_strip_re.sub('', '_'.join(filename.split())) + + # on nt a couple of special files are present in each folder. We + # have to ensure that the target file is not such a filename. In + # this case we prepend an underline + if os.name == 'nt' and filename and \ + filename.split('.')[0].upper() in _windows_device_files: + filename = '_' + filename + + return filename + + +def unhandled_exception_message(): + """ + Returns detailed message about occurred unhandled exception + """ + err_msg = """Cobra version: {cv}\nPython version: {pv}\nOperating system: {os}\nCommand line: {cl}""".format( + cv=__version__, + pv=__python_version__, + os=__platform__, + cl=re.sub(r".+?\bcobra.py\b", "cobra.py", " ".join(sys.argv).encode('utf-8')) + ) + return err_msg + + +def create_github_issue(err_msg, exc_msg): + """ + Automatically create a Github issue with unhandled exception information + """ + issues = [] + try: + with open(issue_history_path, 'r') as f: + for line in f.readlines(): + issues.append(line.strip()) + except: + pass + finally: + # unique + issues = set(issues) + _ = re.sub(r"'[^']+'", "''", exc_msg) + _ = re.sub(r"\s+line \d+", "", _) + _ = re.sub(r'File ".+?/(\w+\.py)', "\g<1>", _) + _ = re.sub(r".+\Z", "", _) + key = hashlib.md5(_).hexdigest()[:8] + + if key in issues: + logger.warning('issue already reported!') + return + + ex = None + + try: + url = "https://api.github.com/search/issues?q={q}".format(q=urllib.quote("repo:wufeifei/cobra [AUTO] Unhandled exception (#{k})".format(k=key))) + logger.debug(url) + resp = requests.get(url=url) + content = resp.json() + _ = content + duplicate = _["total_count"] > 0 + closed = duplicate and _["items"][0]["state"] == "closed" + if duplicate: + warn_msg = "issue seems to be already reported" + if closed: + warn_msg += " and resolved. Please update to the latest version from official GitHub repository at '{u}'".format(u=__url__) + logger.warning(warn_msg) + return + except: + logger.warning('search github issue failed') + pass + + try: + url = "https://api.github.com/repos/wufeifei/cobra/issues" + data = { + "title": "[AUTO] Unhandled exception (#{k})".format(k=key), + "body": "## Environment\n```\n{err}\n```\n## Traceback\n```\n{exc}\n```\n".format(err=err_msg, exc=exc_msg) + } + headers = {"Authorization": "token {t}".format(t='48afbb61693ce187606388842ae1ccaa9a88a10a')} + resp = requests.post(url=url, data=json.dumps(data), headers=headers) + content = resp.text + except Exception as ex: + content = None + + issue_url = re.search(r"https://github.com/wufeifei/cobra/issues/\d+", content or "") + if issue_url: + info_msg = "created Github issue can been found at the address '{u}'".format(u=issue_url.group(0)) + logger.info(info_msg) + + try: + with open(issue_history_path, "a+b") as f: + f.write("{k}\n".format(k=key)) + except: + pass + else: + warn_msg = "something went wrong while creating a Github issue" + if ex: + warn_msg += " ('{m}')".format(m=get_safe_ex_string(ex)) + if "Unauthorized" in warn_msg: + warn_msg += ". Please update to the latest revision" + logger.warning(warn_msg) diff --git a/docs/api.md b/docs/api.md index 3c780da6..273f9f50 100644 --- a/docs/api.md +++ b/docs/api.md @@ -87,4 +87,6 @@ http://127.0.0.1/?sid=afbe69p7dxva ## 扫描详情报告 ``` http://127.0.0.1/report/afbe69p7dxva/sfbe69plo5qs -``` \ No newline at end of file +``` +--- +下一章:[高级功能配置](https://wufeifei.github.io/cobra/config) \ No newline at end of file diff --git a/docs/cli.md b/docs/cli.md index a6f77024..6f5aec2a 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -28,4 +28,52 @@ $ ./cobra.py --version # 查看帮助 $ ./cobra.py --help -``` \ No newline at end of file +``` + +## Help(帮助) +```bash +➜ cobra git:(master) ✗ ./cobra.py --help +usage: cobra [-h] [-t ] [-f ] [-o ] [-r ] + [-d] [-sid SID] [-H ] [-P ] + + ,---. | + | ,---.|---.,---.,---. + | | || || ,---| + `---``---``---`` `---^ v2.0.0 + +GitHub: https://github.com/wufeifei/cobra + +Cobra is a static code analysis system that automates the detecting vulnerabilities and security issue. + +optional arguments: + -h, --help show this help message and exit + +Scan: + -t , --target + file, folder, compress, or repository address + -f , --format + vulnerability output format (formats: html, json, csv, + xml) + -o , --output + vulnerability output STREAM, FILE, HTTP API URL, MAIL + -r , --rule + specifies rules e.g: CVI-100001,cvi-190001 + -d, --debug open debug mode + -sid SID, --sid SID scan id(API) + +RESTful: + -H , --host + REST-JSON API Service Host + -P , --port + REST-JSON API Service Port + +Usage: + ./cobra.py -t tests/vulnerabilities + ./cobra.py -t tests/vulnerabilities -r cvi-190001,cvi-190002 + ./cobra.py -t tests/vulnerabilities -f json -o /tmp/report.json + ./cobra.py -t https://github.com/wufeifei/vc.git -f json -o feei@feei.cn + ./cobra.py -t https://github.com/wufeifei/vc.git -f json -o http://push.to.com/api + sudo ./cobra.py -H 127.0.0.1 -P 80 +``` +--- +下一章:[API模式使用方法](https://wufeifei.github.io/cobra/api) \ No newline at end of file diff --git a/docs/config.md b/docs/config.md index 508b4f21..f2e8fddd 100644 --- a/docs/config.md +++ b/docs/config.md @@ -7,4 +7,7 @@ cp config.template config - 将扫描结果发送到指定邮箱 - 扫描私有GIT项目 -- 变更API Server端口域名 \ No newline at end of file +- 变更API Server端口域名 + +--- +下一章:[升级框架和规则源](https://wufeifei.github.io/cobra/upgrade) \ No newline at end of file diff --git a/docs/contributors.md b/docs/contributors.md new file mode 100644 index 00000000..6d10ecb7 --- /dev/null +++ b/docs/contributors.md @@ -0,0 +1,14 @@ +# Contributors(贡献者) + +## 核心开发者 +> 代码贡献量大于1k行或规则贡献大于10条。 + +| ID | Blog | Mail | +|---|---|---| +| [Feei](https://github.com/wufeifei) | [https://feei.cn](https://feei.cn) | feei@feei.cn | +| [LiGhT1EsS](https://github.com/LiGhT1EsS) | [https://lightless.me](https://lightless.me) | root@lightless.me | +| [40huo](https://github.com/40huo) | [https://www.40huo.cn](https://www.40huo.cn) | i@40huo.cn | +| [BlBana](https://github.com/BlBana) | [http://drops.blbana.cc](http://drops.blbana.cc) | 635373043@qq.com | + +## 主要贡献者 +braveghz、M2shad0w、alioth310、l4yn3、boltomli、JoyChou93 diff --git a/docs/index.md b/docs/index.md index ea64d786..78105a34 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,11 +1,68 @@ +# Introduction(介绍) + +## 什么是"源代码安全审计(白盒扫描)"? +> 由于每位开发人员的技术水平和安全意识各不相同,就导致可能写出一些存在安全漏洞的代码。 +> 而攻击者可以通过渗透测试来找到这些漏洞,从而导致应用被攻击、服务器被入侵、数据被下载、业务受到影响等等问题。 +> "源代码安全审计"是指通过审计发现源代码中的安全问题,而Cobra可将这个流程自动化。 + +## Cobra为什么能从源代码中扫描到漏洞? +> 对于一些特征较为明显的可以使用正则规则来直接进行匹配出,比如硬编码密码、错误的配置等。 +> 对于一些需要判断参数是否可控的,Cobra通过预先梳理能造成危害的函数,并定位代码中所有出现该危害函数的地方,继而基于Lex(Lexical Analyzer Generator, 词法分析生成器)和Yacc(Yet Another Compiler-Compiler, 编译器代码生成器)将对应源代码解析为AST(Abstract Syntax Tree, 抽象语法树),分析危害函数的入参是否可控来判断是否存在漏洞。 + +## Cobra和其它源代码审计系统有什么区别或优势? +> Cobra目标是自动化发现源代码中大部分显著的安全问题,对于一些隐藏较深或特有的问题建议手工审计。 + +- 开源免费(基于开放的MIT License) +- 支持开发语言多(支持十多种开发语言和文件类型) +- 支持漏洞类型多(支持数十种漏洞类型) +- 支持各种场景集成(提供API也可以命令行使用) +- 专业支持,持续维护(由白帽子、开发工程师和安全工程师一起持续维护更新,并在多家企业内部使用) + +## Cobra支持哪些开发语言? +> 目前Cobra主要支持PHP、Java等主要开发语言及其它数十种文件类型,并持续更新规则和引擎以支持更多开发语言,具体见支持的[开发语言和文件类型](https://wufeifei.github.io/cobra/languages)。 + +## Cobra能发现哪些漏洞? +> 覆盖大部分Web端常见漏洞和一些移动端(Android、iOS)通用漏洞,具体见支持的[漏洞类型](https://wufeifei.github.io/cobra/labels)。 + +## Cobra能应用在哪些场景? +1. 【漏洞出现前】通过内置的扫描规则对公司项目进行日常扫描,并推进解决发现的漏洞。 +2. 【漏洞出现后】当出现一种新漏洞,可以立刻编写一条Cobra扫描规则对公司全部项目进行扫描来判断受影响的项目。 + +## Cobra是什么类型应用? +> Cobra提供Web服务的同时也提供了命令行服务。 + +1. 部署成Web服务,提供自助扫描服务和API服务。 +2. 使用CLI模式,在命令行中进行扫描。 + +## 如何参与Cobra开发? +> 如果你符合以下任何一项且对Cobra感兴趣,可加入我们的社区开发小组(QQ群:578732936)。 + +- 拥有Python开发经验 +- 有黑盒漏洞挖掘经验 +- 有白盒源码审计经验 + +## 互联网相关企业如何接入Cobra? +> Cobra欢迎任何互联网相关企业免费接入使用,并提供免费技术支持,可以联系我们(QQ:3504599069,加之前请备注企业名称)。 + # Cobra文档 -- [Cobra介绍](https://wufeifei.github.io/cobra/introduction) -- [Cobra安装](https://wufeifei.github.io/cobra/installation) -- 使用方法 +- 安装 + - [Cobra安装](https://wufeifei.github.io/cobra/installation) +- 基础使用 - [CLI模式使用方法](https://wufeifei.github.io/cobra/cli) - [API模式使用方法](https://wufeifei.github.io/cobra/api) -- [高级功能配置](https://wufeifei.github.io/cobra/config) -- [升级框架和规则源](https://wufeifei.github.io/cobra/upgrade) -- [扫描规则开发规范](https://wufeifei.github.io/cobra/rule) +- 进阶使用 + - [高级功能配置](https://wufeifei.github.io/cobra/config) + - [升级框架和规则源](https://wufeifei.github.io/cobra/upgrade) +- 规则开发规范 + - [规则模板](https://wufeifei.github.io/cobra/rule_template) + - [规则样例](https://wufeifei.github.io/cobra/rule_demo) + - [规则文件命名规范](https://wufeifei.github.io/cobra/rule_name) + - [规则开发流程](https://wufeifei.github.io/cobra/rule_flow) +- 框架引擎 + - [开发语言和文件类型定义](https://wufeifei.github.io/cobra/languages) + - [漏洞类型定义](https://wufeifei.github.io/cobra/labels) + - [危害等级定义](https://wufeifei.github.io/cobra/level) + - [程序目录结构](https://wufeifei.github.io/cobra/tree) - 贡献代码 - - [单元测试](https://wufeifei.github.io/cobra/test) \ No newline at end of file + - [单元测试](https://wufeifei.github.io/cobra/test) + - [贡献者](https://wufeifei.github.io/cobra/contributors) \ No newline at end of file diff --git a/docs/installation.md b/docs/installation.md index ecbc31b4..7a164a96 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -1,5 +1,13 @@ # Installation(安装) +## 系统支持 + +|系统|支持情况| +|---|---| +| mac OS | 支持 | +| Linux | 支持 | +| Windows | 暂不支持 | + ## Python版本 Cobra可运行在以下Python版本 - 2.6 @@ -8,15 +16,21 @@ Cobra可运行在以下Python版本 - 3.4 - 3.5 - 3.6 + - 3.6+ + +## 特殊依赖 -## macOS系统依赖 +#### macOS系统依赖 ``` brew install grep findutils ``` -## 安装Cobra +## 安装方法 ```bash git clone https://github.com/wufeifei/cobra.git && cd cobra pip install -r requirements.txt ./cobra.py --help -``` \ No newline at end of file +``` + +--- +下一章:[CLI模式使用方法](https://wufeifei.github.io/cobra/cli) \ No newline at end of file diff --git a/docs/introduction.md b/docs/introduction.md deleted file mode 100644 index cdacc81c..00000000 --- a/docs/introduction.md +++ /dev/null @@ -1,34 +0,0 @@ -# Introduction(介绍) - -## 什么是"源代码安全审计(白盒扫描)"? -> 由于每位开发人员的技术水平和安全意识各不相同,就导致可能写出一些存在安全漏洞的代码。 -> 而攻击者可以通过渗透测试来找到这些漏洞,从而导致应用被攻击、服务器被入侵、数据被下载、业务受到影响等等问题。 -> "源代码安全审计"是指通过审计发现源代码中的安全问题,而Cobra可将这个流程自动化。 - -## Cobra为什么能从源代码中扫描到漏洞? -> Cobra基于Lex(Lexical Analyzer Generator, 词法分析生成器)和Yacc(Yet Another Compiler-Compiler, 编译器代码生成器)将源代码解析为AST(Abstract Syntax Tree, 抽象语法树),通过预先将各类漏洞编写成对应的识别扫描规则添加到Cobra框架中,Cobra即可根据规则来识别代码中的漏洞。 - -## Cobra和其它源代码审计系统有什么区别或优势? -> 自动化扫描源码中大部分显著的安全问题。 - -- 开源免费 -- 支持开发语言多 -- 支持漏洞类型多 -- 支持各种场景集成 -- 持续维护更新 - -## Cobra支持哪些开发语言? -> 目前Cobra主要支持PHP、Java和Objective-C三种主要语言及其它数十种文件类型,并持续更新规则支持更多开发语言。 - -## Cobra能发现哪些漏洞? -> 覆盖所有常见漏洞,具体见规则编写中的Labels。 - -## Cobra能应用在哪些场景? -1. 【漏洞出现前】通过内置的扫描规则对公司项目进行日常扫描,并推进解决。 -2. 【漏洞出现后】当出现一种新漏洞,可以立刻编写一条Cobra扫描规则对公司全部项目进行扫描来判断受影响的项目。 - -## Cobra是什么类型应用? -> Cobra提供Web服务的同时也提供了命令行服务。 - -1. 部署成Web服务,提供自助扫描服务和API服务。 -2. 使用CLI模式,在命令行中进行扫描。 \ No newline at end of file diff --git a/docs/labels.md b/docs/labels.md new file mode 100644 index 00000000..ac3216ae --- /dev/null +++ b/docs/labels.md @@ -0,0 +1,39 @@ +# Labels(标签) +> 标签用来标记扫描规则所属的漏洞类型。 + +| ID | Label | Description(EN) | Description(CN) | +|---|---|---|---| +| 110 | MS | Misconfiguration | 错误的配置 | +| 120 | SSRF | Server-Side Forge | 服务端伪造 | +| 130 | HCP | Hard-coded Password | 硬编码密码 | +| 140 | XSS | Cross-Site Script | 跨站脚本 | +| 150 | CSRF | Cross-Site Request Forge | 跨站请求伪造 | +| 160 | SQLI | SQL Injection | SQL注入 | +| 163 | XI | Xpath Injection | Xpath注入 | +| 165 | LI | LDAP Injection | LDAP注入 | +| 167 | XEI| XML External Entity Injection | XML实体注入 | +| 170 | FI | Local/Remote File Inclusion | 文件包含漏洞 | +| 180 | CI | Code Injection | 代码注入 | +| 181 | CI | Command Injection | 命令注入 | +| 190 | IE | Information Exposure | 信息泄露 | +| 200 | PPG | Predictable Pseudorandom Generator | 可预测的伪随机数生成器 | +| 210 | UR | Unvalidated Redirect | 未经验证的任意链接跳转 | +| 220 | HRS | HTTP Response Splitting | HTTP响应拆分 | +| 230 | SF | Session Fixation | SESSION固定 | +| 260 | US | unSerialize | 反序列化漏洞 | +| 280 | DF | Deprecated Function | 废弃的函数 | +| 290 | LB | Logic Bug | 逻辑错误 | +| 320 | VO | Variables Override | 变量覆盖漏洞 | +| 350 | WF | Weak Function | 不安全的函数 | +| 355 | WE |Weak Encryption | 不安全的加密 | +| 970 | AV | Android Vulnerabilities | Android漏洞 | +| 980 | IV | iOS Vulnerabilities | iOS漏洞 | +| 999 | IC | Insecure Components| 引用了存在漏洞的三方组件(Maven/Pods/PIP/NPM) | + +## Label制定规范 +- 大类别以10为基数叠加ID的第一位和第二位,小类别以2为基数叠加ID第三位。 +- Label缩写默认使用描述的首字母缩写,最长四位字符。 +- 编辑后请修改对应的`rules/vulnerabilities.xml` + +--- +下一章:[危害等级定义](https://wufeifei.github.io/cobra/level) \ No newline at end of file diff --git a/docs/languages.md b/docs/languages.md new file mode 100644 index 00000000..5d35f681 --- /dev/null +++ b/docs/languages.md @@ -0,0 +1,46 @@ +# Languages(语言) +> 语言用来标记扫描规则对应需要扫描的后缀。 +> Cobra支持以下开发语言和文件类型,支持程度取决于对应开发语言或文件类型的规则数量。 + +|语言|主语言|后缀| +|---|---|---| +|PHP|是|.php/.php3/.php4/.php5| +|Java|是|.java| +|Python|是|.py| +|JSP|否|.jsp| +|C|否|.h/.c| +|Ruby|否|.rb| +|Perl|否|.pl| +|Lua|否|.lua| +|Go|否|.go| +|Swift|否|.swift| +|C++|否|.c/.cpp| +|C#|否|.cs| +|Header|否|.h| +|Objective-C|否|.m| +|Scale|否|.sbt/.scale| +|Ceylon|否|.ceylon| +|Kotlin|否|.kt| +|SHELL|否|.sh| +|BAT|否|.bat| +|JavaScript|否|.js| +|HTML|否|.html/.htm/.pthml| +|CSS|否|.css/.less/.scss/.styl| +|Image|否|.jpg/.png/.bmp/.gif/.ico/.cur| +|Font|否|.eot/.otf/.svg/.ttf/.woff| +|Conf|否|.properties/.conf/.ini/.cfg/.yml/.xml/.iml/.sfp/.manifest| +|CMake|否|.cmake/.cmake.in| +|SQL|否|.sql| +|Compression|否|.zip/.tar/.tar.gz/.rar| +|Executable|否|.exe| +|LOG|否|.log| +|Text|否|.txt/.text/.md/.rst/.csv| +|Office|否|.doc/.docx/.wps/.rtf/.xls/.ppt| +|Media|否|.mp3/.mp4/.swf/.flv| +|Certificate|否|.p12/.crt/.key/.pfx/.csr| +|Source|否|.psd/.ai/.axure/.xmind/.plan| +|Thumb|否|.db/.DS_Store| +|GIT|否|.pack/.idx/.sample| + +--- +下一章:[漏洞类型定义](https://wufeifei.github.io/cobra/labels) \ No newline at end of file diff --git a/docs/level.md b/docs/level.md new file mode 100644 index 00000000..429597b1 --- /dev/null +++ b/docs/level.md @@ -0,0 +1,12 @@ +# Level(危害等级) +> 漏洞等级定义建议按照漏洞最大危害来确定。 + +| 等级 | 分值 | 描述 | +|---|---|---| +| 严重 | 9-10 | 1.可获取服务器权限; 2.严重信息泄露; | +| 高危 | 6-8 | 1.敏感信息泄露; 2.越权; 3.任意文件读取; 4.SQL注入; 5.git/svn泄露; 6.SSRF;| +| 中危 | 3-5 | 1.XSS; 2.URL跳转; 3.CRLF; 4.LFI;| +| 低危 | 1-2 | 1.CSRF; 2.JSONP劫持; 3.异常堆栈信息; 3.PHPINFO; 4.路径泄露; 5.硬编码密码; 6.硬编码内网IP域名; 7.不安全的加密方法; 8.不安全的随机数; 9.日志敏感记录;| + +--- +下一章:[程序目录结构](https://wufeifei.github.io/cobra/tree) \ No newline at end of file diff --git a/docs/report_01.jpg b/docs/report_01.jpg new file mode 100644 index 00000000..7513ebe0 Binary files /dev/null and b/docs/report_01.jpg differ diff --git a/docs/report_02.jpg b/docs/report_02.jpg new file mode 100644 index 00000000..0c2c4a2b Binary files /dev/null and b/docs/report_02.jpg differ diff --git a/docs/report_03.jpg b/docs/report_03.jpg new file mode 100644 index 00000000..41c333ad Binary files /dev/null and b/docs/report_03.jpg differ diff --git a/docs/rule.md b/docs/rule.md deleted file mode 100644 index 3e0b15b6..00000000 --- a/docs/rule.md +++ /dev/null @@ -1,170 +0,0 @@ -# Rule(规则开发规范) - -## 一、Flow(规则编写流程) -1. 开发规则文件`CVI-XXXNNN.xml` -2. 开发漏洞代码`tests/vulnerabilities/v.language` -3. 测试规则扫描`./cobra.py -t tests/vulnerabilities/` - -## 二、Rule Template(规则模板) -```xml - - - - - - - - - - - - - - - - - - - ## 安全风险 - 硬编码密码 - - ## 修复方案 - 将密码抽出统一放在配置文件中,配置文件不放在git中 - - - - -``` - -## 三、规则文件命名规范 -`rules/CVI-100001.xml` -- 统一存放在`rules`目录 -- 大写字母CVI(Cobra Vulnerability ID)开头,横杠(-)分割 -- 六位数字组成,前三位为Label ID,后三位为自增ID -- 结尾以小写.xml结束 - -## 四、规则编写规范 - -|字段(英文)|字段(中文)|是否必填|类型|描述|例子| -|---|---|---|---|---|---| -|`name`|规则名称|是|`string`|描述规则名称|``| -|`language`|规则语言|是|`string`|设置规则针对的开发语言,参见`rules/languages.xml`|``| -|`match`|匹配规则1|是|`string`|匹配规则1|``| -|`match2`|匹配规则2|否|`string`|匹配规则2|``| -|`repair`|修复规则|否|`string`|匹配到此规则,则不算做漏洞|``| -|`level`|影响等级|是|`integer`|标记该规则扫到的漏洞危害等级,使用数字1-10。|``| -|`solution`|修复方案|是|`string`|该规则扫描的漏洞对应的**安全风险**和**修复方案**|`详细的安全风险和修复方案`| -|`test`|测试用例|是|`case`|该规则对应的测试用例|``| -|`status`|是否开启|是|`boolean`|是否开启该规则的扫描,使用`on`/`off`来标记|``| -|`author`|规则作者|是|`attr`|规则作者的姓名和邮箱|``| - -## 五、``/``/``编写规范 - -#### `` Mode(``的规则模式) -> 用来描述规则类型,只能用在``中。 - -|Mode|类型|默认模式|描述| -|---|---|---|---| -|regex-only-match|正则仅匹配|是|默认是此模式,但需要显式的写在规则文件里。以正则的方式进行匹配,匹配到内容则算作漏洞| -|regex-param-controllable|正则参数可控|否|以正则模式进行匹配,匹配出的变量可外部控制则为漏洞| -|function-param-controllable|函数参数可控|否|内容写函数名,将搜索所有该函数的调用,若参数外部可控则为漏洞| -|find-extension|寻找指定后缀文件|否|找到指定后缀文件则算作漏洞| - -#### ``/`` Block(``/``的匹配区块) -> 用来描述需要匹配的代码区块位置,只能用在``或``中。 - -|区块|描述| -|---|---| -| in-current-line | 由第一条规则触发的所在行 | -| in-function | 由第一条规则触发的函数体内 | -| in-function-up | 由第一条规则触发的所在行之上,所在函数体之内 | -| in-function-down | 由第一条规则触发的所在行之下,所在函数体之内 | -| in-file | 由第一条规则触发的文件内 | -| in-file-up | 由第一条规则触发的所在行之上,所在文件之内 | -| in-file-down | 由第一条规则触发的所在行之下,所在文件之内 | - - -## 六、Demo(例子) -> 把常见漏洞划分为四大类 - -#### 1. 单一匹配: 仅匹配单次 -**例子:错误的配置(使用了ECB模式)** -```java -Cipher c = Cipher.getInstance("AES/ECB/NoPadding"); -``` - -**Solution(规则写法)** - -可以通过配置一条match规则,规则mode设置为`regex`(仅匹配,通过正则模式匹配,匹配到则算作漏洞),即可扫描这类问题。 -```xml - -``` -#### 2. 多次匹配:需要进行多次匹配 -**例子:不安全的随机数(首先需要匹配到生成了随机数`new Random`,然后要确保随机数是系统的随机数而非自定义函数)** -```java -import util.random; -Random r = new Random(); -``` -**Solution(规则写法)** - -先配置一条`match`规则来匹配`new Random`,再配置一条`match`来匹配`import util.random`。 -```xml - - -``` - -#### 3. 参数可控:只要判定参数是用户可控的则算作漏洞 -**例子:反射型XSS(直接输出入参)** -```php -$content = $_GET['content']; -print("Text: " + $content); -``` - -**Solution(规则写法)** - -```xml - -``` - -### 4. 依赖安全:当依赖了某个不安全版本的三方组件 - - -## 七、Labels(标签) - -| ID | Label | Description(EN) | Description(CN) | -|---|---|---|---| -| 110 | MS | Misconfiguration | 错误的配置 | -| 120 | SSRF | Server-Side Forge | 服务端伪造 | -| 130 | HCP | Hard-coded Password | 硬编码密码 | -| 140 | XSS | Cross-Site Script | 跨站脚本 | -| 150 | CSRF | Cross-Site Request Forge | 跨站请求伪造 | -| 160 | SQLI | SQL Injection | SQL注入 | -| 170 | LFI/RFI | Local/ Remote File inclusion | 文件包含漏洞 | -| 180 | CI | Code Injection | 代码注入 | -| 181 | CI | Command Injection | 命令注入 | -| 190 | IE | Information Exposure | 信息泄露 | -| 200 | PPG | Predictable Pseudorandom Generator | 可预测的伪随机数生成器 | -| 210 | UR | Unvalidated Redirect | 未经验证的任意链接跳转 | -| 220 | HRS | HTTP Response Splitting | HTTP响应拆分 | -| 230 | SF | Session Fixation | SESSION固定 | -| 240 | XpathI | Xpath Injection | Xpath注入 | -| 250 | LDAP | LDAP Injection | LDAP注入 | -| 260 | Unserialize | unserialize | 反序列化漏洞 | -| 270 | XXEI| XML External Entity Injection | XML实体注入 | -| 280 | DF | Deprecated Function | 废弃的函数 | -| 290 | LB | Logic Bug | 逻辑错误 | -| 320 | VO | Variables Override | 变量覆盖漏洞 | -| 330 | Encryption | 不安全的加密 | -| 350 | WF | Weak Function | 不安全的函数 | -| 970 | AV | Android Vulnerabilities | Android漏洞 | -| 980 | IV | iOS Vulnerabilities | iOS漏洞 | -| 999 | IC | Insecure Components| 引用了存在漏洞的三方组件(Maven/Pods/PIP/NPM) | - -## 八、Level(危害等级) - -| 等级 | 分值 | 描述 | -|---|---|---| -| 严重 | 9-10 | 1.可获取服务器权限; 2.严重信息泄露; | -| 高危 | 6-8 | 1.敏感信息泄露; 2.越权; 3.任意文件读取; 4.SQL注入; 5.git/svn泄露; 6.SSRF;| -| 中危 | 3-5 | 1.XSS; 2.URL跳转; 3.CRLF; 4.LFI;| -| 低危 | 1-2 | 1.CSRF; 2.JSONP劫持; 3.异常堆栈信息; 3.PHPINFO; 4.路径泄露; 5.硬编码密码; 6.硬编码内网IP域名; 7.不安全的加密方法; 8.不安全的随机数; 9.日志敏感记录;| \ No newline at end of file diff --git a/docs/rule_demo.md b/docs/rule_demo.md new file mode 100644 index 00000000..23131734 --- /dev/null +++ b/docs/rule_demo.md @@ -0,0 +1,57 @@ +# Demo(例子) +> 把常见漏洞划分为四大类 + +## 1. 单一匹配: 仅匹配单次 +**例子:错误的配置(使用了ECB模式)** +```java +Cipher c = Cipher.getInstance("AES/ECB/NoPadding"); +``` + +**Solution(规则写法)** + +可以通过配置一条match规则,规则mode设置为`regex`(仅匹配,通过正则模式匹配,匹配到则算作漏洞),即可扫描这类问题。 +```xml + +``` +## 2. 多次匹配:需要进行多次匹配 +**例子:不安全的随机数(首先需要匹配到生成了随机数`new Random`,然后要确保随机数是系统的随机数而非自定义函数)** +```java +import util.random; +Random r = new Random(); +``` +**Solution(规则写法)** + +先配置一条`match`规则来匹配`new Random`,再配置一条`match`来匹配`import util.random`。 +```xml + + +``` + +## 3. 参数可控:只要判定参数是用户可控的则算作漏洞 +**例子:反射型XSS(直接输出入参)** +```php +$content = $_GET['content']; +print("Text: " + $content); +``` + +**Solution(规则写法)** + +```xml + +``` + +### 4. 依赖安全:当依赖了某个不安全版本的三方组件 +> 依赖安全的规则固定配置在`CVI-999999.xml`中。 + +**例子:FastJSON v1.2.24版本存在RCE漏洞** + +**Solution(规则写法)** + +```xml + + fastjson:1.2.24 + +``` + +--- +下一章:[规则文件命名规范](https://wufeifei.github.io/cobra/rule_name) \ No newline at end of file diff --git a/docs/rule_flow.md b/docs/rule_flow.md new file mode 100644 index 00000000..45ad3a7c --- /dev/null +++ b/docs/rule_flow.md @@ -0,0 +1,14 @@ +# Flow(规则编写流程) + +#### 1. 编写规则文件`CVI-XXXNNN.xml` + 参考[规则命名](https://wufeifei.github.io/cobra/rule_name)建立规则文件。 + 参考[规则模板](https://wufeifei.github.io/cobra/rule_template)和[规则样例](https://wufeifei.github.io/cobra/rule_demo)编写对应的规则、修复方案、测试用例等。 + +#### 2. 编写漏洞代码`tests/vulnerabilities/v.language` + 编写实际可能出现的业务场景代码(只需编写一处即可)。 + +#### 3. 测试规则扫描`./cobra.py -t tests/vulnerabilities/` + 测试扫描结果 + +--- +下一章:[开发语言和文件类型定义](https://wufeifei.github.io/cobra/languages) \ No newline at end of file diff --git a/docs/rule_name.md b/docs/rule_name.md new file mode 100644 index 00000000..28f87c30 --- /dev/null +++ b/docs/rule_name.md @@ -0,0 +1,9 @@ +# 规则文件命名规范 +`rules/CVI-100001.xml` +- 统一存放在`rules`目录 +- 大写字母CVI(Cobra Vulnerability ID)开头,横杠(-)分割 +- 六位数字组成,前三位为Label ID,后三位为自增ID +- 结尾以小写.xml结束 + +--- +下一章:[规则开发流程](https://wufeifei.github.io/cobra/rule_flow) \ No newline at end of file diff --git a/docs/rule_template.md b/docs/rule_template.md new file mode 100644 index 00000000..8038090f --- /dev/null +++ b/docs/rule_template.md @@ -0,0 +1,74 @@ +# Rule Template(规则模板) +```xml + + + + + + + + + + + + + + + + + + + ## 安全风险 + 硬编码密码 + + ## 修复方案 + 将密码抽出统一放在配置文件中,配置文件不放在git中 + + + + +``` + + +## 规则字段规范 + +|字段(英文)|字段(中文)|是否必填|类型|描述|例子| +|---|---|---|---|---|---| +|`name`|规则名称|是|`string`|描述规则名称|``| +|`language`|规则语言|是|`string`|设置规则针对的开发语言,参见`languages`|``| +|`match`|匹配规则1|是|`string`|匹配规则1|``| +|`match2`|匹配规则2|否|`string`|匹配规则2|``| +|`repair`|修复规则|否|`string`|匹配到此规则,则不算做漏洞|``| +|`level`|影响等级|是|`integer`|标记该规则扫到的漏洞危害等级,使用数字1-10。|``| +|`solution`|修复方案|是|`string`|该规则扫描的漏洞对应的**安全风险**和**修复方案**|`详细的安全风险和修复方案`| +|`test`|测试用例|是|`case`|该规则对应的测试用例|``| +|`status`|是否开启|是|`boolean`|是否开启该规则的扫描,使用`on`/`off`来标记|``| +|`author`|规则作者|是|`attr`|规则作者的姓名和邮箱|``| + +## 核心字段``/``/``编写规范 + +#### `` Mode(``的规则模式) +> 用来描述规则类型,只能用在``中。 + +|Mode|类型|默认模式|支持语言|描述| +|---|---|---|---|---| +|regex-only-match|正则仅匹配|是|*|默认是此模式,但需要显式的写在规则文件里。以正则的方式进行匹配,匹配到内容则算作漏洞| +|regex-param-controllable|正则参数可控|否|PHP/Java|以正则模式进行匹配,匹配出的变量可外部控制则为漏洞| +|function-param-controllable|函数参数可控|否|PHP|内容写函数名,将搜索所有该函数的调用,若参数外部可控则为漏洞。| +|find-extension|寻找指定后缀文件|否|*|找到指定后缀文件则算作漏洞| + +#### ``/`` Block(``/``的匹配区块) +> 用来描述需要匹配的代码区块位置,只能用在``或``中。 + +|区块|描述| +|---|---| +| in-current-line | 由第一条规则触发的所在行 | +| in-function | 由第一条规则触发的函数体内 | +| in-function-up | 由第一条规则触发的所在行之上,所在函数体之内 | +| in-function-down | 由第一条规则触发的所在行之下,所在函数体之内 | +| in-file | 由第一条规则触发的文件内 | +| in-file-up | 由第一条规则触发的所在行之上,所在文件之内 | +| in-file-down | 由第一条规则触发的所在行之下,所在文件之内 | + +--- +下一章:[规则样例](https://wufeifei.github.io/cobra/rule_demo) \ No newline at end of file diff --git a/docs/test.md b/docs/test.md index f20e02ba..644e73da 100644 --- a/docs/test.md +++ b/docs/test.md @@ -1,4 +1,4 @@ -## Test(测试) +# Test(测试) ``` # TestCase Test @@ -6,4 +6,7 @@ $ pytest -v # TestCase Coverage $ py.test --cov cobra cobra/tests -``` \ No newline at end of file +``` + +--- +下一章:[贡献者](https://wufeifei.github.io/cobra/contributors) \ No newline at end of file diff --git a/docs/tree.md b/docs/tree.md new file mode 100644 index 00000000..e4d4177d --- /dev/null +++ b/docs/tree.md @@ -0,0 +1,68 @@ +# 目录结构(Tree) +```bash +. +├── CONTRIBUTING.md(贡献文档) +├── LICENSE(开源协议) +├── README.md(介绍页) +├── cobra(主程序目录) +│   ├── __init__.py +│   ├── __version__.py +│   ├── api.py +│   ├── cast.py +│   ├── cli.py +│   ├── config.py +│   ├── const.py +│   ├── cve_parse.py +│   ├── dependencies.py +│   ├── detection.py +│   ├── engine.py +│   ├── exceptions.py +│   ├── export.py +│   ├── git_projects.py +│   ├── log.py +│   ├── parser.py +│   ├── pickup.py +│   ├── push_to_api.py +│   ├── result.py +│   ├── rule.py +│   ├── scheduler +│   │   ├── __init__.py +│   │   ├── report.js +│   │   ├── report.py +│   │   └── scan.py +│   ├── send_mail.py +│   ├── templates +│   │   ├── asset(前台展示所需要的静态文件) +│   │   │   ├── codemirror +│   │   │   ├── css +│   │   │   ├── fonts +│   │   │   ├── ico +│   │   │   ├── icon +│   │   │   ├── img +│   │   │   └── js +│   │   └── *.html +│   ├── templite.py +│   └── utils.py +├── cobra.py(Cobra入口调用) +├── config(Cobra主配置文件) +├── config.template(Cobra主配置文件模板) +├── docs +│   ├── _config.yml(GitHub Pages配置) +│   └── *.md (相关文档) +├── logs(日志目录) +├── requirements.txt (Cobra包依赖) +├── rules(规则相关) +│   ├── CVI-110001.xml (漏洞扫描规则定义) +│   ├── frameworks.xml (框架识别的特征定义) +│   ├── languages.xml (开发语言及文件类型与后缀定义) +│   └── vulnerabilities.xml (漏洞类型及Label定义) +└── tests(测试相关) + ├── __init__.py + ├── ast (AST测试) + ├── examples (测试用例所依赖的) + ├── test_*.py (测试用例) + └── vulnerabilities (各类测试的漏洞代码) +``` + +--- +下一章:[单元测试](https://wufeifei.github.io/cobra/test) \ No newline at end of file diff --git a/docs/upgrade.md b/docs/upgrade.md index b52abcfa..e7720f35 100644 --- a/docs/upgrade.md +++ b/docs/upgrade.md @@ -5,3 +5,5 @@ ```bash git pull origin master ``` +--- +下一章:[规则模板](https://wufeifei.github.io/cobra/rule_template) \ No newline at end of file diff --git a/rules/CVI-140003.xml b/rules/CVI-140003.xml index 49e27a71..0b3fe9dd 100644 --- a/rules/CVI-140003.xml +++ b/rules/CVI-140003.xml @@ -2,7 +2,7 @@ - + diff --git a/rules/CVI-270001.xml b/rules/CVI-167001.xml similarity index 100% rename from rules/CVI-270001.xml rename to rules/CVI-167001.xml diff --git a/rules/CVI-181001.xml b/rules/CVI-181001.xml index c24201fc..0386083d 100644 --- a/rules/CVI-181001.xml +++ b/rules/CVI-181001.xml @@ -2,7 +2,7 @@ - + diff --git a/rules/CVI-190008.xml b/rules/CVI-190008.xml index 2209837c..04308f56 100644 --- a/rules/CVI-190008.xml +++ b/rules/CVI-190008.xml @@ -2,7 +2,7 @@ - + diff --git a/rules/CVI-210001.xml b/rules/CVI-210001.xml index 7e3e2497..8068757c 100644 --- a/rules/CVI-210001.xml +++ b/rules/CVI-210001.xml @@ -2,8 +2,7 @@ - - + diff --git a/rules/CVI-220001.xml b/rules/CVI-220001.xml deleted file mode 100644 index 3a7567fc..00000000 --- a/rules/CVI-220001.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - ## 安全风险 - `` - `/index.php?url=a%0a%0dContent-Type:%20text/html%0a%0d%0a%0d` - - ## 修复方案 - 使用白名单判断 - ```php - - ``` - - - - - - - \ No newline at end of file diff --git a/rules/CVI-230001.xml b/rules/CVI-230001.xml index f3ca9bd5..ce188736 100644 --- a/rules/CVI-230001.xml +++ b/rules/CVI-230001.xml @@ -3,7 +3,7 @@ - + ## 安全风险 diff --git a/rules/CVI-330001.xml b/rules/CVI-355001.xml similarity index 100% rename from rules/CVI-330001.xml rename to rules/CVI-355001.xml diff --git a/rules/CVI-330002.xml b/rules/CVI-355002.xml similarity index 100% rename from rules/CVI-330002.xml rename to rules/CVI-355002.xml diff --git a/rules/vulnerabilities.xml b/rules/vulnerabilities.xml index b17519bd..898bda23 100644 --- a/rules/vulnerabilities.xml +++ b/rules/vulnerabilities.xml @@ -7,13 +7,24 @@ - - - - - + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/vulnerabilities/v.ini b/tests/vulnerabilities/v.ini new file mode 100644 index 00000000..ad1b912d --- /dev/null +++ b/tests/vulnerabilities/v.ini @@ -0,0 +1,2 @@ +password: 123@123 +db_query_password=!@#1qa123 \ No newline at end of file diff --git a/tests/vulnerabilities/v.php b/tests/vulnerabilities/v.php index 100aa9bb..f326bd01 100644 --- a/tests/vulnerabilities/v.php +++ b/tests/vulnerabilities/v.php @@ -1,57 +1,36 @@ - * @link https://github.com/wufeifei/cobra - */ $username = $_POST['username']; $password = $_POST['password']; $callback = $_POST['callback']; -# CVI-130005 $target = "10.11.2.220"; -$cmd = $_REQUEST['a'] +$cmd = $_REQUEST['a']; -print($callback); +echo($callback . ";"); -# CVI-210001 -if (isset($_GET['url']) { - header("Location: ".$_GET["url"]); -} +extract($cmd); -# CVI-180001 @array_map("ass\x65rt",(array)@$cmd); -$a = base64_decode($_POST['test']); -eval($a); - -# CVI-181001 $cmd = $_GET['cmd']; -system('ls' + $cmd); -ssh2_exec($connection, '$_GET['pass']') +if (!empty($cmd)){ + eval($cmd); + system('ls' + $cmd); +} -# CVI-230001 -if (isset($_GET['sid']) { - setcookie("PHPSESSID", $_GET["sid"]); +if (isset($_GET['sid'])) { + setcookie("PHPSESSID", $cmd); } -# CVI-190003 phpinfo(); -# CVI-190008 -print_r($a); - -# CVI-110001 -if(!file_exists('log/'.date("Y"))) +if(!empty($url)) { mkdir('log/'.date("Y"),0777); } -chmod("move.php", 0777); -# CVI-120001 function curl($url){ $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); @@ -61,65 +40,63 @@ function curl($url){ } $url = $_GET['url']; -curl($url); +if (!empty($url)){ + curl($cmd); +} -# CVI-120002 $url = $_GET['url']; -$content = file_get_contents($url) -# CVI-120003 -$url = $_GET["url"]; -echo get_headers($url,1); +if (!empty($url)){ + $content = file_get_contents($url); +} -# CVI-140003 -print_r ("Hello " . $_GET['name']); +$url = $_GET["url"]; +if (!empty($url)){ + echo get_headers($url,1); +} -# CVI-140004 -echo "Hello " . $_GET['name']; +print("Hello " . $cmd); -# CVI-160002 CVI-160003 -$query = "SELECT * FROM users WHERE user = $username AND password = $password;"; +$query = "SELECT id, name, inserted, size FROM products WHERE size = '$size' ORDER BY $order LIMIT $limit, $offset;"; mysql_query($query); +mysqli_query($query); -# CVI-160002 CVI-160004 -$query = "SELECT id, name, inserted, size FROM products WHERE size = '$size' ORDER BY $order LIMIT $limit, $offset;"; -$result = odbc_exec($conn, $query); -# CVI-170002 -require_once($_GET['file']); +if(!empty($cmd)){ + require_once($cmd); +} + +highlight_file($cmd); -# CVI-200002 $unique = uniqid(); -# CVI-130002 $appKey = "C787AFE9D9E86A6A6C78ACE99CA778EE"; -# CVI-130001 $password = "cobra123456!@#"; -# CVI-210001 -header("Location: ".$_GET["url"]); +$url = $_GET["url"]; +if (!empty($url)) { + header("Location: ".$url); +} -# CVI-260001 $test = $_POST['test']; $test_uns = unserialize($test); -# CVI-270001 $xml = $_POST['xml']; $data = simplexml_load_string($xml); -# CVI-320001 parse_str($_SERVER['QUERY_STRING']); -# CVI-320002 $a = '0'; -extract($_GET); + if($a==1){ echo "true!"; }else{ echo "false!"; } -# CVI-350001 $file = $_POST["file_name"]; -unlink($file); \ No newline at end of file +if (!empty($file)){ + unlink($file); +} +