Life is short. You need z0scan.
介绍#

z0scan(简称z0)是一款基于Python开发的Web漏洞扫描工具,兼具主动和被动扫描能力。
该项目摒弃了冗杂的组件及框架POC,专注于Web应用和常见服务漏洞检测,采用模块化设计,注重实际扫描效率与边缘类冷门漏洞的发现。
z0 的主要特点:
智能化的扫描策略
- 集成WAF识别和指纹检测功能,可根据目标特征动态调整扫描策略
- 通过优化请求频率和检测逻辑,尽可能减少对目标系统业务的干扰
全面的参数解析能力
- 支持解析JSON、XML等结构化数据中的参数
- 具备伪静态URL参数识别功能,扩展了漏洞检测范围
便捷的数据管理
- 采用SQLite3数据库存储扫描记录,便于结果管理和去重处理
- 数据默认存储在data/z0scan.db文件中
良好的跨平台支持
- 基于Python3开发,支持主流操作系统
- 可在Termux等移动端环境中运行
安装#
通用安装#
通过Pypi安装
pip install z0scan
z0 version
通过GitHub克隆安装
git clone https://github.com/JiuZero/z0scan
cd z0scan
pip install -r requirements.txt
python3 z0.py version
部分特殊环境#
Termux
apt install python-cryptography python-lxml
pip install z0scan
z0 version
iSH(未调试)
建议在M系芯片环境中运行
apk add py3-cryptography py3-lxml py3-certifi py3-six py3-idna py3-certifi py3-cffi
pip install z0scan
z0 version
使用#
参数#
version#
参数 | 输入值示例 | 说明 |
---|---|---|
/ | / | 显示版本信息 |
scan#
参数 | 输入值示例 | 说明 |
---|---|---|
-h, –help | - | 显示帮助信息 |
-s, –server-addr | 127.0.0.1:5920 | 服务器地址格式(ip:port) |
-u, –url | http://example.com | 目标URL |
-f, –file | urls.txt | 批量扫描URL文件 |
-p, –proxy | http@127.0.0.1:8080 | 代理设置(支持http/https/socks5/socks4) |
–timeout | 10 | 连接超时时间(默认6秒) |
–retry | 3 | 超时重试次数(默认2次) |
–random-agent | - | 使用随机User-Agent |
–html | - | 输出HTML报告到默认目录 |
–json | report.json | 指定JSON报告输出路径 |
-t, –threads | 50 | 最大并发请求数(默认31) |
-l, –level | 2 | 检测级别(0-3,默认2) |
-r, –risk | “0,1,2” | 风险等级检测范围(默认[0,1,2]) |
-c, –concise | - | 简洁输出模式 |
-iw, –ignore-waf | - | 忽略WAF检测 |
-sf, –skip-fingerprint | - | 跳过指纹识别 |
-sc, –scan-cookie | - | 启用Cookie扫描 |
–disable | “xss,sqli” | 禁用指定扫描器 |
–able | “rce,ssrf” | 仅启用指定扫描器 |
–debug | 2 | 异常显示级别(1-3) |
被动扫描#
默认配置下被动扫描
(浏览器转发流量到5920端口)
z0 scan -s 127.0.0.1:5920
常用推荐
z0 scan -s 127.0.0.1:5920 --risk 0,1,2,3 --level 2 --disable cmdi,unauth
主动扫描#
默认配置下主动扫描
# 由Burp/Yakit发起请求流量的主动检测(推荐)
z0 scan -s 127.0.0.1:5920

# 直接检测
z0 scan -u https://example.com/?id=1
# 遍历URL列表检测
z0 scan -f urls.txt
报告#
报告展示#


