Life is short. You need z0scan.
介绍#

z0scan(简称z0)是一款基于Python开发的Web漏洞扫描工具,兼具主动和被动扫描能力。
该项目摒弃了冗杂的组件及框架POC,专注于Web应用和常见服务漏洞检测,采用模块化设计,注重实际扫描效率与边缘类冷门漏洞的发现。
z0 的主要特点:
智能化的扫描策略
- 集成WAF识别和指纹检测功能,可根据目标特征动态调整扫描策略
- 通过优化请求频率和检测逻辑,尽可能减少对目标系统业务的干扰
全面的参数解析能力
- 支持解析JSON、XML等结构化数据中的参数
- 具备伪静态URL参数识别功能,扩展了漏洞检测范围
便捷的数据管理
- 采用SQLite3数据库存储扫描记录,便于结果管理和去重处理
- 数据默认存储在data/z0scan.db文件中
良好的跨平台支持
- 基于Python3开发,支持主流操作系统
- 可在Termux等移动端环境中运行
安装#
通用安装#
推荐#
获取发行版本(由nuitka编译构建,速度&性能显著提升):获取
- 想构建适合自己环境的可执行文件?
# 参考网络步骤部署编译环境
...
# 补齐依赖
pip install -r requirements.txt
# 需要ccache!
# 安装ZeroMQ(可选,默认socket)
pip install pyzmq
# 安装编译器
pip install nuitka zstandard imageio
# 构建可执行命令
python3 build.py
# 随后可执行文件会被生成至dist文件夹中
# 同时脚本会将dist文件压缩为一份zip文件备份
不推荐#
自2025.7.19起已不再提交pypi更新
通过GitHub克隆安装
git clone https://github.com/JiuZero/z0scan
cd z0scan
pip install -r requirements.txt
python3 z0.py help
部分特殊环境#
Termux
apt install python-cryptography python-lxml
...
# 后续步骤同上使用GitHub克隆安装
iSH(未调试)
建议在M系芯片环境中运行
apk add py3-cryptography py3-lxml py3-certifi py3-six py3-idna py3-certifi py3-cffi
...
# 后续步骤同上使用GitHub克隆安装
使用#
参数#
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) |
reverse#
参数 | 输入值示例 | 说明 |
---|---|---|
/ | / | 启动反连平台 |
console#
参数 | 输入值示例 | 说明 |
---|---|---|
/ | / | 启动参数交互命令行 |
dbcmd#
参数 | 输入值示例 | 说明 |
---|---|---|
/ | / | 启动数据库交互命令行 |
被动扫描#
默认配置下被动扫描
(浏览器转发流量到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
报告#
报告展示#


插件#
插件列表请见:Z0Scan-README
插件示例&计划(部分)#
示例目标均为内网/外网公开靶场。
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", ]
- 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