From 2f6209d94a19a536f6b14ff8bc536fcaeab15efd Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Sat, 16 May 2026 12:24:36 +0800 Subject: [PATCH] Add ephron.ren security audit PRD (2026-05-16) --- ...026-05-16-ephron-ren-security-audit-prd.md | 381 ++++++++++++++++++ 1 file changed, 381 insertions(+) create mode 100644 security-audits/2026-05-16-ephron-ren-security-audit-prd.md diff --git a/security-audits/2026-05-16-ephron-ren-security-audit-prd.md b/security-audits/2026-05-16-ephron-ren-security-audit-prd.md new file mode 100644 index 0000000..9954094 --- /dev/null +++ b/security-audits/2026-05-16-ephron-ren-security-audit-prd.md @@ -0,0 +1,381 @@ +# ephron.ren 全站安全审计 PRD + +- 日期:2026-05-16 +- 审计方式:源码静态审计 + 浏览器验证 + HTTP 探测 + 安全边界内渗透式验证 +- 审计对象:auth / blog / canvas / prompt / home / shared +- 代码基线:`ephron_ren/ephron.ren` commit `bca0e08efdd33de85550680496528c40588a7501` + +## 一、结论摘要 + +本轮无法证明“整个站点已经安全”,但已经可以确认: + +1. 项目具备一套**基础安全基线**: + - 服务端 admin 鉴权 + - 权限检查 + - CSRF 防护(大部分后台写接口) + - 统一安全头 + - 登录限流 / 若干业务限流 + - Cookie 基本属性(HttpOnly / SameSite / Secure) + +2. 但本轮也发现了**至少 2 个可确认安全漏洞**,其中 1 个优先级较高: + - `prompt` 存在**匿名 SSRF 探针接口** + - `prompt` 存在**已登录态下缺少 CSRF 的写/调用接口** + +3. 此外还有一些**中低风险加固项**: + - CSP 允许 `unsafe-inline`,显著削弱 XSS 防线 + - logout 设计允许 GET/无 CSRF,存在强制登出面 + - 若干 prompt 调试/测试能力暴露边界偏宽 + +因此,当前状态不能下“已确保项目安全”的结论。更准确的说法是: + +> **站点不是明显裸奔,有基础防护,但仍存在已确认漏洞,尚未达到可宣称“安全”的状态。** + +--- + +## 二、审计范围 + +### 覆盖内容 +- 认证 / 授权 / 会话 / Cookie +- CSRF +- 路由保护与 admin 边界 +- 重定向与登录回跳 +- XSS / Markdown / HTML 渲染 +- 上传 +- Prompt 调试与 provider 调用链 +- service token / service API 信任边界 +- 安全头 / 限流 +- 公开 HTTP 行为与浏览器链路 + +### 未覆盖或仅部分覆盖 +- 登录后真实管理员态的全量人工操作验证 +- 大规模自动目录爆破 / 口令喷洒 / 破坏性测试 +- 依赖库 CVE 全量 SBOM 扫描 +- 生产基础设施层(Nginx/WAF/容器/DB/内网 ACL) +- 外部云资源 / 内网元数据可达性验证 + +--- + +## 三、已确认漏洞 + +## 漏洞 1:Prompt 存在匿名 SSRF 探针接口 + +### 级别 +高 + +### 位置 +- `prompt/src/routes/api.py:272-336` +- 前端调用入口:`prompt/templates/admin/settings.html:601-611` + +### 问题描述 +`POST /api/test-connection` 允许调用方提交: +- `provider` +- `base_url` +- `api_key` +- `model` + +后端会直接基于传入的 `base_url` 向外发起 HTTP 请求,但该接口代码中**没有任何登录校验、权限校验、CSRF 校验**。 + +这意味着任意未登录访问者都可以把这个接口当作: +- 服务端网络可达性探针 +- SSRF 跳板 +- 响应探测器 + +### 代码证据 +`prompt/src/routes/api.py` +- `272`: `@router.post("/test-connection")` +- `273`: `async def test_connection(payload: TestConnectionRequest):` +- 未见 `get_auth_user(...)` +- 未见 `require_admin_role(...)` +- 未见 `verify_csrf_token(...)` +- `281`: `f"{payload.base_url}/v1/messages"` +- `309`: `f"{payload.base_url}/chat/completions"` + +### 动态验证证据 +未登录直接请求: + +```http +POST https://prompt.ephron.ren/api/test-connection +Content-Type: application/json + +{ + "provider": "openai", + "base_url": "https://example.com", + "api_key": "test", + "model": "gpt-4o-mini" +} +``` + +返回: +- HTTP 200 +- body 含 `HTTP 405: ` + +说明: +1. 接口对匿名用户开放 +2. 后端确实向 `https://example.com/chat/completions` 发起了请求 +3. 目标响应片段被带回调用方 + +### 影响 +- 可用于探测服务器对外网络访问能力 +- 可作为 SSRF 探针访问内网/控制面/云元数据等目标(实际影响取决于部署网络) +- 可帮助攻击者绘制内网/出口访问面 +- 若未来返回信息更多,可能升级为更强的信息泄露 + +### 判定 +**已确认漏洞** + +### 修复目标 +- 该接口必须至少要求管理员鉴权 +- 同时建议加入 CSRF 防护 +- 对 `base_url` 做 allowlist 或严格限制为已保存 provider 域名 +- 不应把上游原始响应文本直接回显给前端 + +--- + +## 漏洞 2:Prompt 测试 / 保存示例接口缺少 CSRF 防护 + +### 级别 +中 + +### 位置 +- `prompt/src/routes/api.py:141-262` +- 前端调用:`prompt/static/js/test-prompt.js:104-108` + +### 问题描述 +以下两个接口依赖 Cookie 登录态,但没有做 CSRF 校验: + +1. `POST /api/prompts/{key}/test` +2. `POST /api/prompts/{key}/test/save-example` + +项目其它后台写操作普遍使用 `shared.csrf.verify_csrf_token(...)`,但这两处没有跟进。 + +### 代码证据 +#### 1) 测试接口 +`prompt/src/routes/api.py` +- `141`: `@router.post("/prompts/{key}/test")` +- `147`: `token = request.cookies.get("ephron_auth")` +- `148`: `user = get_auth_user(token)` +- 未见 `verify_csrf_token(...)` +- `186-214`: 会调用 `chat_completion(...)` + +#### 2) 保存示例接口 +`prompt/src/routes/api.py` +- `234`: `@router.post("/prompts/{key}/test/save-example")` +- `240`: `token = request.cookies.get("ephron_auth")` +- `241`: `user = get_auth_user(token)` +- 未见 `verify_csrf_token(...)` +- `252-257`: 调用 `update_prompt(...)` + +#### 前端调用方式 +`prompt/static/js/test-prompt.js` +- `104`: `fetch(`/api/prompts/${this.promptKey}/test`, {` +- `107`: 仅 `Content-Type: application/json` +- 未附带 CSRF header / token + +### 影响 +- 已登录用户若访问恶意站点,可能被跨站触发 prompt 测试调用 +- 可能造成: + - LLM token / 配额 / 费用消耗 + - prompt 调试链被滥用 + - 在有权限条件下篡改 example_input / example_output + +### 利用前提 +- 受害者已登录 +- 浏览器会带上站点 Cookie +- 目标接口接受跨站触发请求(是否可完整读取响应受 CORS 影响,但**触发请求本身**已足够构成 CSRF) + +### 判定 +**已确认漏洞** + +### 修复目标 +- 为这两个接口加入 CSRF 校验 +- 前端统一发送 `X-CSRF-Token` 或显式双提交 token +- 最好把“测试”和“保存示例”都收敛到 admin 路由保护体系下 + +--- + +## 四、低风险 / 加固项 + +## 加固项 A:CSP 允许 `unsafe-inline` + +### 级别 +中(加固项) + +### 位置 +- `shared/security_headers.py` +- 线上响应头验证已确认 + +### 证据 +源码与线上响应一致: +- `script-src 'self' 'unsafe-inline'` +- `script-src-elem 'self' 'unsafe-inline' https://cdn.jsdelivr.net` + +### 风险 +这不等于“当前已经有 XSS 漏洞”,但会显著削弱 CSP 对未来/潜在注入点的缓解能力。 + +### 建议 +- 逐步迁移到 nonce/hash 驱动 +- 消灭内联脚本 +- 降低 XSS 成功后的利用空间 + +--- + +## 加固项 B:多个 logout 允许 GET 或无 CSRF,存在强制登出面 + +### 级别 +低 + +### 位置 +- `home/src/routes/pages.py:56-70` +- 公开 `/logout` 设计在 blog/canvas/prompt 也存在类似跨站触发面(主要表现为清 Cookie + 跳登录) + +### 动态证据 +例如: +- `GET https://blog.ephron.ren/logout?next=/admin` +- 返回 `303`,并清空 `ephron_auth` + +### 风险 +攻击者可诱导用户点击或加载链接,导致用户被登出。通常不构成高危,但仍属于不必要的跨站状态变更面。 + +### 建议 +- logout 改为 POST-only +- 加入 CSRF +- 保守处理 next 参数 + +--- + +## 五、未证实但需继续关注的方向 + +## 1) Prompt provider / base_url 配置链的更深层 SSRF 面 +本轮已确认匿名 `test-connection` 是漏洞。除此之外,还应继续确认: +- 管理后台保存 provider 配置后,正式推理链是否也能被导向任意内网地址 +- 是否存在低权限用户能改该配置的路径 +- 部署网络是否允许访问敏感内网资源 + +## 2) Blog 上传链的资源消耗风险 +当前已看到: +- 扩展名限制 +- 原始大小限制 +- Pillow 解码后重编码为 WebP + +但还应继续验证: +- 是否可被特制图片触发高内存 / 高 CPU 消耗 +- 是否有 decompression bomb 风险 + +## 3) 登录后完整权限模型验证 +本轮未进行管理员真实登录态全量遍历,因此仍需补: +- 低权限用户是否能触达部分管理面 +- service token 是否在所有服务都被正确限制 +- 是否存在 IDOR / 越权修改 + +--- + +## 六、已确认的安全基线(正向结论) + +以下能力本轮已经确认存在: + +### 1) 服务端 admin 鉴权 +- blog / canvas / prompt / auth / home 都有服务端 admin 路由保护 +- 未登录访问 admin 会跳转至 auth 登录页 + +### 2) 权限校验 +- 多数写操作与管理页面都有 `require_permission(...)` +- home admin 明确拒绝 service token 访问 + +### 3) CSRF 防护 +- 大部分后台表单写操作已实现双提交 Cookie 风格防护 +- `shared/csrf.py` 逻辑完整 + +### 4) Cookie 基本属性合理 +动态观测与源码表明认证 Cookie 具备: +- `HttpOnly` +- `SameSite=Lax` +- `Secure` +- `Domain=.ephron.ren` + +### 5) 安全头存在 +线上已观测到: +- `Content-Security-Policy` +- `X-Frame-Options: DENY` +- `X-Content-Type-Options: nosniff` +- `Referrer-Policy: strict-origin-when-cross-origin` + +### 6) 登录与若干业务限流存在 +- auth 登录限流 +- blog 上传/评论/点赞限流 +- home/prompt 若干 admin 写接口限流 + +--- + +## 七、优先级建议 + +## P0 / 立即处理 +1. 关闭或修复 `POST /api/test-connection` + - 必须加鉴权 + - 必须限制可请求目标 + - 必须减少错误回显 + +## P1 / 尽快处理 +2. 为 prompt 测试与保存示例接口补齐 CSRF +3. 复查 prompt 调试 API 是否应全部纳入 admin 体系 + +## P2 / 短期加固 +4. 收紧 CSP,逐步去掉 `unsafe-inline` +5. 统一 logout 为 POST + CSRF +6. 对 SSRF 风险做更系统的目标 allowlist 策略 + +## P3 / 持续审计 +7. 登录后做完整权限/越权验证 +8. 做上传/渲染/资源消耗专项测试 +9. 做依赖漏洞与部署层基线检查 + +--- + +## 八、是否可以宣称“项目已安全” + +**不可以。** + +原因不是因为“完全没有安全措施”,而是: +- 已发现真实漏洞,且至少一个可被匿名利用 +- 登录后权限与部署层仍未完成全覆盖验证 +- 目前最多只能说“安全基线已具备,但尚未通过完整安全审计闭环” + +更合适的对外表述应为: + +> 项目已具备基础安全防护,但最新审计发现若干需要修复的问题;在完成修复与复测前,不建议宣称已完全安全。 + +--- + +## 九、后续验收标准 + +当以下条件全部满足后,才建议把本轮问题关闭: + +1. `/api/test-connection` 需要管理员身份,且目标地址受严格限制 +2. prompt 调试 / 保存示例相关接口补全 CSRF +3. 上述修复具备自动化测试覆盖 +4. 复测确认匿名 SSRF 不再成立 +5. 复测确认跨站无法再触发 prompt 测试/保存示例 +6. CSP 至少形成去 `unsafe-inline` 的迁移计划与落地路径 + +--- + +## 十、附:本轮代表性动态证据摘要 + +### 匿名 SSRF 探针 +- 请求:`POST https://prompt.ephron.ren/api/test-connection` +- 结果:HTTP 200 +- 现象:回显 `example.com` 页面片段,证明后端代发请求成立 + +### Prompt test 未登录受保护 +- 请求:`POST https://prompt.ephron.ren/api/prompts/nonexistent/test` +- 结果:HTTP 401 +- 说明:该接口不是匿名开放,但仍缺 CSRF + +### 各服务 admin 未登录重定向 +- `blog.ephron.ren/admin` → 302 到 auth 登录 +- `canvas.ephron.ren/admin` → 302 到 auth 登录 +- `prompt.ephron.ren/admin` → 302 到 auth 登录 + +### logout 跨站可触发 +- `GET https://blog.ephron.ren/logout?next=/admin` → 303 + 清 Cookie +- `GET https://canvas.ephron.ren/logout?next=/admin` → 303 + 清 Cookie +- `GET https://prompt.ephron.ren/logout?next=/admin` → 303 + 清 Cookie