docs(security): 合并 gateway 审计文档并新建 ca 审计索引
- 三份 gateway 审计(20260801/0807/0809)合并为单一 docs/security/gateway_SECURITY_AUDIT.md - 已完成修复已合入 gateway/src/main.cpp 注释,认证相关项标注迁 ca - 新建 docs/security/ca_SECURITY_AUDIT.md,同步迁入 P2-3/P1-7/A2 等开放项 - 更新 docs/INDEX.md 审计表、ca/README.md 引用、auth_service_plan/libhttp 文档链接 Co-Authored-By: WorkBuddy <workbuddy@tencent.com>
This commit is contained in:
1 parent
b634f192ea
commit
dbc79c52f3
10 files changed
+185
-734
No files matched your search
@@ -0,0 +1,64 @@
|
||||
# ca 模块安全审计索引(Central Auth)
|
||||
|
||||
> ca(Central Auth,C++20,v1.0.0)是 UniServe Framework 的中央认证服务,承接原 gateway v0.8.0 的全部认证职责。
|
||||
> 本索引汇总:① 从 gateway v0.9.0 认证独立化**迁移接收**的审计项(已在 ca 闭环);② **ca 相关开放项**(与 gateway 转发层/配置共享,需 ca 侧处理);③ ca 原生安全态势基线。
|
||||
> 关联文档:gateway 合并审计 [`gateway_SECURITY_AUDIT.md`](gateway_SECURITY_AUDIT.md);认证迁移设计 [`../reports/auth_service_plan.md`](../reports/auth_service_plan.md)。
|
||||
|
||||
---
|
||||
|
||||
## 1. 从 gateway 迁移接收项(v0.9.0,已在 ca 闭环)
|
||||
|
||||
下列历史问题随 v0.9.0 认证独立化由 gateway 移交 ca,gateway 侧不再跟踪。ca 已闭环(实现见 `ca/src/main.cpp`):
|
||||
|
||||
| 原编号 | 问题 | ca 侧状态 | 实现位置 / 说明 |
|
||||
|--------|------|-----------|----------------|
|
||||
| P0-1 | RSA 密钥持久化 | 设计接受(重启即密钥轮换,同进程验签) | ca 内存生成 RSA 密钥 |
|
||||
| P0-2 / P0-3 | 错误信息脱敏 | ✅ 已闭环 | ca handler 错误不外泄内部原因 |
|
||||
| P1-1 | Token 吊销机制 | ✅ 已实现 | jti 黑名单(5000 阈值)+ 用户版本号双轨,纯内存 |
|
||||
| P1-2 | 账户级登录失败锁定 | ✅ 已实现 | 登录失败计数 + 锁定(ca 侧) |
|
||||
| P2-5 | JTI 黑名单内存有界 | ✅ 已实现 | 5000 条阈值清理 |
|
||||
| P2-6 | 用户名 / 邮箱长度限制 | ✅ 已实现 | ca 输入校验 |
|
||||
| A3 | 缺字段返回 400 | ✅ 已实现 | ca handler 参数校验 |
|
||||
| A6 / A7 | 验证码双渠道统一 / 废弃内置验证码 | ✅ 已闭环 | 统一走 captcha2 远端校验 |
|
||||
|
||||
---
|
||||
|
||||
## 2. ca 相关开放项(需 ca 侧处理)
|
||||
|
||||
下列 gateway 合并审计的开放项与 ca 直接相关,归 ca 跟踪:
|
||||
|
||||
| 编号 | 等级 | 问题 | 归属说明 | 状态 |
|
||||
|------|------|------|----------|------|
|
||||
| P2-3 | 🟡 | CORS 白名单未生效(OPTIONS 预检反射 Origin) | ca `CORS.allowed_origins` 已读取但未用于预检响应头(见 `ca/README.md` 配置段);与 gateway 共享同一白名单配置 | ❌ 未修复 |
|
||||
| P1-7 | 🟠 | CSRF Token 缺失 | gateway 纯转发无认证表单;CSRF 评估在 ca 层(会话模型 + `SameSite=Lax`)。如 ca 启用 `tls=on` 独立 HTTPS 需评估 | ❌ 未修复(低优先级) |
|
||||
| A2 | 🟠 | 白名单 403 断言 | `Target-Url` 白名单由 gateway `Proxy.opened` 裁决 403,但白名单配置 / 断言随转发层迁入 ca;跨模块边界,gateway 与 ca 需保持一致 | ⚠️ 边界共识(gateway A2 已转绿,ca 侧需保持白名单同步) |
|
||||
|
||||
> 其余 gateway 开放项(P1-3 代理超时、P1-5 请求体大小、P1-8 日志注入、P2-2 TLS、P2-4 HTTP 超时、A4 XFF)属 gateway 纯转发层 / libhttp 层,不归 ca,见 [`gateway_SECURITY_AUDIT.md`](gateway_SECURITY_AUDIT.md) §4。
|
||||
|
||||
---
|
||||
|
||||
## 3. ca 原生安全态势基线
|
||||
|
||||
摘自 `ca/README.md` 安全特性(v1.0.0):
|
||||
|
||||
| 特性 | 实现 |
|
||||
|------|------|
|
||||
| 密码哈希 | PBKDF2-HMAC-SHA256,100000 轮,16B 随机盐,32B 哈希 |
|
||||
| 密码强度 | ≥8 字符,大写/小写/数字/特殊中至少 3 类 |
|
||||
| Token 机制 | `Base64(payload) + "_" + RSA-Sign(payload)`,验签 + 过期(默认 1h) |
|
||||
| Token 吊销 | 双轨纯内存:jti 黑名单(5000 阈值)+ 用户版本号 |
|
||||
| 常数时间比较 | PBKDF2 验证 `diff |= hash[i] ^ expected[i]` 防 timing |
|
||||
| 防枚举 | 不存在用户 dummy PBKDF2 拉平时序;重置密码恒成功 |
|
||||
| CAPTCHA | 统一 captcha2 远端校验(一次性 Token);`/login` 强制 |
|
||||
| TLS 可选 | `Server.tls = off` 纯 HTTP(配合 WAF),`on` 用 `full_chain.crt` / `server.key` |
|
||||
| 下游容错 | pymsg `retry_on_refused` 阻塞等待 db/confs;日志 fail-safe |
|
||||
|
||||
---
|
||||
|
||||
## 4. 维护说明
|
||||
|
||||
- 新增 / 复测 ca 相关开放项请编辑本文件 §2;修复后移至 §1 或合入 `ca/src/main.cpp` 注释。
|
||||
- gateway 合并审计的“已迁 ca”清单(§1)以本文件为权威;如 gateway 文档与本文件不一致,以本文件为准。
|
||||
|
||||
**审计人**: AI Security Auditor
|
||||
**建立于**: 2026-08-10(从 gateway 合并审计同步开放项与迁移接收项;此前 ca 无独立审计文档)
|
||||
@@ -0,0 +1,110 @@
|
||||
# gateway 模块安全审计(合并文档)
|
||||
|
||||
> 本文档合并了 gateway 自 v0.8.0 以来的三轮安全审计记录:
|
||||
> - **2026-08-01** 初版审计(gateway v0.8.0,2500+ 行,认证内嵌)
|
||||
> - **2026-08-07** 复审(P0 全部修复、进程退出 bug 修复)
|
||||
> - **2026-08-09** 增量复审(8 项新发现:优雅关闭崩溃、头名大小写、XFF 伪造绕过修复、黑盒测试重写落地)
|
||||
>
|
||||
> **架构变更(v0.9.0)**:gateway 已退化为**纯转发服务**(仅 loopback 监听,不验签),认证职责(Token 签发/验签、登录/注册/重置、限流、WS 代理鉴权、PBKDF2/RSA)整体迁移至 [ca](../ca/) 模块。
|
||||
>
|
||||
> 所有 ✅ 已修复项已作为代码注释合入 `gateway/src/main.cpp`(以“审计 A*”“P1-1”等标记内联于对应代码)。本文档仅保留**开放项、历史追溯与上线检查清单**,避免陈旧信息。
|
||||
|
||||
---
|
||||
|
||||
## 1. 审计轮次概览
|
||||
|
||||
| 轮次 | 日期 | 基线 / 范围 | 关键结论 |
|
||||
|------|------|-------------|----------|
|
||||
| 初版 | 2026-08-01 | v0.8.0 `src/main.cpp`、`src/shared/`(libth/libhttp/libcrypt/libsqlite3) | 建立 P0/P1/P2 分级;P0-1 RSA 持久化定为设计接受;发现认证内嵌带来的 Token 吊销缺失等 |
|
||||
| 复审 | 2026-08-07 | v0.9.0 前 `src/main.cpp`、`inc/`、配置 | P0 三项全部修复;进程退出 bug 修复;Token 吊销(P1-1)实现并随认证独立化迁 ca |
|
||||
| 增量复审 | 2026-08-09 | v0.9.0 纯转发 `src/main.cpp` | 8 项新发现(A1/A2/A5/A8 等代理层修复、黑盒测试重写落地);认证类问题转入 ca 审计 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 已完成修复(已合入代码,供追溯)
|
||||
|
||||
代码位置:`gateway/src/main.cpp`(注释以“审计 A*”/“P1-1”等标记)。
|
||||
|
||||
- **A1** 优雅关闭 `double-join` 崩溃(删除 else 分支重复 `join`)
|
||||
- **A2** 白名单外 Target-Url 返回 **403**(改抛 `HttpException 2303`,不再 500)
|
||||
- **A5** `is_special_header` 头名大小写归一(统一转小写比较)
|
||||
- **A8** 错误消息 `[Http][Error NNNN]` 前缀剥离(仅 `e.message()` 外泄,前缀留服务端日志)
|
||||
- **P1-1** 路径遍历防护(`is_safe_target`)+ XFF 剥离与可信注入(`is_special_header` / `handleProxy`)
|
||||
- **libhttp P1-2** 反向代理响应体 10MB 上限
|
||||
- 信号安全重构(async-signal-safe 标志 + SIGPIPE 忽略 + 关闭 8s / 日志 3s 超时保护)
|
||||
- 启动 loopback 强制自检 + `X-Auth-User` 存在性不变量(v0.9.0 新增)
|
||||
|
||||
> 认证侧脱敏(P0-2 / P0-3 错误信息脱敏)已在 `wrapperHttp` / `ExpBase.hpp` 闭环。
|
||||
|
||||
---
|
||||
|
||||
## 3. 已随认证迁移至 ca(v0.9.0,不再属 gateway 待办)
|
||||
|
||||
以下历史问题在 v0.9.0 认证独立化后于 ca 模块闭环,gateway 侧无需再处理;ca 的独立审计见 `ca_SECURITY_AUDIT.md`(其 §2 为 ca 相关开放项)。
|
||||
|
||||
- P0-1 RSA 密钥持久化(设计接受:重启即密钥轮换)
|
||||
- P0-2 / P0-3 错误信息脱敏
|
||||
- P1-1 Token 吊销机制(jti 黑名单 + 用户版本号双轨,已实现于 ca)
|
||||
- P1-2 账户级登录失败锁定(ca 侧)
|
||||
- P2-5 JTI 黑名单内存有界(ca 侧)
|
||||
- P2-6 用户名 / 邮箱长度限制(ca 侧)
|
||||
- A3 缺字段返回 400(ca 侧 handler)
|
||||
- A6 / A7 验证码双渠道统一 / 废弃内置验证码(ca + captcha2 侧)
|
||||
|
||||
---
|
||||
|
||||
## 4. 当前开放项(gateway v0.9.0 纯转发)
|
||||
|
||||
| 编号 | 等级 | 问题 | 状态 | 说明 / 修复建议 |
|
||||
|------|------|------|------|------|
|
||||
| P1-3 | 🟠 | 上游代理 `HttpClient` 未显式设置超时 | ⚠️ 部分修复 | 下游服务(captcha/smtp)超时已配;代理转发 `HttpClient` 未设连接/读取超时。建议连接 5s、读取 30s(`handleProxy` 出站调用处) |
|
||||
| P1-5 | 🟠 | 请求体大小无显式限制 | ❌ 未修复 | `req.body()` 直接读取,gateway 层未检查 `Content-Length`;依赖 libhttp 默认 10MB。建议超 10MB 返回 413 |
|
||||
| P1-7 | 🟠 | CSRF Token 缺失 | ❌ 未修复(低优先级) | gateway 纯转发、无认证表单;前端 + ca 会话模型覆盖。如启用独立 HTTPS 模式(`tls = on`)需评估 |
|
||||
| P1-8 | 🟠 | 日志注入 | ⚠️ 部分修复 | 用户输入经 ca 透传后直接拼接日志;内部 pymsg→db,外部不可见,风险低。建议对 username/email 等字段做换行符转义 |
|
||||
| P2-2 | 🟡 | TLS 版本 / 套件未限定 | ❌ 未修复(低风险) | `tls = off`,TLS 由 WAF 终止;如启用 `tls = on` 需增加 TLS 版本与 CipherSuites 配置项 |
|
||||
| P2-3 | 🟡 | CORS 反射任意 Origin | ❌ 未修复 | `_CORS_ORIGINS` 白名单已加载但未用于响应头;OPTIONS 预检由 libhttp 直接反射 Origin。需 libhttp 层接入白名单或由 gateway 注册显式 OPTIONS 路由 |
|
||||
| P2-4 | 🟡 | HTTP 服务器无 Read/Write/Idle 超时 | ⚠️ 部分修复 | `HttpServer(addr,port,4)` 的 `4` 为连接超时,无请求级超时;存在 Slowloris / 连接耗尽风险 |
|
||||
| A4 | 🟡 | 模式 B 独立部署限流可被 XFF 伪造绕过 | 📝 已文档化 | README 模式 B 已加安全降级提示(伪造 XFF 可绕过限流,生产必须用模式 A / WAF 前置)。根因需 libhttp 向 handler 透传 TCP 对端地址(跨模块 API 变更,见 `../reports/libhttp_remote_addr_dev_doc.md`) |
|
||||
|
||||
> **不在 gateway 范围(libhttp 层,gateway 侧跳过)**:P1-4 C++ HTTP 解析器 fuzz(属 libhttp);P1-6 反向代理响应大小已由 libhttp 默认 10MB 保证(视为已闭环)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 配置安全审计(历史结论,仍适用)
|
||||
|
||||
| 检查项 | 状态 | 说明 |
|
||||
|--------|------|------|
|
||||
| 监听地址 | ✅ 安全 | `address = 127.0.0.1` |
|
||||
| TLS 模式 | ✅ 符合部署 | `tls = off`,WAF 终止 |
|
||||
| 管理员白名单 | ✅ 安全 | `users` 显式配置,无默认管理员 |
|
||||
| CORS 白名单 | ⚠️ 配置未生效 | `allowed_origins` 已配但未用于响应头(见 P2-3) |
|
||||
| Token 有效期 | ✅ 合理 | `keep_time = 3600`,`max_refresh = 10` |
|
||||
| WS 代理路由 | ✅ 安全 | 仅 `/ws/aibox`,需管理员权限 |
|
||||
| 敏感凭据 | ✅ 无泄露 | 配置文件无硬编码密码 / 密钥 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 黑盒测试待办(需测试钩子)
|
||||
|
||||
- 完整旅程(captcha → send_code → register → login → whoami → logout)、Cookie 链路(Set-Cookie 属性、代理注入 Authorization)、jti / 版本号吊销、refresh_token、白名单 403(A2 已转绿)、防枚举时序
|
||||
- `test_captcha.py` / `test_smtp.py` 重写为 pymsg 直连(captcha2:8009、smtp:8008)
|
||||
- 钩子方案(二选一):captcha2 / smtp 提供测试模式(固定验证码答案);或 `gateway.ini` 增加 `[Security] captcha_required / email_code_required` 开关
|
||||
|
||||
---
|
||||
|
||||
## 7. 上线前检查清单
|
||||
|
||||
- [ ] 确认 WAF 规则已启用
|
||||
- [ ] 确认所有服务绑定 127.0.0.1
|
||||
- [ ] 确认云安全组 / ufw 仅放行 TCP 443
|
||||
- [ ] 确认无默认管理员账户
|
||||
- [ ] 确认 `.env` 权限 0600,SMTP 密码未硬编码
|
||||
- [ ] 确认 gateway 配置 `tls = off`、`address = 127.0.0.1`
|
||||
- [ ] 回归测试:登录 / 注册 / 代理 / Token 刷新 / 吊销全链路正常
|
||||
|
||||
---
|
||||
|
||||
**维护说明**:新增 / 复测开放项请直接编辑本文件;修复后请将对应行移至“已完成”(§2)或合入 `gateway/src/main.cpp` 代码注释后删除本行。
|
||||
|
||||
**审计人**: AI Security Auditor
|
||||
**合并于**: 2026-08-10(三轮审计合并;源文件 `gateway_SECURITY_AUDIT_20260801.md` / `_20260807.md` / `_20260809.md` 已归档删除)
|
||||
@@ -1,142 +0,0 @@
|
||||
# gateway 模块安全审计报告
|
||||
|
||||
**审计日期**: 2026-08-01
|
||||
**审计范围**: `src/main.cpp`、`src/shared/`(libth、libhttp、libcrypt、libsqlite3)
|
||||
**代码版本**: dev_qing 分支
|
||||
|
||||
---
|
||||
|
||||
## 修复进度
|
||||
|
||||
| 优先级 | 总数 | 已修复 | 部分修复 | 未修复 |
|
||||
|--------|------|--------|----------|--------|
|
||||
| P0 | 1 | 0 | 1 | 0 |
|
||||
| P1 | 6 | 0 | 1 | 5 |
|
||||
| P2 | 4 | 1 | 0 | 3 |
|
||||
| **合计** | **11** | **1** | **2** | **8** |
|
||||
|
||||
---
|
||||
|
||||
## 🔴 P0 — 严重问题
|
||||
|
||||
### P0-1: RSA 私钥存储在仓库中/硬编码
|
||||
- **位置**: `main.cpp` L686 `_KEYGEN = std::make_unique<MyRSA::Generator>();`
|
||||
- **当前状态**: ⚠️ 部分修复
|
||||
- 密钥由 `MyRSA::Generator` 在内存中运行时生成,**未硬编码在源码中**,**未随仓库分发**
|
||||
- 每个进程实例独立生成密钥对,满足"每个部署独立密钥"要求
|
||||
- **遗留问题**: 密钥为内存生成,进程重启后丢失,所有已签发 Token 立即失效
|
||||
- **修复建议**:
|
||||
1. ~~私钥从文件路径加载(启动参数或环境变量),不硬编码~~ ✅ 已不在源码中硬编码
|
||||
2. ~~每个部署实例生成独立密钥对~~ ✅ 已满足
|
||||
3. 私钥文件权限 0600,仅服务运行用户可读 — 需增加持久化机制
|
||||
4. 公钥分发机制:dnfs、aibox 等需要验签的服务可访问公钥 — 待实现
|
||||
|
||||
---
|
||||
|
||||
## 🟠 P1 — 高危问题
|
||||
|
||||
| # | 问题 | 状态 | 说明 |
|
||||
|---|------|------|------|
|
||||
| P1-1 | Token 吊销机制缺失 | ❌ 未修复 | `handleLogout` (L1195) 仅清除 Cookie,无服务端黑名单/吊销列表;修改密码/注销后旧 Token 仍有效直到过期 |
|
||||
| P1-2 | 登录失败次数限制 | ⚠️ 部分修复 | IP 级限流已实现 (`_LOGIN_RATE` L376,`check_and_record(ip,5,300)` L1033),但**无账户级锁定**(同一邮箱多次失败不触发账户锁定) |
|
||||
| P1-3 | 上游代理超时配置 | ❌ 未修复 | `handleProxy` (L2084) 中 `HttpClient` 未显式设置超时,依赖 libhttp 默认值 |
|
||||
| P1-4 | C++ HTTP 解析器缓冲区溢出审计 | ❌ 未修复 | libhttp 库(`src/shared/`)需全面 fuzz 测试 |
|
||||
| P1-5 | 请求体大小限制 | ❌ 未修复 | 未配置显式请求体大小上限(如 10MB),依赖 Boost.Beast 默认行为 |
|
||||
| P1-6 | 反向代理响应无大小限制 | ❌ 未修复 | `handleProxy` 回调 L2212-2214 拼接下游响应 body 无上限(`res.body() += ret`,带 `FIXME: 增加长度限制`),下游(static/dnfs)返回超大响应可导致 OOM(全局审计 P1-1) |
|
||||
|
||||
---
|
||||
|
||||
## 🟡 P2 — 中危问题
|
||||
|
||||
| # | 问题 | 状态 | 说明 |
|
||||
|---|------|------|------|
|
||||
| P2-1 | 错误页面泄露版本号/堆栈信息 | ✅ 已修复 | `wrapperHttp` (L920-965) 异常捕获返回统一 `"Internal Server Error"`;`ExceptionBase::build_message()` 中文件/行号/函数名已注释(`ExpBase.hpp` L173-197),`what()` 仅含业务消息,不向客户端暴露源码位置 |
|
||||
| P2-2 | HTTPS TLS 版本和密码套件安全 | ❌ 未修复 | `main.cpp` L682 TLS 模式下仅指定证书文件,未显式限定 TLS 版本和 CipherSuites |
|
||||
| P2-3 | CORS 通配(反射任意 Origin) | ❌ 未修复 | **根因**:`_CORS_ORIGINS` 白名单 (L598) 已读取但**从未用于响应头**——OPTIONS 预检由 libhttp 库处理(`HttpServer_Request.cpp` L73-74),直接反射请求 Origin,libhttp 不感知 gateway 的 `_CORS_ORIGINS` 状态,等于允许任意来源(全局审计 P2)。修复需在 libhttp 层接入白名单或由 gateway 注册显式 OPTIONS 路由 |
|
||||
| P2-4 | HTTP 服务器无超时配置 | ❌ 未修复 | 未设置 Read/Write/Idle 超时,`HttpServer(addr,port,threads)` 构造函数无超时参数,依赖 libhttp 默认行为,存在 Slowloris/连接耗尽风险(全局审计 P2) |
|
||||
|
||||
---
|
||||
|
||||
## ✅ 已有安全机制
|
||||
|
||||
1. ✅ **PBKDF2 密码哈希**: 慢哈希算法防彩虹表,使用唯一盐值 (`main.cpp` L58-143)
|
||||
2. ✅ **RSA Token 签名**: Token 使用 RSA 私钥签名,防伪造 (`main.cpp` L1112-1130)
|
||||
3. ✅ **反向代理架构**: 统一入口,隐藏后端服务拓扑
|
||||
4. ✅ **速率限制**: 多层防护(IP 级 + 邮箱级双窗口限流、60秒冷却、失败锁定)
|
||||
5. ✅ **防用户名枚举**: 用户不存在时执行 dummy PBKDF2 校验拉平响应时序 (`main.cpp` L1113-1118)
|
||||
6. ✅ **WAF 前置防护**: Go WAF 处理 SQLi/XSS/CC/协议解析
|
||||
7. ✅ **C++ 高性能实现**: 避免 Python GIL 瓶颈,抗并发能力强
|
||||
8. ✅ **依赖审计**: 使用项目自带的 libcrypt 加密库(PBKDF2、RSA)
|
||||
9. ✅ **路径遍历防护**: `is_safe_target()` 校验请求路径,拒绝 `..`、空字节、URL 编码遍历 (`main.cpp` L2301-2326)
|
||||
10. ✅ **X-Forwarded-For 可信注入**: 剥离客户端伪造的 IP 头后由 gateway 主动设置 (`main.cpp` L2229, `is_special_header` L2266-2280)
|
||||
11. ✅ **优雅关闭**: SIGINT/SIGTERM 信号处理 (`main.cpp` L482-496)
|
||||
12. ✅ **Token 刷新机制**: 支持宽限期刷新 + 次数上限 + 限流 (`main.cpp` L1463-1590)
|
||||
13. ✅ **CAPTCHA 强制校验**: 登录/注册/发送验证码均需 CAPTCHA,对接 captcha 服务 (`main.cpp` L443, 调用点 L1048-1055/1707-1713/1801-1810/1915-1923)
|
||||
14. ✅ **异步日志**: 批量推送 + fail-safe,db 不可用时静默丢弃 (`LogC.hpp`)
|
||||
|
||||
---
|
||||
|
||||
## 修复路线图
|
||||
|
||||
### 立即(24小时)
|
||||
- [ ] 增加请求体大小限制(参考 waf 10MB 配置)
|
||||
- [ ] 反向代理响应体大小限制(P1-6,防 OOM)
|
||||
- [ ] 代理上游 HttpClient 增加显式超时配置
|
||||
|
||||
### 短期(72小时)
|
||||
- [x] 确认 RSA 私钥未硬编码 → ✅ 已确认:运行时内存生成,非硬编码
|
||||
- [ ] 实现 Token 吊销列表(密码修改时注销旧 Token)
|
||||
- [ ] 增加账户级登录失败锁定
|
||||
- [ ] libhttp 解析器 fuzz 测试
|
||||
|
||||
### 中期
|
||||
- [x] 错误页面脱敏 → ✅ 已完成
|
||||
- [ ] TLS 安全配置(限定 TLS 版本和 CipherSuites)
|
||||
- [ ] CORS 白名单生效(`_CORS_ORIGINS` 接入响应头,替代反射任意 Origin)
|
||||
- [ ] HTTP 服务器超时配置(Read/Write/Idle)
|
||||
- [ ] 管理操作审计日志
|
||||
- [ ] RSA 密钥持久化(避免重启后 Token 全部失效)
|
||||
|
||||
---
|
||||
|
||||
## 代码验证记录
|
||||
|
||||
**验证日期**: 2026-08-03
|
||||
**验证方式**: 逐条对照审计报告与当前代码(`main.cpp`、`Conf.hpp`、`DbC.hpp`、`LogC.hpp`、`Msg.hpp`)
|
||||
|
||||
### 验证结果汇总
|
||||
|
||||
| 等级 | 总数 | 已修复 | 部分修复 | 未修复 | 修复率 |
|
||||
|------|------|--------|----------|--------|--------|
|
||||
| P0 | 1 | 0 | 1 | 0 | 50% (部分) |
|
||||
| P1 | 6 | 0 | 1 | 5 | 8% (部分) |
|
||||
| P2 | 4 | 1 | 0 | 3 | 25% |
|
||||
| **合计** | **11** | **1** | **2** | **8** | **18%** |
|
||||
|
||||
### 逐条验证
|
||||
|
||||
| 编号 | 状态 | 代码位置 | 验证说明 |
|
||||
|------|------|----------|----------|
|
||||
| P0-1 | ⚠️ 部分 | `main.cpp` L686 | `MyRSA::Generator()` 运行时生成,非硬编码;但无持久化,重启后 Token 全部失效 |
|
||||
| P1-1 | ❌ 未修复 | `main.cpp` L1195-1209 | `handleLogout` 仅清除 Cookie,无 Token 黑名单/吊销机制 |
|
||||
| P1-2 | ⚠️ 部分 | `main.cpp` L376, L1033 | IP 限流已实现 (5次/300s),无账户级锁定 |
|
||||
| P1-3 | ❌ 未修复 | `main.cpp` L2084-2262 | `handleProxy` 中 `HttpClient` 未显式设置超时 |
|
||||
| P1-4 | ❌ 未修复 | `src/shared/` | libhttp 库未进行 fuzz 测试 |
|
||||
| P1-5 | ❌ 未修复 | `main.cpp` | 未设置请求体大小上限 |
|
||||
| P1-6 | ❌ 未修复 | `main.cpp` L2212-2214 | `handleProxy` 回调 `res.body() += ret` 拼接下游响应 body 无上限,超大响应可 OOM |
|
||||
| P2-1 | ✅ 已修复 | `main.cpp` L920-965, `ExpBase.hpp` L173-197 | 异常统一返回 `"Internal Server Error"`;`build_message()` 已注释文件/行号/函数名,不泄露源码位置 |
|
||||
| P2-2 | ❌ 未修复 | `main.cpp` L682 | TLS 模式仅指定证书,未限定版本和密码套件 |
|
||||
| P2-3 | ❌ 未修复 | `main.cpp` L598, libhttp `HttpServer_Request.cpp` L73-74 | `_CORS_ORIGINS` 白名单读取但未用于响应头(OPTIONS 由 libhttp 处理,不感知 gateway 状态);OPTIONS 预检反射任意 Origin |
|
||||
| P2-4 | ❌ 未修复 | `main.cpp` L677-679, libhttp `HttpServer` | `HttpServer(addr,port,threads)` 无超时参数,未设置 HTTP 服务器 Read/Write/Idle 超时 |
|
||||
|
||||
### 新增安全机制(审计报告未覆盖)
|
||||
|
||||
| 机制 | 代码位置 | 说明 |
|
||||
|------|----------|------|
|
||||
| 路径遍历防护 | `main.cpp` L2301-2326 | `is_safe_target()` 拒绝 `..`、空字节、URL 编码遍历 |
|
||||
| XFF 可信注入 | `main.cpp` L2229, L2266-2280 | `is_special_header` 剥离客户端伪造 IP 头后主动设置可信 XFF |
|
||||
| 优雅关闭 | `main.cpp` L482-496 | SIGINT/SIGTERM 信号处理 |
|
||||
| 防用户枚举 | `main.cpp` L1113-1118 | 用户不存在时执行 dummy PBKDF2 拉平响应时序 |
|
||||
| 密码强度校验 | `main.cpp` L146-180 | 8-128字符,至少3种字符类别 |
|
||||
| 邮箱双窗口限流 | `main.cpp` L1814-1818 | 3次/小时 + 10次/天/邮箱 |
|
||||
| 异步日志 fail-safe | `LogC.hpp` L210-213 | db 不可用时静默丢弃,绝不影响业务 |
|
||||
@@ -1,318 +0,0 @@
|
||||
# gateway 模块安全审计报告(更新版)
|
||||
|
||||
**审计日期**: 2026-08-07
|
||||
**审计范围**: `src/main.cpp`、`inc/` 头文件、配置文件
|
||||
**代码版本**: dev_qing 分支
|
||||
**基于版本**: 2026-08-01 初版审计,2026-08-07 复审
|
||||
|
||||
---
|
||||
|
||||
## 修复进度总览
|
||||
|
||||
| 优先级 | 总数 | 已修复 | 部分修复 | 未修复 | 修复率 |
|
||||
|--------|------|--------|----------|--------|--------|
|
||||
| P0 | 3 | 3 | 0 | 0 | 100% |
|
||||
| P1 | 8 | 3 | 3 | 2 | 56% |
|
||||
| P2 | 6 | 1 | 2 | 3 | 33% |
|
||||
| **合计** | **17** | **7** | **5** | **5** | **56%** |
|
||||
|
||||
> 对比 2026-08-01 版本:修复率从 18% 提升至 56%,**所有P0严重问题已全部修复**,核心安全机制(Token吊销、错误信息脱敏)已落地。
|
||||
>
|
||||
> **更新日志**:
|
||||
> - 2026-08-07 P0修复:完成P0-3修复,将4处暴露内部步骤的错误消息替换为用户友好文案,修正拼写错误和状态码错误。
|
||||
> - 2026-08-07 稳定性修复:修复进程无法正常退出("杀不死")问题,包括信号处理安全、Listener无限递归CPU 100%、关闭超时保护等多个关键bug。
|
||||
|
||||
---
|
||||
|
||||
## 🔴 P0 — 严重问题
|
||||
|
||||
### P0-1: RSA 私钥持久化问题 — ✅ 已修复(设计接受)
|
||||
|
||||
- **位置**: `main.cpp` L967 `_KEYGEN = std::make_unique<MyRSA::Generator>();`
|
||||
- **当前状态**: ✅ 已修复(按设计接受)
|
||||
- **修复说明**:
|
||||
- 密钥由 `MyRSA::Generator` 在内存中运行时生成,**未硬编码在源码中**
|
||||
- 每个进程实例独立生成密钥对,满足"每个部署独立密钥"要求
|
||||
- **设计决策**: 进程重启后 RSA 密钥对重新生成,旧 Token 因 RSA 验签失败而自然失效,这是一种有效的隐式密钥轮换机制
|
||||
- 配合 Token 版本号机制(`_USER_VER_MAP`),重启等同于全量 Token 吊销,安全风险可控
|
||||
- **备注**: 如需持久化密钥(避免重启后用户需重新登录),可后续从文件加载;当前设计在安全性上更优(重启即吊销)。
|
||||
|
||||
### P0-2(新增): 错误页面泄露版本号/堆栈信息 — ✅ 已修复
|
||||
|
||||
- **原编号**: P2-1(初版),升级确认后确认核心问题已解决
|
||||
- **位置**: `main.cpp` L1167-1213、`ExpBase.hpp` L173-197
|
||||
- **当前状态**: ✅ 已修复
|
||||
- **修复说明**:
|
||||
- `wrapperHttp` 中 `std::exception` 捕获分支已不再返回 C++ 类型名和 `what()` 详情,统一返回 `"Internal Server Error"`
|
||||
- `ExceptionBase::build_message()` 中源码文件路径、行号、函数名、嵌套异常等位置信息已全部注释,`what()` 仅返回错误码和业务消息
|
||||
- 完整错误详情仍通过 `std::cerr` 输出到服务端日志,并写入 db `Logs` 表供排障
|
||||
- 服务端日志记录完整异常链,不影响排障能力
|
||||
|
||||
### P0-3(新增,原初版P2-1延伸): HttpException 业务消息返回 — ✅ 已修复
|
||||
|
||||
- **位置**: `main.cpp` L1179-1181, L1318, L1381, L1882, L2205
|
||||
- **当前状态**: ✅ 已修复(2026-08-07)
|
||||
- **修复说明**:
|
||||
- `HttpException` 被捕获后,`e.what()` 仍写入客户端 JSON 响应的 `message` 字段,但已确保不暴露敏感信息
|
||||
- **已解决**: 不再返回源码路径、行号、C++ 类型签名等敏感信息(P0-2已修复)
|
||||
- **已修复**: 4处暴露内部步骤的错误消息已全部替换为用户友好文案:
|
||||
- `"Login (Query Step) Failed"` → `"Login failed, please try again later"`
|
||||
- `"Login (Update Step) Failed"` → `"Login failed, please try again later"`
|
||||
- `"Registe Failed"` (拼写错误+400) → `"Registration failed, please try again later"` (修正为500)
|
||||
- `"Reset Failed"` → `"Password reset failed, please try again later"`
|
||||
- 详细错误信息仍记录在服务端日志(stderr + db Logs表),不影响排障
|
||||
- **修复验证**: 所有500错误消息均为用户友好的通用文案,不再暴露内部实现步骤
|
||||
|
||||
---
|
||||
|
||||
## 🟠 P1 — 高危问题
|
||||
|
||||
| # | 问题 | 状态 | 说明 |
|
||||
|---|------|------|------|
|
||||
| P1-1 | Token 吊销机制缺失 | ✅ 已修复 | jti 黑名单 + 用户版本号双轨机制已完整实现(L538-754) |
|
||||
| P1-2 | 登录失败次数限制 | ⚠️ 部分修复 | IP 级限流已实现(5次/300s/IP),**无账户级锁定** |
|
||||
| P1-3 | 上游代理超时配置 | ⚠️ 部分修复 | 下游服务(captcha 5s/smtp 5s/smtp send 20s)超时已配置,代理转发 HttpClient 未显式设置超时 |
|
||||
| P1-4 | C++ HTTP 解析器缓冲区溢出审计 | ⏭️ 跳过 | 属于 libhttp 层审计范围,gateway 侧无法直接修复;libhttp 80% 问题已修复 |
|
||||
| P1-5 | 请求体大小限制 | ❌ 未修复 | `req.body()` 直接读取(L2341),main.cpp 层未显式限制;依赖 libhttp 默认 10MB 限制 |
|
||||
| P1-6 | 反向代理响应无大小限制 | ✅ 已修复 | 注释 L2302 说明:响应体大小限制由 HttpClient 默认 10MB 上限保证(libhttp P1-2 已修复) |
|
||||
| P1-7(新增) | CSRF 防护不足 | ❌ 未修复 | Cookie 设置 `SameSite=Lax`,但所有 POST 接口(`/login`、`/register`、`/reset_password`、`/logout`)缺少 CSRF Token 验证 |
|
||||
| P1-8(新增) | 日志注入风险 | ⚠️ 部分修复 | 用户名/邮箱直接拼接写入日志消息(如 L1429、L1885、L2111);日志通过 pymsg 写入内部 db,外部无法直接查看,风险较低 |
|
||||
|
||||
---
|
||||
|
||||
## 🟡 P2 — 中危问题
|
||||
|
||||
| # | 问题 | 状态 | 说明 |
|
||||
|---|------|------|------|
|
||||
| P2-2 | HTTPS TLS 版本和密码套件安全 | ❌ 未修复 | TLS 模式仅指定证书路径和超时参数(L963),未显式限定 TLS 版本和 CipherSuites;当前配置 `tls = off`,TLS 由 WAF 终止,实际部署风险低 |
|
||||
| P2-3 | CORS 通配(反射任意 Origin) | ❌ 未修复 | `_CORS_ORIGINS` 白名单已从配置加载(L879),但 `handleProxy()` 中未应用 CORS 响应头;CORS 预检由 libhttp 直接反射请求 Origin |
|
||||
| P2-4 | HTTP 服务器无超时配置 | ⚠️ 部分修复 | HttpServer 构造函数传入 `4` 作为参数(L963、L965),为连接超时;未配置 Read/Write/Idle 请求级超时 |
|
||||
| P2-5(新增) | JTI 黑名单内存无界风险 | ✅ 已修复 | `_JTI_DENYLIST_MAX_SIZE = 5000`(L461),超过阈值时批量清理过期条目;RSA 密钥重启重新生成,天然限制黑名单生命周期 |
|
||||
| P2-6(新增) | 用户名输入未做长度限制 | ⚠️ 部分修复 | 密码有 8-128 字符长度校验(L148-181),但用户名/邮箱未做显式长度限制;db 层字段长度约束作为兜底 |
|
||||
|
||||
---
|
||||
|
||||
## ✅ 已验证的安全机制(完整列表)
|
||||
|
||||
### 核心安全能力
|
||||
|
||||
| 机制 | 位置 | 状态 | 说明 |
|
||||
|------|------|------|------|
|
||||
| PBKDF2 密码哈希 | L58-143 | ✅ | PBKDF2-HMAC-SHA256,100,000 迭代,16字节随机盐,恒定时间比较 |
|
||||
| 密码强度校验 | L148-181 | ✅ | 8-128字符,至少3类字符(大写/小写/数字/特殊) |
|
||||
| RSA Token 签名 | L1385 | ✅ | Token 使用 RSA 私钥签名,防伪造 |
|
||||
| 安全随机 JTI | L540-550 | ✅ | OpenSSL `RAND_bytes` 生成 128-bit 随机 JTI |
|
||||
| **Token 吊销机制** | L538-754 | ✅ | jti 黑名单(单Token吊销)+ 用户版本号(全设备吊销)双轨机制 |
|
||||
| jti 黑名单自动清理 | L569-578 | ✅ | 超过5000条目时批量清理过期条目,内存有界 |
|
||||
| IP 级速率限制 | L186-227, L1271 | ✅ | 登录/注册/发码/刷新/重置均有独立 RateLimiter |
|
||||
| 邮箱级双窗口限流 | L422-423, L1948-1955 | ✅ | 同一邮箱3次/小时 + 10次/天 |
|
||||
| 60秒发码冷却 | L2090-2107 | ✅ | smtp 层返回 `retry_after`,gateway 透传 429 |
|
||||
| CAPTCHA 强制校验 | L466-516 | ✅ | 登录/注册/发码/重置均强制校验,一次性消费 |
|
||||
| **CAPTCHA fail-closed** | L500-516 | ✅ | captcha 服务不可用时一律视为校验失败 |
|
||||
| 邮箱验证码 fail-closed | L520-536 | ✅ | smtp 服务不可用时一律视为校验失败 |
|
||||
| 防用户名枚举 | L1351-1357 | ✅ | 用户不存在时执行 dummy PBKDF2 校验拉平响应时序 |
|
||||
| 防重置密码枚举 | L2210-2212 | ✅ | 用户不存在时仍返回成功,不泄露邮箱是否注册 |
|
||||
| SameSite Cookie | L1424, L1722 | ✅ | `SameSite=Lax` |
|
||||
| HttpOnly + Secure Cookie | L1423-1426, L1720-1724 | ✅ | `HttpOnly; Secure; SameSite=Lax` |
|
||||
| Token 生命周期控制 | L1570-1747 | ✅ | refresh_count 限制(最多10次)+ 宽限期刷新(2倍有效期) |
|
||||
| 路径遍历防护 | L2392-2418 | ✅ | `is_safe_target()` 拒绝 `..`、空字节、URL编码遍历、非绝对路径 |
|
||||
| 伪造 IP 头剥离 | L2357-2369 | ✅ | `is_special_header()` 剥离客户端伪造的 XFF/X-Real-IP/Forwarded |
|
||||
| 可信 XFF 注入 | L2323-2329 | ✅ | gateway 主动设置可信 X-Forwarded-For,下游可信任 |
|
||||
| 敏感头过滤 | L2357-2369 | ✅ | 过滤 Host/Connection/Accept-Encoding 等传输层头 |
|
||||
| Authorization 自动注入 | L2331-2334 | ✅ | Cookie Token 自动转换为 `Authorization: Bearer` 向下游传递 |
|
||||
| WebSocket 管理员鉴权 | L1140-1145 | ✅ | WS 代理握手阶段校验 Token,仅 `is_admin=true` 可连接 |
|
||||
| 异常统一捕获 | L1158-1213 | ✅ | wrapperHttp 捕获所有异常,未处理异常返回 500 不崩溃 |
|
||||
| 错误信息脱敏 | L1192-1213, ExpBase.hpp | ✅ | 500错误不再返回 C++ 类型名、源码路径、行号 |
|
||||
| 异步日志 fail-safe | LogC.hpp | ✅ | 后台线程批量推送,db不可用时静默丢弃,不影响业务 |
|
||||
| 优雅关闭 | L763-777 | ✅ | SIGINT/SIGTERM 信号处理,停止服务后 flush 日志 |
|
||||
| 管理员白名单 | L1389 | ✅ | 登录时命中 `[Admin].users` 才签发 `is_admin=true` |
|
||||
| 公网目标白名单 | L2286-2298 | ✅ | 代理目标必须在 `Proxy.opened` 列表中,否则拒绝 |
|
||||
| db/captcha/smtp 仅本地监听 | gateway.ini | ✅ | 所有下游服务绑定 127.0.0.1,不对外暴露 |
|
||||
| 配置无硬编码密钥 | gateway.ini | ✅ | 配置文件中无硬编码密码/API Key |
|
||||
| 密码字段不记录日志 | 全文 | ✅ | 所有日志不记录明文密码、Token 等敏感字段 |
|
||||
|
||||
### 新增安全机制(自2026-08-01以来)
|
||||
|
||||
| 机制 | 位置 | 说明 |
|
||||
|------|------|------|
|
||||
| Token 吊销完整实现 | L538-754 | jti 黑名单 + 版本号双轨,支持单Token吊销和全设备吊销 |
|
||||
| logout 吊销 Token | L1448-1455 | `/logout` 吊销当前 jti |
|
||||
| refresh 轮换旧 Token | L1694-1697 | `/refresh_token` 吊销旧 jti,签发新 jti |
|
||||
| reset_password 全设备吊销 | L2208 | 重置密码后调用 `revoke_all_user_tokens()` 递增版本号 |
|
||||
| 线程安全的 CAPTCHA 存储 | L440-441, L480-494 | `_CAPTCHA_MTX` 互斥锁保护 |
|
||||
| 线程安全的 Token 吊销存储 | L460, L554, L565 | `_TOKEN_REVOKE_MTX` 互斥锁保护 |
|
||||
| 管理员 WS 代理鉴权 | L1140-1145 | WebSocket 升级前校验管理员权限 |
|
||||
| 业务异常分级日志 | L1173-1178 | 5xx 记 ERROR,4xx 记 WARNING |
|
||||
|
||||
---
|
||||
|
||||
## 问题修复路线图(更新版)
|
||||
|
||||
### 立即修复(24小时内)
|
||||
- [ ] **P1-7**: 为 POST 接口添加 CSRF Token 验证(或确认 API 使用场景仅为同源 + Bearer Token)
|
||||
- [ ] **P1-3**: 为代理 HttpClient 显式设置连接超时和读取超时(建议连接5s,读取30s)
|
||||
|
||||
### 短期修复(72小时内)
|
||||
- [ ] **P1-2**: 添加账户级登录失败锁定(如同一邮箱5次失败后锁定15分钟)
|
||||
- [ ] **P1-5**: 在 `req.body()` 读取前显式检查 `Content-Length`,超过 10MB 返回 413
|
||||
- [ ] **P1-8**: 日志客户端对用户输入字段(username/email)做换行符转义,防止日志注入
|
||||
|
||||
### 中期修复
|
||||
- [ ] **P2-3**: CORS 白名单生效,在 `handleProxy()` 响应中检查 Origin 并设置 `Access-Control-Allow-Origin`
|
||||
- [ ] **P2-4**: 确认 HttpServer 超时参数语义,必要时补充 Read/Write/Idle 超时配置
|
||||
- [ ] **P2-6**: 为 username/email 添加显式长度限制(如邮箱最大254字符)
|
||||
- [ ] **P2-2**: 如启用独立 HTTPS 模式(`tls = on`),增加 TLS 版本和密码套件配置项
|
||||
|
||||
### 已完成项
|
||||
- [x] **P0-1**: RSA 密钥未硬编码(设计接受,重启即密钥轮换)
|
||||
- [x] **P0-2**: 错误页面脱敏(不再泄露源码路径/类型名)
|
||||
- [x] **P0-3**: HttpException业务消息脱敏(替换内部步骤消息为用户友好文案,修正拼写和状态码)
|
||||
- [x] P1-1: Token 吊销机制(jti + 版本号双轨)
|
||||
- [x] P1-6: 反向代理响应大小限制(依赖 libhttp 10MB 限制)
|
||||
- [x] P2-5: JTI 黑名单内存有界(5000条阈值 + 过期清理)
|
||||
|
||||
---
|
||||
|
||||
## 代码验证记录
|
||||
|
||||
**验证日期**: 2026-08-07
|
||||
**验证方式**: 逐条对照初版审计报告与当前代码(逐行阅读 `main.cpp` 全部2418行 + 全部5个头文件)
|
||||
|
||||
### 逐条验证结果
|
||||
|
||||
| 编号 | 初版状态 | 当前状态 | 代码位置 | 验证说明 |
|
||||
|------|----------|----------|----------|----------|
|
||||
| P0-1 | ⚠️ 部分 | ✅ 已修复 | L967, L451注释 | 重启重新生成RSA密钥是设计决策,配合版本号机制安全;注释明确说明设计意图 |
|
||||
| P1-1 | ❌ 未修复 | ✅ 已修复 | L538-754 | jti黑名单+版本号双机制完整实现,logout/refresh/reset_password均调用吊销 |
|
||||
| P1-2 | ⚠️ 部分 | ⚠️ 部分 | L1271 | IP限流5次/300s,仍无账户级锁定 |
|
||||
| P1-3 | ❌ 未修复 | ⚠️ 部分 | L429-431, L2337-2342 | 下游服务超时已配置,代理HttpClient未显式设置 |
|
||||
| P1-4 | ❌ 未修复 | ⏭️ 跳过 | libhttp | 属于libhttp层,libhttp修复率80% |
|
||||
| P1-5 | ❌ 未修复 | ❌ 未修复 | L2341 | main.cpp未显式检查Content-Length |
|
||||
| P1-6 | ❌ 未修复 | ✅ 已修复 | L2302注释 | libhttp HttpClient已修复默认10MB响应限制 |
|
||||
| P2-2 | ❌ 未修复 | ❌ 未修复 | L963 | TLS版本/套件仍不可配置;当前tls=off,风险低 |
|
||||
| P2-3 | ❌ 未修复 | ❌ 未修复 | L879, L2221-2354 | _CORS_ORIGINS已加载但handleProxy中未应用 |
|
||||
| P2-4 | ❌ 未修复 | ⚠️ 部分 | L963, L965 | 传入4作为超时参数(连接超时),无读写超时 |
|
||||
| P0-2(新增) | — | ✅ 已修复 | L1192-1213, ExpBase.hpp | std::exception返回通用消息,位置信息已注释 |
|
||||
| P0-3(新增) | — | ✅ 已修复 | L1318, L1381, L1882, L2205 | 4处内部步骤消息已替换为用户友好文案,修正拼写和状态码 |
|
||||
| P1-7(新增) | — | ❌ 未修复 | 全局POST接口 | SameSite=Lax但无CSRF Token |
|
||||
| P1-8(新增) | — | ⚠️ 部分 | L1429, L1885, L2111 | 日志记录用户名/邮箱;日志为内部系统,风险低 |
|
||||
| P2-5(新增) | — | ✅ 已修复 | L461, L569-578 | JTI黑名单5000条上限+过期清理 |
|
||||
| P2-6(新增) | — | ⚠️ 部分 | L1283 | 用户名/邮箱无显式长度限制,db层兜底 |
|
||||
|
||||
---
|
||||
|
||||
## 配置安全审计
|
||||
|
||||
**文件**: `confs/configs/gateway.ini`
|
||||
|
||||
| 检查项 | 状态 | 说明 |
|
||||
|--------|------|------|
|
||||
| 监听地址 | ✅ 安全 | `address = 127.0.0.1`,仅本地监听 |
|
||||
| TLS 模式 | ✅ 符合部署 | `tls = off`,TLS 由前置 WAF 终止 |
|
||||
| 管理员白名单 | ✅ 安全 | `users` 显式配置管理员邮箱,无默认管理员 |
|
||||
| CORS 白名单 | ⚠️ 配置未生效 | `allowed_origins` 已配置具体域名,但代码中未应用 |
|
||||
| Token 有效期 | ✅ 合理 | `keep_time = 3600`(1小时),`max_refresh = 10` |
|
||||
| WS 代理路由 | ✅ 安全 | 仅配置 `/ws/aibox` 一条路由,且需管理员权限 |
|
||||
| 敏感凭据 | ✅ 无泄露 | 配置文件中无硬编码密码/密钥 |
|
||||
|
||||
---
|
||||
|
||||
## 架构安全评估
|
||||
|
||||
### 安全分层(纵深防御)
|
||||
|
||||
```
|
||||
互联网
|
||||
↓
|
||||
waf:443 [TLS终止 + coraza规则匹配 + 速率限制]
|
||||
↓
|
||||
gateway:8443 [鉴权 + Token + 限流 + 代理白名单 + 路径校验]
|
||||
↓
|
||||
├─ static:9200 [前端托管 + 路径遍历防护]
|
||||
├─ dnfs:8006 [树状笔记 + AI内容审核]
|
||||
└─ (db/ai_review/captcha/smtp/confs 不对外暴露)
|
||||
```
|
||||
|
||||
### 安全优势
|
||||
|
||||
1. **多层防护**: WAF边缘防护 + gateway应用层鉴权 + 服务最小暴露
|
||||
2. **密码安全**: PBKDF2慢哈希 + 唯一盐值 + 恒定时间比较
|
||||
3. **Token安全**: RSA签名 + 完整吊销机制 + 生命周期控制
|
||||
4. **防枚举**: 登录/重置均有时序拉平和统一响应
|
||||
5. **fail-closed**: 验证码/邮件服务不可用时拒绝请求,而非放行
|
||||
6. **内存安全**: C++20 智能指针 + RAII,无手动内存管理
|
||||
7. **异步安全**: 日志fail-safe设计,后端故障不影响主业务
|
||||
8. **重启即吊销**: 密钥内存生成,重启后所有旧Token失效
|
||||
|
||||
### 剩余风险
|
||||
|
||||
1. **CSRF**: POST接口缺少CSRF Token,依赖SameSite=Lax防护
|
||||
2. **CORS**: 白名单配置未生效,跨域策略依赖libhttp默认行为
|
||||
3. **账户锁定**: 仅IP级限流,可被代理池绕过进行定向账户爆破
|
||||
4. **超时配置**: 代理转发未显式设置超时,慢后端可能占用连接
|
||||
5. **请求体限制**: main.cpp层未显式限制请求体大小(依赖libhttp默认值)
|
||||
|
||||
---
|
||||
|
||||
## 总结与建议
|
||||
|
||||
### 整体安全评级
|
||||
|
||||
**A-(优秀)** - **所有P0严重问题已全部修复**,核心安全机制完善,认证授权体系健全,剩余问题均为防御加固类,无直接可远程利用的严重漏洞。
|
||||
|
||||
### 优先修复建议
|
||||
|
||||
| 优先级 | 问题 | 预计工作量 | 安全收益 |
|
||||
|--------|------|------------|----------|
|
||||
| P1 | 代理超时显式配置 | 0.5小时 | 防慢后端连接耗尽 |
|
||||
| P1 | CSRF Token 评估 | 1小时 | 防跨站请求伪造 |
|
||||
| P1 | 请求体大小显式限制 | 0.5小时 | 防OOM攻击 |
|
||||
| P1 | 账户级登录锁定 | 2小时 | 防爆破 |
|
||||
| P2 | CORS 白名单生效 | 1小时 | 规范跨域策略 |
|
||||
|
||||
### 上线前检查清单
|
||||
|
||||
- [ ] 确认 WAF 规则已启用(非注释状态)
|
||||
- [ ] 确认所有服务绑定 127.0.0.1(aibox/redirect80除外)
|
||||
- [ ] 确认云安全组/ufw仅放行TCP 443
|
||||
- [ ] 确认无默认管理员账户(首位注册者为管理员)
|
||||
- [ ] 确认 `.env` 文件权限 0600,SMTP密码未硬编码
|
||||
- [ ] 确认 gateway 配置 `tls = off`、`address = 127.0.0.1`
|
||||
- [ ] 回归测试:登录/注册/代理/Token刷新/吊销全链路正常
|
||||
|
||||
---
|
||||
|
||||
## 🟢 稳定性修复(2026-08-07)
|
||||
|
||||
在安全审计过程中发现并修复了 gateway 进程"杀不死"问题,这是一个严重影响服务可靠性的bug:
|
||||
|
||||
### 修复的问题
|
||||
|
||||
| # | 问题 | 严重程度 | 位置 | 修复方式 |
|
||||
|---|------|----------|------|----------|
|
||||
| 1 | 信号处理函数调用非 async-signal-safe 函数(std::cout、_SERVE->stop()、锁),可能死锁 | 高 | `main.cpp:763-767` | 改为仅设置 `volatile sig_atomic_t` 标志,主线程轮询后安全关闭 |
|
||||
| 2 | 未处理 SIGPIPE,写关闭socket时进程可能被内核意外终止 | 中 | `main.cpp` | 新增 SIGPIPE 忽略处理 |
|
||||
| 3 | Listener::doAccept() acceptor关闭后仍无条件递归,导致CPU 100%空转 | **严重** | `HttpServer_Listener.cpp:46` | 检查acceptor状态,关闭或operation_aborted时停止递归 |
|
||||
| 4 | stop()未先关闭acceptor,停止过程中仍接受新连接 | 中 | `HttpServer.cpp:98-113` | 新增Listener::close()方法,stop()先关acceptor再停ioc |
|
||||
| 5 | stop()/shutdown()无超时保护,网络IO阻塞时join线程永久挂起 | 高 | `main.cpp` | stop() 8秒超时,日志flush 3秒超时,超时强制exit保证进程退出 |
|
||||
| 6 | 主线程直接运行ioc->run(),无法安全响应信号 | 中 | `main.cpp` | 服务器在独立线程运行,主线程负责信号检测和关闭协调 |
|
||||
|
||||
### 修复后的关闭保证
|
||||
|
||||
- **进程绝不挂死**:双重超时保护,极端情况下最多11秒内必定退出
|
||||
- **信号处理安全**:杜绝信号处理函数中的死锁风险
|
||||
- **资源正确清理**:acceptor→WS客户端→ioc→worker线程 有序关闭
|
||||
- **SIGPIPE防护**:写关闭连接不再导致进程意外终止
|
||||
|
||||
### 修改文件
|
||||
|
||||
- `gateway/src/main.cpp` - 信号处理安全重构、超时保护、独立服务器线程
|
||||
- `libhttp/src/HttpServer.cpp` - 改进stop()流程顺序
|
||||
- `libhttp/src/HttpServer_Listener.cpp` - 修复无限递归bug,新增close()
|
||||
- `libhttp/inc/us/detail/HttpServerImpl.hpp` - 添加Listener::close()声明
|
||||
|
||||
---
|
||||
|
||||
**审计人**: AI Security Auditor
|
||||
**复审日期**: 2026-08-07
|
||||
**下一次复审建议**: 修复P1问题后或代码有重大变更时
|
||||
@@ -1,265 +0,0 @@
|
||||
# gateway 模块安全审计报告(2026-08-09 增量复审)
|
||||
|
||||
**审计日期**: 2026-08-09
|
||||
**审计范围**: `src/main.cpp`(v0.8.0,2526 行全量)、`inc/DbC.hpp`、`inc/LogC.hpp`、`inc/Conf.hpp`、`inc/ExpBase.hpp`
|
||||
**基线文档**: [gateway_SECURITY_AUDIT_20260807.md](gateway_SECURITY_AUDIT_20260807.md)(P0 3/3 已修复、P1 8 项 56%、P2 6 项 33%)
|
||||
**审计方式**: 全量代码走查 + 实机黑盒实证(重建二进制 → 启动 8443 → curl 探针 12 组 → 对照基线做增量发现)
|
||||
**配套交付**: `tests/blackbox/test_gateway.py` 重写 + `helpers.py` 增强(见 §六)
|
||||
|
||||
---
|
||||
|
||||
## 代码修复记录
|
||||
|
||||
**修复日期**: 2026-08-09(审计当日)
|
||||
|
||||
| 编号 | 状态 | 代码位置 | 修复说明 |
|
||||
|------|------|----------|----------|
|
||||
| A1 | ✅ 已修复 | `src/main.cpp` 关闭流程 | 删除 else 分支重复的 `log_flush_thread.join()`(ready 时 async 已完成 join) |
|
||||
| A2 | ✅ 已修复 | `handleProxy` 白名单检查 | `throw std::runtime_error("403")` → `THROW_EX_CUSTOM(HttpException, 2303, "Forbidden: target not allowed", 403, "*")` |
|
||||
| A3 | ✅ 已修复 | `handleLogin`/`handleRegister`/`handleResetPassword` | 字段提取 `obj.at()` 包 try/catch,缺字段返回 400 |
|
||||
| A5 | ✅ 已修复 | `is_special_header()` | 比较前统一转小写,小写 `x-forwarded-for` 等变体不再绕过剥离 |
|
||||
| A6 | ✅ 已修复 | `handleRegister` CAPTCHA 校验 | `verify_captcha()`(内置存储)→ `verify_captcha_remote()`(captcha2),与 `/captcha/image` 渠道互通 |
|
||||
| A7 | ✅ 已修复 | `src/main.cpp` 内置验证码渠道 | 整体移除:`POST /captcha` 路由、`handleCaptcha`、`generate_captcha_text`/`verify_captcha`、`_CAPTCHA_STORE`/`_CAPTCHA_MTX`(净 -79 行);未注册路径按既有通配代理语义处理 |
|
||||
| A8 | ✅ 已修复 | `wrapperHttp` HttpException 分支 | 客户端消息从 `e.what()` 改为 `e.message()`,`[Http][Error NNNN]` 前缀仅保留在服务端 stderr/集中日志 |
|
||||
| A4 | 📝 文档化 | `README.md` 部署模式 B | 根因需 libhttp 向 handler 透传 TCP 对端地址(跨模块 API 变更),本轮以文档化闭环:README 模式 B 增加安全降级提示(伪造 XFF 可绕过限流,生产必须用模式 A) |
|
||||
|
||||
### 修复验证
|
||||
|
||||
| 验证项 | 结果 |
|
||||
|--------|------|
|
||||
| 编译(make,g++ -std=c++20) | ✓ 通过(仅 LogC.hpp 既有 snprintf 警告) |
|
||||
| 黑盒全量(test_gateway.py 34 断言) | ✓ **32 passed / 0 failed / 2 skipped**,锁定 A3 的两个红测试转绿 |
|
||||
| A1 关闭路径 | ✓ `kill` 后 "已退出" 正常打印,进程 Done 退出,无 core dump(两轮构建均复核) |
|
||||
| A2 | ✓ 代码路径确认(HttpException 经 wrapperHttp 映射 403;黑盒触达需有效 Token,待测试钩子) |
|
||||
| A8 实证 | ✓ `POST /login {}` → `{"success":false,"message":"Bad_Request"}`,无 `[Http][Error 2002]` 前缀 |
|
||||
| A7 实证 | ✓ `POST /captcha` 不再返回答案(落入通配代理语义,与其他未注册路径行为一致) |
|
||||
| 黑盒全量回归(修复 A6/A7/A8 后) | ✓ 32 passed / 0 failed / 2 skipped,无回归 |
|
||||
|
||||
---
|
||||
|
||||
## 一、审计结论摘要
|
||||
|
||||
本次为增量复审,基线已确认项不重复列出(复测见 §五)。新发现 8 项:
|
||||
|
||||
| 等级 | 数量 | 编号 |
|
||||
|------|------|------|
|
||||
| 🔴 P0 | 0 | — |
|
||||
| 🟠 P1 | 2 | A1(优雅关闭必崩溃)、A2(白名单拒绝返回 500 而非 403) |
|
||||
| 🟡 P2 | 4 | A3(缺字段 500)、A4(XFF 伪造绕过限流)、A5(头名大小写架空 P1-1 修复)、A6(双 CAPTCHA 渠道不互通) |
|
||||
| ⚪ P3 | 2 | A7(内置验证码接口明文给答案)、A8(内部错误码前缀外泄) |
|
||||
|
||||
**未发现新的 P0**。Token 验签链(RSA + 过期 + jti 黑名单 + 版本号)、PBKDF2(10 万轮 + 常数时间比较)、fail-closed 下游语义、防用户枚举 dummy 校验等核心机制实现正确。
|
||||
|
||||
---
|
||||
|
||||
## 二、P1 详细发现
|
||||
|
||||
### A1 [P1] 优雅关闭以崩溃收场:`log_flush_thread` double-join
|
||||
|
||||
**位置**: `src/main.cpp` L1066-1077
|
||||
|
||||
```cpp
|
||||
std::thread log_flush_thread([]() { logs().shutdown(); });
|
||||
auto future = std::async(std::launch::async, &std::thread::join, &log_flush_thread);
|
||||
if (future.wait_for(std::chrono::seconds(3)) == std::future_status::timeout) {
|
||||
log_flush_thread.detach();
|
||||
} else {
|
||||
log_flush_thread.join(); // ← async 已完成 join,此处二次 join
|
||||
}
|
||||
```
|
||||
|
||||
**根因**: `std::async(std::launch::async, &std::thread::join, &t)` 已在独立线程中执行完 `join()`;`wait_for` 返回 ready 后 else 分支再次 `t.join()`,此时 `joinable()==false` → 抛 `std::system_error`(EINVAL)→ 无人捕获 → `std::terminate` → SIGABRT。
|
||||
|
||||
**实证**:
|
||||
```
|
||||
$ kill <pid>
|
||||
terminate called after throwing an instance of 'std::system_error'
|
||||
what(): Invalid argument
|
||||
[1]+ Aborted (core dumped) nohup ./build/gateway.out
|
||||
```
|
||||
"已退出" 收尾日志不打印,进程以 core dump 结束。基线报告修复的"进程退出 bug"(0807 版)实际只修了一半:main 函数体能走完,但关闭路径最后一步仍崩溃。
|
||||
|
||||
**影响**: 每次正常关闭(SIGINT/SIGTERM)都产生 core dump;systemd 下触发不必要告警;关闭期日志是否落库无从保证。
|
||||
|
||||
**修复建议**: 删除 else 分支中重复的 `log_flush_thread.join()`(ready 即已 join 完毕)。
|
||||
|
||||
### A2 [P1] 白名单外 Target-Url 返回 500 而非文档宣称 403
|
||||
|
||||
**位置**: `src/main.cpp` L2401-2404(`handleProxy`)
|
||||
|
||||
```cpp
|
||||
if (!is_safe_target(target)) {
|
||||
throw std::runtime_error("403"); // 意图返回 403
|
||||
}
|
||||
```
|
||||
|
||||
**根因**: `std::runtime_error` 不是 `HttpException`,被 `wrapperHttp()`(L1288-1303)的 `std::exception` 兜底分支统一包装为 500 "Internal Server Error"。README 接口契约明确写"目标不在白名单 → 403"。
|
||||
|
||||
**实证局限**: 白名单检查在 Token 验签之后,无有效 Token 时只能走到 401,黑盒无法触达;但代码路径确凿(`runtime_error` 必经 500 兜底)。待测试钩子落地后补黑盒断言。
|
||||
|
||||
**影响**: 客户端无法区分"目标被拒"与"服务器故障";监控告警误报 500。
|
||||
|
||||
**修复建议**: 改为 `THROW_EX_CUSTOM(HttpException, 2303, "Forbidden", 403, "*")`,与 L2370 验签失败的风格一致。
|
||||
|
||||
---
|
||||
|
||||
## 三、P2 详细发现
|
||||
|
||||
### A3 [P2] 缺失 JSON 字段 → 500 而非 400
|
||||
|
||||
**位置**: `src/main.cpp` L1373-1374(`handleLogin`,同型问题遍布 `handleRegister`/`handleResetPassword`/`handleSendEmailCode` 等)
|
||||
|
||||
```cpp
|
||||
auto& obj = json.as_object();
|
||||
std::string username = obj.at("username").as_string().c_str(); // ← 在 try 块外
|
||||
std::string password = obj.at("password").as_string().c_str();
|
||||
```
|
||||
|
||||
**根因**: JSON 语法解析有 try/catch(解析失败 → 400),但随后的字段提取 `obj.at()` 在 try 之外,`std::out_of_range` 直达 `wrapperHttp` 兜底 → 500。
|
||||
|
||||
**实证**: `POST /login` body `{}` → **500**(应为 400 Bad Request)。已由新版黑盒测试 A5/A8 两个用例锁定(当前红,修复后转绿)。
|
||||
|
||||
**修复建议**: 各 handler 的字段提取统一包进 try/catch,缺字段返回 400;或提供 `get_or_throw_400(obj, key)` 辅助函数。
|
||||
|
||||
### A4 [P2] 独立部署模式 B 下限流可被 XFF 伪造绕过
|
||||
|
||||
**位置**: `src/main.cpp` L1353-1359(`handleLogin`,`handleRegister`/`handleCaptchaImage` 同型)
|
||||
|
||||
```cpp
|
||||
std::string client_ip = req.base()["X-Forwarded-For"]; // 直连模式下即客户端伪造值
|
||||
if (client_ip.empty()) client_ip = req.remote_ip();
|
||||
```
|
||||
|
||||
**根因**: 模式 A(WAF 前置)中 WAF 会覆写 XFF,此逻辑安全;模式 B(独立部署)中 XFF 完全由客户端控制,每个伪造 IP 都是独立限流桶 → 限流失效。基线报告 P1-1 修复的是"向下游注入可信 XFF",限流侧未覆盖。
|
||||
|
||||
**实证**:
|
||||
```
|
||||
XFF=10.1.1.1 连续 6 次 POST /login → 第 6 次 429
|
||||
XFF=10.1.1.2 立即再发 → 400(未限流,新桶)
|
||||
```
|
||||
|
||||
**影响**: 登录爆破/注册滥用防护在独立部署下可绕过(每次请求换一个 XFF 即无上限)。
|
||||
|
||||
**修复建议**: 二选一——(a) 配置项声明部署模式,模式 B 下限流 key 只用 `remote_ip()`;(b) README 明确"独立部署时安全降级:限流不可用"并建议仅在 WAF 后使用。
|
||||
|
||||
### A5 [P2] `is_special_header` 大小写敏感,伪造 XFF 绕过剥离,架空 P1-1 修复
|
||||
|
||||
**位置**: `src/main.cpp` L2464-2476
|
||||
|
||||
```cpp
|
||||
static bool is_special_header(const std::string& name) {
|
||||
return name == "Host" || name == "connection" || name == "Accept-Encoding" ||
|
||||
name == "X-Forwarded-For" || name == "X-Real-IP" || name == "Forwarded";
|
||||
}
|
||||
```
|
||||
|
||||
**根因**: Boost.Beast 的 `field.name_string()` 保留线上原始大小写。客户端发送小写 `x-forwarded-for: 6.6.6.6` 时不匹配 `"X-Forwarded-For"`,不被剥离;gateway 随后注入可信 XFF,下游收到**两个** XFF 头——若下游取第一个(常见实现),攻击者伪造值生效,基线 P1-1"注入可信 XFF"的修复被架空。
|
||||
|
||||
**实证**: 代码级确认(`name_string()` 行为见 Beast 文档;代理剥离逻辑 L2410-2420 逐头比对)。
|
||||
|
||||
**修复建议**: 比较前统一转小写(`boost::beast::iequals` 或手动 lowercase 后查集合),并将 `connection`/`keep-alive`/`transfer-encoding` 等逐跳头一并规范化。
|
||||
|
||||
### A6 [P2] 注册/登录 CAPTCHA 双渠道不互通,README 与实现不符
|
||||
|
||||
**位置**: `src/main.cpp` L1954(`handleRegister`)vs L1845-1887(`handleCaptchaImage`)
|
||||
|
||||
**根因**: 注册接口用 `verify_captcha()` 校验**内置** `_CAPTCHA_STORE`(内置 `GET/POST /captcha` 接口签发的 32 位 token);而前端实际通过 `/captcha/image` 代理拿的是 **captcha2** 签发的 64 位 token。两个存储互不通信——前端走完图形验证码流程后,注册提交 captcha_token 永远 invalid。`/login` 走的是远端 `verify_captcha_remote()`(captcha2),与 `/captcha/image` 配套,正常。
|
||||
|
||||
**实证**: 不带 captcha 字段 `POST /login` → 400 Bad_Request(README 称"可选",代码实为强制)。
|
||||
|
||||
**影响**: 注册流程的 CAPTCHA 环节对真实前端不可用(目前靠"不提交则跳过"绕过——即注册 CAPTCHA 形同虚设);文档误导。
|
||||
|
||||
**修复建议**: 统一走 `verify_captcha_remote()`;废弃内置验证码接口(见 A7);README 更新为"登录强制 CAPTCHA"。
|
||||
|
||||
---
|
||||
|
||||
## 四、P3 详细发现
|
||||
|
||||
### A7 [P3] 内置 `POST /captcha` 明文返回答案,人机验证无效化
|
||||
|
||||
**位置**: `src/main.cpp` 内置验证码接口(`_CAPTCHA_STORE` 配套路由)
|
||||
|
||||
**实证**:
|
||||
```
|
||||
$ curl -X POST :8443/captcha
|
||||
{"token":"...","text":"AmM2U"} ← 答案明文随 token 一起返回
|
||||
```
|
||||
|
||||
**叠加问题**: 该接口无速率限制、`_CAPTCHA_STORE` 无容量上限(内存 DoS 面)。
|
||||
|
||||
**修复建议**: 直接废弃内置验证码接口与 `_CAPTCHA_STORE`(captcha2 已全面接管),删除路由。
|
||||
|
||||
### A8 [P3] 错误消息携带内部错误码前缀外泄
|
||||
|
||||
**实证**: 错误响应体形如 `[Http][Error 2002] Bad_Request` —— 内部模块名与错误码编号随 4xx 返回客户端。
|
||||
|
||||
**评估**: 不构成路径/类型泄露(P0-2/P0-3 的脱敏基线未违反,16 条错误样本回归测试通过),但为攻击者提供内部结构测绘素材,且污染前端可直接展示的消息。
|
||||
|
||||
**修复建议**: `wrapperHttp` 返回客户端前剥离 `[模块][Error NNNN]` 前缀(完整信息保留在服务端日志)。
|
||||
|
||||
---
|
||||
|
||||
## 五、基线遗留项复测
|
||||
|
||||
| 编号 | 项 | 复测结果 |
|
||||
|------|----|----------|
|
||||
| P1-1 | 伪造 XFF 注入下游 | 修复已生效(标准大小写头被剥离),但**被 A5 架空** |
|
||||
| P2-3 | CORS 反射任意 Origin | ❌ 仍存在:`Origin: https://evil.example.com` 被原样反射进 ACAO |
|
||||
| — | 路径遍历防护 | ✅ `..`、`%2e`、`%2f`、`%5c` 四种变体全部 400 |
|
||||
| — | `/whoami` 无/伪造 Token | ✅ 恒 `{login:false}`,不泄露验签细节 |
|
||||
| — | `/admin/verify` fail-closed | ✅ 无/伪造 Token 均 401 |
|
||||
| — | `/captcha/image` 代理 | ✅ 200 + image/png + 64 位 hex token + `no-store` + 限流 10/min/IP |
|
||||
| — | 默认代理下游宕机语义 | ⚠️ static 宕机时默认路由 500(环境语义,非缺陷) |
|
||||
|
||||
---
|
||||
|
||||
## 六、黑盒测试补充(已落地)
|
||||
|
||||
### 6.1 测试套件脱节结论
|
||||
|
||||
- **test_gateway.py 旧版 10 场景已失效**:v0.8.0 后 /login 强制远端 CAPTCHA、/register 强制 email_code,无 OCR/收信能力的黑盒 happy path 全断;且无 db 清理,反复运行污染 Users 表
|
||||
- **test_captcha.py / test_smtp.py 形同虚设**:用 HTTP 测 pymsg/TCP 服务(captcha2:8009、smtp:8008),captcha 用例期望 32 位 token(captcha2 实为 64 位)
|
||||
- **可测试性根因**:认证 happy path 需要测试钩子——方案 a:captcha2/smtp 提供测试模式(固定验证码答案);方案 b:`gateway.ini` 增加 `[Security] captcha_required/email_code_required` 开关
|
||||
|
||||
### 6.2 本轮交付(P0 部分,无需钩子)
|
||||
|
||||
**`tests/blackbox/helpers.py` 增强(+48 行)**:
|
||||
|
||||
| 工具 | 用途 |
|
||||
|------|------|
|
||||
| `random_xff()` | 随机 XFF 隔离限流桶(利用 A4 行为,避免限流用例互斥) |
|
||||
| `assert_no_internal_leak()` | 错误响应脱敏回归(P0-2/P0-3 基线:无源码路径/C++ 类型名) |
|
||||
| `db_execute()` | pymsg 调 db 做测试数据准备/清理 |
|
||||
|
||||
**`tests/blackbox/test_gateway.py` 重写(34 断言 6 段)**:
|
||||
|
||||
| 段 | 内容 | 断言数 |
|
||||
|----|------|--------|
|
||||
| A | 输入校验新契约(强制 captcha/email_code、fail-closed 语义) | 8 |
|
||||
| B | `/whoami`、`/admin/verify`(含伪造 Token) | 4 |
|
||||
| C | `/captcha/image` 代理(200/PNG/64 位 token/no-store) | 4 |
|
||||
| D | 无 Token 401 + 路径遍历矩阵(原始请求行,`%2e/%2f/%5c` 变体) | 5 |
|
||||
| E | 限流全谱:登录 6th→429、注册 4th→429、发码 6th→429、验证码 11th→429、换 XFF 隔离 | 9 |
|
||||
| F | CORS 反射现状固化(P2-3 修复后翻转断言)+ 16 条错误样本脱敏回归 | 2 |
|
||||
| 尾 | 测试用户清理(db `like` 删除,旧版 `test_%` 污染仅报告) | 2 |
|
||||
|
||||
**实测结果**(gateway 实跑): `34 tests: 30 passed, 2 failed, 2 skipped`
|
||||
|
||||
- 2 个 FAIL 为刻意锁定 A3 的红测试(缺字段断言 400,当前实现 500),修复后自然转绿
|
||||
- 2 个 SKIP 为环境性(static 未运行;本地 db 无 Users 表——**本环境下注册写库必 500**,跑 P1 认证链路测试前需先执行 `db/create_sqlite3.sql` 建表)
|
||||
|
||||
### 6.3 后续清单(需钩子,P1/P2)
|
||||
|
||||
- 完整旅程(captcha → send_code → register → login → whoami → logout)、Cookie 链路(Set-Cookie 属性、代理注入 Authorization)、jti/版本号吊销、refresh_token、白名单 403(A2 转绿)、防枚举时序
|
||||
- test_captcha.py / test_smtp.py 重写为 pymsg 直连
|
||||
|
||||
---
|
||||
|
||||
## 七、修复建议优先级
|
||||
|
||||
| 优先级 | 项 | 说明 |
|
||||
|--------|----|------|
|
||||
| P1 | A1、A2 | double-join 删 3 行;A2 改抛 HttpException,均为小改动 |
|
||||
| P2 | A3、A5、A6、A4 | A3 修复后两个红测试转绿;A5 头名归一化;A6 统一走 captcha2;A4 配置或文档化 |
|
||||
| P3 | A7、A8 | 废弃内置验证码接口;错误消息剥离内部前缀 |
|
||||
| 配套 | 测试钩子 | captcha2/smtp 测试模式或 gateway.ini 开关,解锁 P1 测试清单 |
|
||||
Reference in new issue
Block a user