插件#
插件列表#
- PerFile
插件名称 | 插件简述 | Risk |
---|---|---|
sqli-bool | SQL布尔盲注检测 | 2 |
sqli-time | SQL时间盲注检测 | 2 |
sqli-error | SQL报错注入检测 | 2 |
codei-asp | Asp代码执行 | 3 |
codei-php | Php代码执行 | 3 |
cmdi | 命令执行 | 3 |
other-objectdese | 反序列参数分析 | 3 |
sensi-js | Js敏感信息泄露 | 0 |
sensi-jsonp | Jsonp敏感信息泄露 | 1 |
sensi-php-realpath | Php真实目录发现 | 0 |
redirect | 重定向 | 1 |
sensi-webpack | webpack源码泄露 | 1 |
other-webdav-passive | webdav服务被动发现 | 1 |
xpathi-error | 基于报错的XPATH注入检测 | 2 |
trave-path | 路径穿越 | 2 |
sensi-backup_1 | 基于文件的备份文件检测 | 1 |
sensi-viewstate | 未加密的VIEWSTATE发现 | 0 |
xss | 基于JS语义的XSS扫描 | 1 |
crlf_1 | CRLF漏洞检测 | 2 |
cors-passive | CORS漏洞检测(被动分析) | 2 |
unauth | 未授权访问漏洞 | 2 |
leakpwd-page-passive | 后台登陆页弱口令 | 2 |
sensi-editfile | 编辑器备份文件泄露 | 1 |
sensi-sourcecode | 源码泄露 | 1 |
captcha-bypass | 验证码绕过 | 0 |
- PerFolder
插件名称 | 插件简述 | Risk |
---|---|---|
sensi-backup_2 | 基于各级目录的备份文件扫描 | 1 |
trave-list | 目录浏览 | 2 |
sensi-repository | 仓库源码泄漏 | 1 |
sensi-php-phpinfo | Phpinfo文件发现 | 0 |
upload-oss | OSS储存桶任意文件上传 | 2 |
- PerServer
插件名称 | 插件简述 | Risk |
---|---|---|
sensi-errorpage | 错误页敏感信息泄露 | 0 |
xss-net | .NET通杀XSS | 1 |
other-dns-zonetransfer | DNS域传送漏洞 | 1 |
xss-flash | Flash通杀XSS | 1 |
other-idea-parse | Idea目录解析 | 1 |
other-xst | XST漏洞检测 | -1 |
other-webdav-active | webdav服务发现 | 1 |
upload-put | 基于PUT请求的任意文件上传 | 3 |
sensi-backup_3 | 基于域名的备份文件检测 | 1 |
cors-active | CORS漏洞检测(主动发现) | 2 |
crlf_3 | CRLF换行注入漏洞 | 2 |
other-hosti | Host头注入攻击检测 | 1 |
other-oss-takeover | OSS储存桶接管漏洞 | 3 |
sensi-iis-shortname | IIS短文件名漏洞 | 0 |
插件示例&计划(部分)#
示例目标均为内网/外网公开靶场。
sqli-bool#

sqli-error#

sqli-time#

- SQLite的延时检测支持
xss#

- 部分情况下提供的Payload不可用
- 部分情况下无法检测出漏洞
leakpwd-page-passive#

sensi-viewstate#

trave-path#

sensi-repository#

sensi-js#

captcha-bypass#

codei-php#

redirect#

trave-list#

crlf_1#

