├── README.md ├── config ├── config.ini └── sysrule.json ├── img ├── 1.png ├── 2.png ├── 3.png └── 4.png ├── lib ├── __pycache__ │ ├── req.cpython-39.pyc │ └── util.cpython-39.pyc ├── req.py └── util.py └── xazlscan.py /README.md: -------------------------------------------------------------------------------- 1 | 一键检测系统历史漏洞,信安之路POC管理平台配套自动化工具,支持单网站检测以及指定目标文件两种方式,所需POC一次解锁,终身可用,效果如图: 2 | 3 | 1、单网站检测 4 | 5 | ![](img/1.png) 6 | 7 | 2、多网站检测 8 | 9 | ![](img/2.png) 10 | 11 | 首次使用该工具,需要注册信安之路 POC 管理平台,并生成属于自己的 Token,然后修改配置文件为自己的邮箱和Token。 12 | 13 | 1、注册地址:https://www.xazlsec.com 14 | 15 | 2、前往用户中心-》个人管理 https://www.xazlsec.com/user_center/# 16 | 17 | ![](img/3.png) 18 | 19 | 3、修改配置文件:config/config.ini 20 | 21 | ![](img/4.png) 22 | 23 | 4、下载 nuclei 工具至 bin 目录,给 nuclei 增加可执行权限后方可使用,没有bin 目录可以自己创建 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /config/config.ini: -------------------------------------------------------------------------------- 1 | 2 | [login] 3 | email=admin@xazlscan.com 4 | token=vdNBfhdxsJeKBFpF4aERJOGEWd5eZZ -------------------------------------------------------------------------------- /img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myh0st/xazlscan/87aa0755acb28032f86f6747eba4e6693acc0613/img/1.png -------------------------------------------------------------------------------- /img/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myh0st/xazlscan/87aa0755acb28032f86f6747eba4e6693acc0613/img/2.png -------------------------------------------------------------------------------- /img/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myh0st/xazlscan/87aa0755acb28032f86f6747eba4e6693acc0613/img/3.png -------------------------------------------------------------------------------- /img/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myh0st/xazlscan/87aa0755acb28032f86f6747eba4e6693acc0613/img/4.png -------------------------------------------------------------------------------- /lib/__pycache__/req.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myh0st/xazlscan/87aa0755acb28032f86f6747eba4e6693acc0613/lib/__pycache__/req.cpython-39.pyc -------------------------------------------------------------------------------- /lib/__pycache__/util.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myh0st/xazlscan/87aa0755acb28032f86f6747eba4e6693acc0613/lib/__pycache__/util.cpython-39.pyc -------------------------------------------------------------------------------- /lib/req.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import re 4 | import os 5 | import sys 6 | import json 7 | import hashlib 8 | import base64 9 | import zipfile 10 | import time 11 | import random 12 | import shutil 13 | import subprocess 14 | import ssl 15 | import requests 16 | import warnings 17 | from gzip import GzipFile 18 | from lib.util import judgeRuleTime 19 | 20 | try: 21 | from StringIO import StringIO 22 | readBytesCustom = StringIO 23 | except ImportError: 24 | from io import BytesIO 25 | readBytesCustom = BytesIO 26 | 27 | try: 28 | from urllib.request import Request, urlopen 29 | except ImportError: 30 | from urllib2 import Request, urlopen 31 | 32 | 33 | warnings.filterwarnings("ignore") 34 | 35 | #检查 API 接口是否可以正常访问 36 | def check_api_alive(): 37 | apiPath = ["https://www.xazlsec.com/api/", "http://api.xazlsec.com/"] 38 | for api in apiPath: 39 | try: 40 | requests.get(api, timeout=10) 41 | return api 42 | except: 43 | pass 44 | return "" 45 | 46 | #检查单条规则 47 | def check_rule(rule, site_info): 48 | title = site_info[0] 49 | header = site_info[1] 50 | server = site_info[2] 51 | body = site_info[3] 52 | 53 | #print(rule, title, header) 54 | for r in rule: 55 | try: 56 | func = r['match'] 57 | except: 58 | print(r) 59 | content = r['content'].lower() 60 | #if content == "cyberpanel": 61 | # print(func, content, header) 62 | #sys.exit() 63 | if func == 'header_contains': 64 | if content not in header: 65 | return False 66 | elif func == 'body_contains': 67 | if content not in body: 68 | return False 69 | elif func == 'server_contains': 70 | if content not in server: 71 | return False 72 | elif func == 'title_contains': 73 | if content not in title: 74 | return False 75 | else: 76 | return False 77 | return True 78 | 79 | #获取网站信息, 输出规则检查所需内容 80 | def get_headers(): 81 | """ 82 | 生成伪造请求头 83 | """ 84 | user_agents = [ 85 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' 86 | '(KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36', 87 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 ' 88 | '(KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36', 89 | 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 ' 90 | '(KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36', 91 | 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:54.0) Gecko/20100101 Firefox/68.0', 92 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:61.0) ' 93 | 'Gecko/20100101 Firefox/68.0', 94 | 'Mozilla/5.0 (X11; Linux i586; rv:31.0) Gecko/20100101 Firefox/68.0'] 95 | ua = random.choice(user_agents) 96 | headers = { 97 | 'Accept': 'text/html,application/xhtml+xml,' 98 | 'application/xml;q=0.9,*/*;q=0.8', 99 | 'Accept-Language': 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7', 100 | 'Cache-Control': 'max-age=0', 101 | 'Connection': 'keep-alive', 102 | 'DNT': '1', 103 | 'Upgrade-Insecure-Requests': '1', 104 | 'User-Agent': ua, 105 | } 106 | return headers 107 | 108 | def get_response(url, response, ignore=False): 109 | if ignore: 110 | html = "" 111 | size = response.headers.get("content-length", default=1000) 112 | else: 113 | response.encoding = response.apparent_encoding if response.encoding == 'ISO-8859-1' else response.encoding 114 | response.encoding = "utf-8" if response.encoding is None else response.encoding 115 | html = response.content.decode(response.encoding,"ignore") 116 | size = len(response.text) 117 | return response.status_code, response.headers, html 118 | 119 | def send_request(url): 120 | ''' 121 | Send requests with Requests 122 | ''' 123 | try: 124 | #proxies = { "http": "127.0.0.1:8080","https": "127.0.0.1:8080"} 125 | with requests.get(url, timeout=60, headers=get_headers(), verify=False, 126 | allow_redirects=True) as response: 127 | if int(response.headers.get("content-length", default=1000)) > 200000: 128 | code, rep_headers, body = get_response(url, response, True) 129 | else: 130 | code, rep_headers, body = get_response(url, response) 131 | except KeyboardInterrupt: 132 | print("用户强制程序,系统中止!") 133 | exit(0) 134 | except Exception as e: 135 | #print(e) 136 | return "", 0, "", "", "" 137 | 138 | server = "" 139 | if "Server" in rep_headers: 140 | server = rep_headers["Server"] 141 | 142 | title = "" 143 | titles=re.findall(r"<\s*title\s*>\s*([^<]+)\s*<\s*\/title\s*>",body, re.I) 144 | if len(titles) != 0: 145 | title = titles[0] 146 | 147 | 148 | return title.lower(), code, server.lower(), str(rep_headers).lower(), body.lower() 149 | 150 | #获取网站信息并识别指纹 151 | def get_site_info(website, sytem_rules): 152 | title, status, server, header, body = send_request(website) 153 | site_info = [title, header, server, body] 154 | n = 0 155 | systemlist = [] 156 | for sid in sytem_rules: 157 | flag = False 158 | for rule in sytem_rules[sid]["rules"]: 159 | if check_rule(rule, site_info): 160 | flag = True 161 | break 162 | if flag == True: 163 | systemlist.append(sid) 164 | 165 | return systemlist 166 | 167 | #初始化规则库 168 | def initSysRules(apiPath, email, token): 169 | url = apiPath + "get_sysrules/?token={}&email={}".format(token, email) 170 | rulepath = "config/sysrule.json" 171 | if not os.path.exists(rulepath) or not judgeRuleTime(rulepath): 172 | sysinfo = requests.get(url, timeout=60).json() 173 | open(rulepath, "w", encoding="utf-8").write(json.dumps(sysinfo)) 174 | return sysinfo 175 | 176 | #下载POC到本地 177 | def downloadPoc(apiPath, savefile, pocuuid, email, token): 178 | url = apiPath + "download_poc/?token={}&id={}&email={}".format(token, pocuuid, email) 179 | 180 | pocdata = requests.get(url) 181 | 182 | try: 183 | info = pocdata.json() 184 | return info["msg"] 185 | except: 186 | pass 187 | 188 | open(savefile, "wb").write(pocdata.content) 189 | 190 | #搜索POC信息 191 | def searchPoc(apiPath, sid, token, email): 192 | url = apiPath + "search_poc/?token={}&sid={}&email={}".format(token, sid, email) 193 | jsondata = requests.get(url).json() 194 | return jsondata 195 | 196 | #购买POC 197 | def buyPoc(apiPath, sid, token, email): 198 | url = apiPath + "buy_poc/?token={}&sid={}&email={}".format(token, sid, email) 199 | jsondata = requests.get(url).json() 200 | return jsondata 201 | 202 | #检查Token是否有效 203 | def checkToken(apiPath, token, email): 204 | url = apiPath + "check_token/?token={}&email={}".format(token, email) 205 | jsondata = requests.get(url).json() 206 | if jsondata["status"] == "false": 207 | return False 208 | 209 | return jsondata["uid"] 210 | -------------------------------------------------------------------------------- /lib/util.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | import json 6 | import time 7 | import uuid 8 | import subprocess 9 | from configparser import ConfigParser 10 | 11 | respath = "res/" 12 | vulnpath = "vulns/" 13 | 14 | #获取配置中的token和email 15 | def getTokenOrEmail(): 16 | # 创建解析器实例 17 | config = ConfigParser() 18 | # 读取配置文件 19 | config.read('config/config.ini') 20 | 21 | # 获取信息 22 | email = config.get('login', 'email') 23 | token = config.get('login', 'token') 24 | return email, token 25 | 26 | #判断规则库的有效期 27 | def judgeRuleTime(rulepath): 28 | if not os.path.exists(rulepath): 29 | return False 30 | sysinfo = json.loads(open(rulepath, encoding="utf-8").read()) 31 | downtime = sysinfo["timestamp"] 32 | nowtime = time.time() 33 | if int(nowtime) - int(downtime) > 24 * 60 * 60: 34 | return False 35 | return True 36 | 37 | #执行系统命令 38 | def execCmd(cmdline): 39 | rsp = subprocess.Popen(cmdline, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 40 | output, error = rsp.communicate() 41 | return output, error 42 | 43 | #读取文件内容 44 | def readFile(filepath): 45 | return open(filepath, encoding="utf-8").read() 46 | 47 | #检查临时目录是否存在,并新建目录 48 | def getTmpUuid(): 49 | uinfo = str(uuid.uuid4()) 50 | 51 | if not os.path.exists(respath): 52 | os.mkdir(respath) 53 | if not os.path.exists(respath+uinfo): 54 | os.mkdir(respath+uinfo) 55 | 56 | if not os.path.exists(vulnpath): 57 | os.mkdir(vulnpath) 58 | if not os.path.exists(vulnpath+uinfo): 59 | os.mkdir(vulnpath+uinfo) 60 | 61 | return uinfo 62 | 63 | #保存文件内容 64 | def saveFile(savefile, info): 65 | obj = open(savefile, "a+", encoding="utf-8") 66 | obj.writelines(info+"\n") 67 | obj.close() 68 | 69 | #获取网页跟目录 70 | def getRootSite(url): 71 | return "/".join(url.split("/")[:3]) 72 | -------------------------------------------------------------------------------- /xazlscan.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | import click 6 | import threading 7 | import queue 8 | from lib.req import * 9 | from lib.util import * 10 | 11 | respath = "res/" 12 | vulnpath = "vulns/" 13 | pocpath = "pocdb/" 14 | if sys.platform == 'win32': 15 | nucleipath = "bin\\nuclei.exe" 16 | else: 17 | nucleipath = "bin/nuclei" 18 | rulepath = "config/sysrule.json" 19 | inputQueue = queue.Queue() 20 | num = 0 21 | length = 0 22 | sysrules = {} 23 | 24 | 25 | #基于SID获取相关信息,根据用户选择同步 POC 到本地 26 | def getPocToLocal(apiPath, sid, token, email): 27 | pocinfo = searchPoc(apiPath, sid, token, email) 28 | if pocinfo["status"] == "false": 29 | print("[-]查询POC信息失败,错误提示:", pocinfo["msg"]) 30 | return False 31 | 32 | if pocinfo["pnum"] != 0: 33 | print("[+]SID: ", sid," 系统信息:", pocinfo["sysinfo"]) 34 | else: 35 | return False 36 | 37 | newpoc = pocinfo["pnum"]-pocinfo["bnum"] 38 | if newpoc > 0: 39 | print("[+]积分信息,剩余积分:", pocinfo["score"], "本次扫描需要消耗积分:", newpoc * 5) 40 | select = input("[+]是否确定要消耗积分并执行漏洞探测?(0 否)") 41 | if select == "0": 42 | return False 43 | 44 | #获取已经购买的 POC 信息,如果未购买,则自动购买 45 | buyinfo = buyPoc(apiPath, sid, token, email) 46 | if buyinfo["status"] == "false": 47 | print("[-]POC购买失败,错误提示:", buyinfo["msg"]) 48 | return False 49 | else: 50 | print("[+]购买成功,剩余积分:", buyinfo["score"]) 51 | 52 | #打印POC相关信息 53 | poclist = buyinfo["poclist"] 54 | if len(poclist) != 0: 55 | print("[+]系统收录该系统的 POC 数量为: ", len(poclist)) 56 | for item in poclist: 57 | print("[*]", item[1], item[2], item[3]) 58 | 59 | if not os.path.exists(pocpath): 60 | os.mkdir(pocpath) 61 | 62 | savepath = pocpath + str(sid) 63 | if not os.path.exists(savepath): 64 | os.mkdir(savepath) 65 | 66 | #同步下载POC到本地 67 | for poc in poclist: 68 | puuid = poc[0] 69 | pname = poc[1] 70 | savefile = savepath + "/" + pname + ".yaml" 71 | if not os.path.exists(savefile): 72 | print("[+]正在下载POC:", savefile) 73 | downloadPoc(apiPath, savefile, puuid, email, token) 74 | return True 75 | 76 | #检测基础环境 77 | def checkBase(email, token): 78 | #检查 API 接口是否可以正常访问 79 | apiPath = check_api_alive() 80 | if apiPath == "": 81 | return False 82 | print("[+]当前使用的 API 接口地址为:", apiPath) 83 | #首先检查用户token是否有效 84 | if not checkToken(apiPath, token, email): 85 | print("[-]Token 无效,请前往 https://www.xazlsec.com 的个人资料中查看有效和 token 并更新配置文件") 86 | return False 87 | #检查 nuclei 是否安装 88 | if not os.path.exists(nucleipath): 89 | print("[-]nuclei 未下载至 bin 目录,无法使用漏洞探测,请下载 nuclei 到指定目录") 90 | return False 91 | 92 | return apiPath 93 | 94 | #针对单个网站进行指纹识别以及漏洞探测 95 | def scanSingleSite(target): 96 | email, token = getTokenOrEmail() 97 | 98 | apiPath = checkBase(email, token) 99 | if not apiPath: 100 | print("[-]Api 地址无法访问,您的 IP 已经被腾讯云、阿里云拉黑!") 101 | sys.exit() 102 | 103 | #第一步,初始化指纹库,并读取指纹规则 104 | if not judgeRuleTime(rulepath): 105 | sysRules = initSysRules(apiPath, email, token) 106 | else: 107 | sysRules = json.loads(open(rulepath, encoding="utf-8").read()) 108 | 109 | #第二步,获取网页信息并识别指纹 110 | rootsite = getRootSite(target) 111 | sysList = get_site_info(rootsite, sysRules["system_rules"]) 112 | uinfo = getTmpUuid() 113 | print("临时目录名:", uinfo, "指纹识别结果:", sysList) 114 | #判断是否识别为蜜罐,指纹识别结果超过阈值,或者命中蜜罐规则 5998 115 | if len(sysList) >= 10 or 5998 in sysList: 116 | print("[-]该网站识别结果超过 10 个,疑似蜜罐,漏洞探测程序退出!") 117 | sys.exit() 118 | 119 | #第三步,根据获取到的系统列表,判断用户是否有足够的积分购买POC 120 | for sid in sysList: 121 | if not getPocToLocal(apiPath, sid, token, email): 122 | continue 123 | 124 | scanflag = input("[+]是否检测该系统(0 不检测)?") 125 | if scanflag == "0": 126 | continue 127 | 128 | vulnfile = vulnpath + uinfo + "/" + str(sid) + ".txt" 129 | if sys.platform == 'win32': 130 | cmd = nucleipath + " -duc -t " + pocpath + str(sid) + "/ -u " + rootsite + " -o " + vulnfile 131 | else: 132 | cmd = "./" + nucleipath + " -duc -t " + pocpath + str(sid) + "/ -u " + rootsite + " -o " + vulnfile 133 | #print(cmd) 134 | execCmd(cmd) 135 | 136 | #读取漏洞文件,获取漏洞结果 137 | print("[+]漏洞检测结果如下:") 138 | for filename in os.listdir(vulnpath + uinfo): 139 | print(readFile(vulnpath + uinfo + "/" + filename)) 140 | 141 | 142 | #针对多个网站进行指纹识别及漏洞探测,非多线程版 143 | def auto_get(): 144 | global sysrules 145 | global length 146 | global num 147 | while True: 148 | num = num + 1 149 | rootsite = inputQueue.get() 150 | syslist = get_site_info(rootsite, sysrules) 151 | 152 | #判断是否识别为蜜罐,指纹识别结果超过阈值,或者命中蜜罐规则 5998 153 | if len(syslist) >= 10 or 5998 in syslist: 154 | continue 155 | 156 | #print(sysrules) 157 | for sid in syslist: 158 | saveFile(respath+uinfo+"/"+str(sid)+".txt", rootsite) 159 | if num % 100 == 0: 160 | print("[+]正在指纹识别网站:", rootsite, "网站总数:", length, "当前进度:", num) 161 | 162 | 163 | #根据用户指定的目录进行漏洞扫描, 据获取到的所有系统类型,一一计算需要消耗的积分数 164 | def nuclei_scan(apiPath, uinfo, token, email): 165 | filepath = respath + uinfo 166 | for sfile in os.listdir(filepath): 167 | sid = sfile.split(".")[0] 168 | 169 | #同步 POC 到本地 170 | if not getPocToLocal(apiPath, sid, token, email): 171 | continue 172 | 173 | #询问用户是否进行漏洞探测,根据系统来选择 174 | scanflag = input("[+]是否检测该系统(0 不检测)?") 175 | if scanflag == "0": 176 | continue 177 | 178 | vulnfile = filepath + "/" + str(sid) + ".txt" 179 | if sys.platform == 'win32': 180 | cmd = nucleipath + " -duc -t " + pocpath + str(sid) + "/ -l " + respath + uinfo + "/" + sfile + " -o " + vulnfile 181 | else: 182 | cmd = "./" + nucleipath + " -duc -t " + pocpath + str(sid) + "/ -l " + respath + uinfo + "/" + sfile + " -o " + vulnfile 183 | execCmd(cmd) 184 | 185 | #最后,将所有漏洞检测的结果进行展示 186 | #读取漏洞文件,获取漏洞结果 187 | print("[+]漏洞检测结果如下:") 188 | for filename in os.listdir(vulnpath + uinfo): 189 | print(readFile(vulnpath + uinfo + "/" + filename)) 190 | 191 | 192 | 193 | def scanSiteFile(tfile, thread=30): 194 | global length 195 | global sysrules 196 | global uinfo 197 | email, token = getTokenOrEmail() 198 | apiPath = checkBase(email, token) 199 | if not apiPath: 200 | print("[-]Api 地址无法访问,您的 IP 已经被腾讯云、阿里云拉黑!") 201 | sys.exit() 202 | 203 | if not os.path.exists(tfile): 204 | print("[-]指定文件不存在,请确保指定的文件路径正确!") 205 | sys.exit() 206 | 207 | #第一步,初始化指纹库,并读取指纹规则 208 | if not judgeRuleTime(rulepath): 209 | sysRules = initSysRules(apiPath, email, token) 210 | else: 211 | sysRules = json.loads(open(rulepath, encoding="utf-8").read()) 212 | 213 | #第二步,获取网站信息并识别指纹,将结果保存在临时目录下 214 | uinfo = getTmpUuid() 215 | #启用多线程,创建线程池 216 | for x in range(int(thread)): 217 | t = threading.Thread(target=auto_get) 218 | t.setDaemon(True) 219 | t.start() 220 | 221 | length = len(list(open(tfile, encoding="utf-8"))) 222 | sysrules = sysRules["system_rules"] 223 | sitelist = [] 224 | for site in open(tfile, encoding="utf-8"): 225 | rootsite = getRootSite(site.strip()) 226 | if rootsite not in sitelist: 227 | inputQueue.put(rootsite) 228 | 229 | while True: 230 | if inputQueue.qsize() <= 1000: 231 | break 232 | else: 233 | time.sleep(5) 234 | 235 | while True: 236 | time.sleep(5) 237 | if inputQueue.qsize() <= 1: 238 | break 239 | #多线程部分结束 240 | 241 | #调用漏洞扫描模块 242 | nuclei_scan(apiPath, uinfo, token, email) 243 | 244 | @click.command() 245 | @click.option("-u", "--target", help="指定需要检测的目标地址") 246 | @click.option("-f", "--tfile", help="指定需要检测的目标文件路径") 247 | @click.option("-t", "--thread", help="指定指纹识别时使用的线程数默认30") 248 | @click.option("-s", "--uuid", help="指定要进行漏洞扫描的临时目录,也就是指纹识别结果所保存的目录名") 249 | 250 | def main(uuid, target, tfile, thread): 251 | if uuid: 252 | email, token = getTokenOrEmail() 253 | apiPath = checkBase(email, token) 254 | if not apiPath: 255 | print("[-]Api 地址无法访问,您的 IP 已经被腾讯云、阿里云拉黑!") 256 | sys.exit() 257 | nuclei_scan(apiPath, uuid, token, email) 258 | 259 | if target: 260 | scanSingleSite(target) 261 | 262 | if tfile: 263 | if thread: 264 | scanSiteFile(tfile, thread) 265 | else: 266 | scanSiteFile(tfile) 267 | 268 | 269 | if __name__=="__main__": 270 | main() 271 | --------------------------------------------------------------------------------