# 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