扫描插件编写#
命名#
插件名称按照以下命名规则命名:
漏洞类型(简写) + 指纹 + 简述
如PerFile/PerFolder/PerServer下存在同名插件,在单个插件名后按照PerFile:1,PerFolder:2,PerServer:3
在名末下划线连接,如:
PerFile中的sensi-backup:sensi-backup_1
漏洞类型分类与简写形式见api/VulType
部分
内置模块#
api#
名称 | 描述 | 使用 | 必选/可选 |
---|---|---|---|
generateResponse | 用于报告中响应体的生成 | generateResponse(r) # r=request.get(…) | 必选 |
random_num | 生成随机数 | / | 可选 |
random_str | 生成随机字符串 | / | 可选 |
VulType | 对漏洞类型的选定 | 见api/VulType | 必选 |
Type | 对扫描类型的选定 | 见api/Type | 必选 |
PluginBase | 被继承以获取关键数据 | class Z0SCAN(PluginBase) | 必选 |
conf | 储存一些命令行参数值 | 主要使用level,见api/conf.level | 必选 |
PLACE | 对漏洞注入点(请求中的可控点)的选定 | 见api/PLACE | 必选 |
Threads | 插件内置线程(针对参数的多线程) | 见api/Threads | 可选 |
- api/VulType:
名称 | 描述 | (插件命名)简写 |
---|---|---|
CMD_INNJECTION | 命令注入漏洞 | cmdi |
CODE_INJECTION | 代码注入漏洞 | codei |
XSS | XSS跨站脚本攻击 | xss |
SQLI | SQL注入漏洞 | sqli |
TRAVERSAL | 遍历漏洞 | trave |
XXE | XML外部实体注入 | xxe |
SSRF | 服务器端请求伪造 | ssrf |
CSRF | CSRF | csrf |
REDIRECT | 重定向漏洞 | redirect |
WEAK_PASSWORD | 弱口令 | weakpwd |
CRLF | 换行注入 | crlf |
SENSITIVE | 敏感信息泄露漏洞 | sensi |
SSTI | 服务器端模板注入 | ssti |
UNAUTH | 未授权访问 | unauth |
FILEUPLOAD | 文件上传 | upload |
CORS | CORS漏洞 | cors |
OTHER | 其它漏洞 | other |
- api/Type :
名称 | 描述 |
---|---|
ANALYZE | 被动分析发现 |
REQUEST | 主动请求发现 |
- api/conf.level :
扫描深度(反映请求量)
值 | 描述 |
---|---|
0 | 纯被动分析模式,不做任何请求 |
1 | 最低请求量的扫描,最低的业务影响 |
2 | 中等请求量的扫描,Payload多为通用Top5 |
3 | 大量请求扫描,Payload覆盖面更广 |
- api/conf.risk :
需要扫描的漏洞危害程度
值 | 描述 |
---|---|
-1 | 难以利用的极低危常见漏洞 |
0 | 可能产生1~3级危害的辅助性信息 |
1 | 低危漏洞 |
2 | 中危漏洞 |
3 | 高危漏洞 |
- api/PLACE :
名称 | 描述 |
---|---|
PARAM | URL后参数部分 |
DATA | 在BODY中传递的参数 |
COOKIE | COOKIE中传递的参数 |
URL | 伪静态参数 |
NORMAL_DATA | 常规POST传参格式中的参数 |
JSON_DATA | JSON格式中的参数 |
XML_DATA | XML格式中的参数 |
MULTIPART_DATA | MULTIPART格式中的参数 |
ARRAY_LIKE_DATA | ARRAY_LIKE格式中的参数 |
SOAP_DATA | SOAP_DATA格式中的参数 |
- api/Threads :
z0thread = Threads(name="sqli-error") # name : 插件名
z0thread.submit(task_func, task_data, args, kwargs)
# task_func: 要执行的任务函数
# task_data: 任务数据迭代器,每个元素会作为task_func的第一个参数
# args: 传递给task_func的额外位置参数
# kwargs: 传递给task_func的额外关键字参数 (可选)
解析数据#
注:继承PluginBase后读取
- self.fingerprints :
名称 | 类型 | 描述 |
---|---|---|
os | dict | OS系统指纹 |
programing | dict | 项目类型 |
waf | str | WAF名称(未检测到WAF时为None) |
对于os、webserver、programing
:
>> print(self.fingerprints.programing)
{"PHP" : "1.9.6"} # 名称 : 版本信息
- self.requests :
名称 | 类型 | 描述 | 示例 |
---|---|---|---|
url | str | 完整的URL(包含GET参数) | https://www.example.com:443/a/file.php?id=1 |
suffix | str | 文件后缀 | .php |
scheme | str | 请求协议 | http |
port | int | 服务端口 | 8888 |
host | str | 域名(不包括端口) | www.myscantest.com |
netloc | str | 包含协议与端口信息的域名 | https://www.example.com:443 |
raw | str | 原始的请求包 | / |
method | str | 请求方法 | GET |
headers | dict | 请求头字典 | / |
cookies | dict | COOKIE | / |
params | dict | 在URL中包含的参数 | {‘id’: ‘1’} |
post_hint | str | POST请求包类型 | / |
datas | dict | POST数据 | / |
data | str | 原始请求头 | / |
注: 仅常规解析,datas、params均允许直接requests构造请求
如需解析伪静态及xml等格式请求中的参数,见self.generateItemdatas()
- self.response :
名称 | 类型 | 描述 | 示例 |
---|---|---|---|
status_code | int | 返回状态码 | 200 |
content | byte | 返回字节类型 | / |
headers | dict | 请求头 | / |
raw | str | 原始的返回包 | / |
text | str | 返回的文本 | / |
- self.generateItemdatas() :
generateItemdatas()会将参数名、参数值及其所处的可控点整理后返回
注意: 它会额外地解析出伪静态及xml等包含的参数,
并按照用户要求不将cookie参数作为解析对象,
以达到不对cookie扫描检测的目的。
>> iterdatas = self.generateItemdatas()
>> print(iterdatas)
[
["id", "1", "URL"],
["username", "admin", "DATA"],
]
- self.insertPayload({“key”: k, “value”: v, “position”: position, “payload”: _payload}) :
令参数名为key,参数值为value(value为可选值),并向参数值后添加payload
最终返回其对应可控点修改后的数据
建议配合self.generateItemdatas()使用
>> datas = self.insertPayload({"key": "username", "value": "admin", "position": "DATA", "payload": "'--+"})
>> print(datas)
{"username": "admin'--+", "passwd": "admin"}
>> r = request.get(url, data=datas)
- self.req(position, payload) :
payload为对应可控点修改后的整体数据,可以为self.insertPayload的返回
需配合self.generateItemdatas()使用
>> datas = {"username": "admin'--+", "passwd": "admin"}
>> r = self.req("DATA", datas)
>> print(r)
… # r为request的返回
示范#
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# w8ay 2020/5/10
# JiuZero 2025/3/13
from data.rule.sqli_error import rules
from api import generateResponse, random_num, random_str, VulType, Type, PluginBase, conf, logger, Thread
from lib.helper.helper_sensitive import sensitive_page_error_message_check
import re
class Z0SCAN(PluginBase):
name = "sqli-error" # 插件名
desc = 'SQL Error Finder' # 描述
version = "2025.3.13" # 版本(最后更新日期)
risk = 2 # 危害等级
def audit(self):
if not self.fingerprints.waf and 2 in conf.risk and conf.level != 0:
_payloads = [
## 宽字节
r'鎈\'"\(',
## 通用报错
r';)\\\'\\"',
r'\' oRdeR bY 500 ',
r';`)',
r'\\',
r"%%2727",
r"%25%27",
r"%60",
r"%5C",
]
if conf.level == 3:
_payloads += [
## 强制报错
# MySQL
r'\' AND 0xG1#',
# PostgreSQL
r"' AND 'a' ~ 'b\[' -- ",
# MSSQL
r"; RAISERROR('Error generated', 16, 1) -- ",
# Oracle
r"' UNION SELECT XMLType('<invalid><xml>') FROM dual -- ",
# SQLite
r"' UNION SELECT SUBSTR('o', -1, 1) -- ",
]
iterdatas = self.generateItemdatas()
# 内置的线程并发
z0thread = Threads(name="sqli-error")
z0thread.submit(self.process, iterdatas, _payloads)
def Get_sql_errors(self):
sql_errors = []
for database, re_strings in rules.items():
for re_string in re_strings:
sql_errors.append((re.compile(re_string, re.IGNORECASE), database))
return sql_errors
def process(self, _, _payloads):
k, v, position = _
for _payload in _payloads:
payload = self.insertPayload({
"key": k,
"value": v,
"position": position,
"payload": _payload
})
r = self.req(position, payload)
if not r:
continue
html = r.text
for sql_regex, dbms_type in self.Get_sql_errors():
match = sql_regex.search(html)
if match:
# 生成报告
result = self.generate_result()
result.main({
"type": Type.REQUEST, # 扫描类型
"url": self.requests.url, # 漏洞URL
"vultype": VulType.SQLI, # 漏洞类型
"show": { # 你希望向命令行展示的信息
"Position": f"{position} > {k}", # 建议键名首字母大写
"Payload": payload,
"Msg": "DBMS_TYPE Maybe {}; Match {}".format(dbms_type, match.group())
}
})
# 验证步骤(可以添加多个过程,如二次验证)
result.step("Request1", {
"request": r.reqinfo,
"response": generateResponse(r),
"desc": "Dbms Maybe {}; Match {}".format(dbms_type, match.group())
})
self.success(result)
return True
message_lists = sensitive_page_error_message_check(html)
if message_lists:
result = self.generate_result()
result.main({
"type": Type.REQUEST,
"url": self.requests.url,
"vultype": VulType.SQLI,
"show": {
"Position": f"{position} > {k}",
"Payload": payload,
"Msg": "Receive Error Msg {}".format(repr(message_lists))
}
})
result.step("Request1", { # 步骤标题
"request": r.reqinfo, # 请求体
"response": generateResponse(r), # 响应体(由generateResponse生成)
"desc": "Receive Error Msg {}".format(repr(message_lists)) # 步骤的关键信息
})
self.success(result)
break