diff --git a/docs/security/gateway_SECURITY_AUDIT_20260801.md b/docs/security/gateway_SECURITY_AUDIT_20260801.md index e95d8ec..74dc945 100644 --- a/docs/security/gateway_SECURITY_AUDIT_20260801.md +++ b/docs/security/gateway_SECURITY_AUDIT_20260801.md @@ -20,7 +20,7 @@ ## 🔴 P0 — 严重问题 ### P0-1: RSA 私钥存储在仓库中/硬编码 -- **位置**: `main.cpp` L669 `_KEYGEN = std::make_unique();` +- **位置**: `main.cpp` L681 `_KEYGEN = std::make_unique();` - **当前状态**: ⚠️ 部分修复 - 密钥由 `MyRSA::Generator` 在内存中运行时生成,**未硬编码在源码中**,**未随仓库分发** - 每个进程实例独立生成密钥对,满足"每个部署独立密钥"要求 @@ -37,12 +37,12 @@ | # | 问题 | 状态 | 说明 | |---|------|------|------| -| P1-1 | Token 吊销机制缺失 | ❌ 未修复 | `handleLogout` (L1160) 仅清除 Cookie,无服务端黑名单/吊销列表;修改密码/注销后旧 Token 仍有效直到过期 | -| P1-2 | 登录失败次数限制 | ⚠️ 部分修复 | IP 级限流已实现 (`_LOGIN_RATE`, L998, 5次/300秒),但**无账户级锁定**(同一邮箱多次失败不触发账户锁定) | -| P1-3 | 上游代理超时配置 | ❌ 未修复 | `handleProxy` (L1712) 中 `HttpClient` 未显式设置超时,依赖 libhttp 默认值 | +| P1-1 | Token 吊销机制缺失 | ❌ 未修复 | `handleLogout` (L1184) 仅清除 Cookie,无服务端黑名单/吊销列表;修改密码/注销后旧 Token 仍有效直到过期 | +| P1-2 | 登录失败次数限制 | ⚠️ 部分修复 | IP 级限流已实现 (`_LOGIN_RATE` L371,`check_and_record(ip,5,300)` L1022),但**无账户级锁定**(同一邮箱多次失败不触发账户锁定) | +| P1-3 | 上游代理超时配置 | ❌ 未修复 | `handleProxy` (L1947) 中 `HttpClient` 未显式设置超时,依赖 libhttp 默认值 | | P1-4 | C++ HTTP 解析器缓冲区溢出审计 | ❌ 未修复 | libhttp 库(`src/shared/`)需全面 fuzz 测试 | | P1-5 | 请求体大小限制 | ❌ 未修复 | 未配置显式请求体大小上限(如 10MB),依赖 Boost.Beast 默认行为 | -| P1-6 | 反向代理响应无大小限制 | ❌ 未修复 | `handleProxy` 回调中拼接下游响应 body 无上限,下游(static/dnfs)返回超大响应可导致 OOM(全局审计 P1-1) | +| P1-6 | 反向代理响应无大小限制 | ❌ 未修复 | `handleProxy` 回调 L2069-2074 拼接下游响应 body 无上限(`res.body() += ret`,带 `FIXME: 增加长度限制`),下游(static/dnfs)返回超大响应可导致 OOM(全局审计 P1-1) | --- @@ -50,10 +50,10 @@ | # | 问题 | 状态 | 说明 | |---|------|------|------| -| P2-1 | 错误页面泄露版本号/堆栈信息 | ✅ 已修复 | `wrapperHttp` (L919-940) 异常捕获返回统一 `"Internal Server Error"`,详细错误仅输出到 stderr,不向客户端暴露 | -| P2-2 | HTTPS TLS 版本和密码套件安全 | ❌ 未修复 | `main.cpp` L664-668 TLS 模式下仅指定证书文件,未显式限定 TLS 版本和 CipherSuites | -| P2-3 | CORS 通配(反射任意 Origin) | ❌ 未修复 | `_CORS_ORIGINS` 白名单 (L593) 已读取但**未用于响应头**;libhttp OPTIONS 预检直接反射请求 Origin(`HttpServer_Request.cpp` L73-74),等于允许任意来源(全局审计 P2) | -| P2-4 | HTTP 服务器无超时配置 | ❌ 未修复 | 未设置 Read/Write/Idle 超时,依赖 libhttp 默认行为,存在 Slowloris/连接耗尽风险(全局审计 P2) | +| P2-1 | 错误页面泄露版本号/堆栈信息 | ✅ 已修复 | `wrapperHttp` (L909-940) 异常捕获返回统一 `"Internal Server Error"`;`ExceptionBase::build_message()` 中文件/行号/函数名已注释(`ExpBase.hpp` L173-197),`what()` 仅含业务消息,不向客户端暴露源码位置 | +| P2-2 | HTTPS TLS 版本和密码套件安全 | ❌ 未修复 | `main.cpp` L677 TLS 模式下仅指定证书文件,未显式限定 TLS 版本和 CipherSuites | +| P2-3 | CORS 通配(反射任意 Origin) | ❌ 未修复 | **根因**:`_CORS_ORIGINS` 白名单 (L593) 已读取但**从未用于响应头**——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) | --- @@ -63,15 +63,15 @@ 2. ✅ **RSA Token 签名**: Token 使用 RSA 私钥签名,防伪造 (`main.cpp` L1112-1130) 3. ✅ **反向代理架构**: 统一入口,隐藏后端服务拓扑 4. ✅ **速率限制**: 多层防护(IP 级 + 邮箱级双窗口限流、60秒冷却、失败锁定) -5. ✅ **防用户名枚举**: 用户不存在时执行 dummy PBKDF2 校验拉平响应时序 (`main.cpp` L1078-1083) +5. ✅ **防用户名枚举**: 用户不存在时执行 dummy PBKDF2 校验拉平响应时序 (`main.cpp` L1102-1108) 6. ✅ **WAF 前置防护**: Go WAF 处理 SQLi/XSS/CC/协议解析 7. ✅ **C++ 高性能实现**: 避免 Python GIL 瓶颈,抗并发能力强 8. ✅ **依赖审计**: 使用项目自带的 libcrypt 加密库(PBKDF2、RSA) -9. ✅ **路径遍历防护**: `is_safe_target()` 校验请求路径,拒绝 `..`、空字节、URL 编码遍历 (`main.cpp` L1926-1952) -10. ✅ **X-Forwarded-For 可信注入**: 剥离客户端伪造的 IP 头后由 gateway 主动设置 (`main.cpp` L1853-1863) -11. ✅ **优雅关闭**: SIGINT/SIGTERM 信号处理 (`main.cpp` L465-479) -12. ✅ **Token 刷新机制**: 支持宽限期刷新 + 次数上限 + 限流 (`main.cpp` L1302-1453) -13. ✅ **CAPTCHA 强制校验**: 登录/注册/发送验证码均需 CAPTCHA,对接 captcha 服务 (`main.cpp` L426-442) +9. ✅ **路径遍历防护**: `is_safe_target()` 校验请求路径,拒绝 `..`、空字节、URL 编码遍历 (`main.cpp` L2161-2186) +10. ✅ **X-Forwarded-For 可信注入**: 剥离客户端伪造的 IP 头后由 gateway 主动设置 (`main.cpp` L2088-2098, `is_special_header` L2126-2138) +11. ✅ **优雅关闭**: SIGINT/SIGTERM 信号处理 (`main.cpp` L484-490) +12. ✅ **Token 刷新机制**: 支持宽限期刷新 + 次数上限 + 限流 (`main.cpp` L1339-1450) +13. ✅ **CAPTCHA 强制校验**: 登录/注册/发送验证码均需 CAPTCHA,对接 captcha 服务 (`main.cpp` L438, 调用点 L1044/1671/1784/1905) 14. ✅ **异步日志**: 批量推送 + fail-safe,db 不可用时静默丢弃 (`LogC.hpp`) --- @@ -117,26 +117,26 @@ | 编号 | 状态 | 代码位置 | 验证说明 | |------|------|----------|----------| -| P0-1 | ⚠️ 部分 | `main.cpp` L669 | `MyRSA::Generator()` 运行时生成,非硬编码;但无持久化,重启后 Token 全部失效 | -| P1-1 | ❌ 未修复 | `main.cpp` L1160-1174 | `handleLogout` 仅清除 Cookie,无 Token 黑名单/吊销机制 | -| P1-2 | ⚠️ 部分 | `main.cpp` L998 | IP 限流已实现 (5次/300s),无账户级锁定 | -| P1-3 | ❌ 未修复 | `main.cpp` L1712-1889 | `handleProxy` 中 `HttpClient` 未显式设置超时 | +| P0-1 | ⚠️ 部分 | `main.cpp` L681 | `MyRSA::Generator()` 运行时生成,非硬编码;但无持久化,重启后 Token 全部失效 | +| P1-1 | ❌ 未修复 | `main.cpp` L1184-1198 | `handleLogout` 仅清除 Cookie,无 Token 黑名单/吊销机制 | +| P1-2 | ⚠️ 部分 | `main.cpp` L371, L1022 | IP 限流已实现 (5次/300s),无账户级锁定 | +| P1-3 | ❌ 未修复 | `main.cpp` L1947-2124 | `handleProxy` 中 `HttpClient` 未显式设置超时 | | P1-4 | ❌ 未修复 | `src/shared/` | libhttp 库未进行 fuzz 测试 | | P1-5 | ❌ 未修复 | `main.cpp` | 未设置请求体大小上限 | -| P1-6 | ❌ 未修复 | `main.cpp` `handleProxy` 回调 | 拼接下游响应 body 无上限,超大响应可 OOM | -| P2-1 | ✅ 已修复 | `main.cpp` L919-940 | 异常统一返回 `"Internal Server Error"`,不泄露类型/堆栈 | -| P2-2 | ❌ 未修复 | `main.cpp` L664-668 | TLS 模式仅指定证书,未限定版本和密码套件 | -| P2-3 | ❌ 未修复 | `main.cpp` L593, libhttp `HttpServer_Request.cpp` L73-74 | `_CORS_ORIGINS` 白名单读取但未用于响应头;OPTIONS 预检反射任意 Origin | -| P2-4 | ❌ 未修复 | `main.cpp` | 未设置 HTTP 服务器 Read/Write/Idle 超时 | +| P1-6 | ❌ 未修复 | `main.cpp` L2069-2074 | `handleProxy` 回调 `res.body() += ret` 拼接下游响应 body 无上限,超大响应可 OOM | +| P2-1 | ✅ 已修复 | `main.cpp` L909-940, `ExpBase.hpp` L173-197 | 异常统一返回 `"Internal Server Error"`;`build_message()` 已注释文件/行号/函数名,不泄露源码位置 | +| P2-2 | ❌ 未修复 | `main.cpp` L677 | TLS 模式仅指定证书,未限定版本和密码套件 | +| P2-3 | ❌ 未修复 | `main.cpp` L593, 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` L1926-1952 | `is_safe_target()` 拒绝 `..`、空字节、URL 编码遍历 | -| XFF 可信注入 | `main.cpp` L1853-1863 | 剥离客户端伪造 IP 头后主动设置可信 XFF | -| 优雅关闭 | `main.cpp` L465-479 | SIGINT/SIGTERM 信号处理 | -| 防用户枚举 | `main.cpp` L1078-1083 | 用户不存在时执行 dummy PBKDF2 拉平响应时序 | -| 密码强度校验 | `main.cpp` L148-181 | 8-128字符,至少3种字符类别 | -| 邮箱双窗口限流 | `main.cpp` L1652-1660 | 3次/小时 + 10次/天/邮箱 | +| 路径遍历防护 | `main.cpp` L2161-2186 | `is_safe_target()` 拒绝 `..`、空字节、URL 编码遍历 | +| XFF 可信注入 | `main.cpp` L2088-2098, L2126-2138 | `is_special_header` 剥离客户端伪造 IP 头后主动设置可信 XFF | +| 优雅关闭 | `main.cpp` L484-490 | SIGINT/SIGTERM 信号处理 | +| 防用户枚举 | `main.cpp` L1102-1108 | 用户不存在时执行 dummy PBKDF2 拉平响应时序 | +| 密码强度校验 | `main.cpp` L146-176 | 8-128字符,至少3种字符类别 | +| 邮箱双窗口限流 | `main.cpp` L1677-1681 | 3次/小时 + 10次/天/邮箱 | | 异步日志 fail-safe | `LogC.hpp` L210-213 | db 不可用时静默丢弃,绝不影响业务 | diff --git a/gateway/README.md b/gateway/README.md index 844dafa..347a3af 100644 --- a/gateway/README.md +++ b/gateway/README.md @@ -13,7 +13,7 @@ UniServe Framework 的应用层网关,C++20 实现。 - **邮箱验证码**:`/register/send_code` 发送 6 位验证码邮件(10 分钟有效);注册时校验,一次性消费。 - **速率限制**:登录 5 次/5 分钟、注册 3 次/10 分钟、发验证码 5 次/10 分钟(IP 级)+ 3 次/小时/10 次/天(邮箱级),按 `X-Forwarded-For` 客户端 IP 计数(由 WAF 注入),超限一律返回 429。 - **邮件轰炸防护**:`/register/send_code` 强制 CAPTCHA 校验 + 邮箱级双窗口限流(3 次/小时 + 10 次/天),smtp 层兜底同样限流 + 全局熔断(默认 100 封/小时,可配)。 -- **OPTIONS 预检**:通配 `*` 返回 CORS 头,允许跨域。 +- **OPTIONS 预检**:由 libhttp 处理,`Access-Control-Allow-Origin` 反射请求的 Origin 头(任意来源均被放行);`CORS.allowed_origins` 白名单已读取但尚未用于预检响应(见审计 P2-3)。 ## 安全特性 @@ -147,7 +147,7 @@ Target-Url: http://127.0.0.1:9200 // 可选,缺省取 Proxy.default ### `OPTIONS *` -返回 CORS 预检响应:`Access-Control-Allow-Origin: *`、`Allow-Methods: GET, POST, OPTIONS`、`Max-Age: 86400`。 +返回 CORS 预检响应:`Access-Control-Allow-Origin` 反射请求 Origin 头(等价于允许任意来源,见审计 P2-3)、`Access-Control-Allow-Methods: GET, POST, OPTIONS`、`Max-Age: 86400`。 ### WebSocket 代理(`[WsProxy]` 配置的路径) @@ -202,7 +202,7 @@ gateway 配置采用**两层加载模式**,与其他 Python 服务(dnfs/db/s - `Proxy.closed`:仅文档记录用途,实际靠"不在 opened 中即拒绝"实现隔离。 - `Token.*`:Token 有效期(`keep_time`)、刷新宽限期倍数(`refresh_grace_multiplier`)、最大刷新次数(`max_refresh`)。 - `Admin.users`:管理员白名单(分号分隔邮箱),登录命中时 Token payload 写入 `is_admin=true`。 -- `CORS.allowed_origins`:允许跨域的 Origin 白名单(分号分隔)。 +- `CORS.allowed_origins`:允许跨域的 Origin 白名单(分号分隔)。⚠️ 当前已读取但**尚未用于预检响应**——OPTIONS 预检由 libhttp 反射请求 Origin,白名单暂不生效(见审计 P2-3)。 - `WsProxy.routes`:WebSocket 代理路由,格式 `/path=ws://host:port/target`,分号分隔。 - `Downstream.*`:下游 pymsg 服务地址(db/captcha/smtp),可被 `DB_HOST`/`DB_PORT`、`CAPTCHA_HOST`/`CAPTCHA_PORT`、`SMTP_HOST`/`SMTP_PORT` 环境变量覆盖。