Files
ephron-ren-prd/security-audits/2026-05-16-ephron-ren-security-audit-prd.md

11 KiB
Raw Blame History

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
  • 外部云资源 / 内网元数据可达性验证

三、已确认漏洞

漏洞 1Prompt 存在匿名 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"

动态验证证据

未登录直接请求:

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: <!doctype html><html ... Example Domain ...>

说明:

  1. 接口对匿名用户开放
  2. 后端确实向 https://example.com/chat/completions 发起了请求
  3. 目标响应片段被带回调用方

影响

  • 可用于探测服务器对外网络访问能力
  • 可作为 SSRF 探针访问内网/控制面/云元数据等目标(实际影响取决于部署网络)
  • 可帮助攻击者绘制内网/出口访问面
  • 若未来返回信息更多,可能升级为更强的信息泄露

判定

已确认漏洞

修复目标

  • 该接口必须至少要求管理员鉴权
  • 同时建议加入 CSRF 防护
  • base_url 做 allowlist 或严格限制为已保存 provider 域名
  • 不应把上游原始响应文本直接回显给前端

漏洞 2Prompt 测试 / 保存示例接口缺少 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 路由保护体系下

四、低风险 / 加固项

加固项 ACSP 允许 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 逻辑完整

动态观测与源码表明认证 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 / 尽快处理

  1. 为 prompt 测试与保存示例接口补齐 CSRF
  2. 复查 prompt 调试 API 是否应全部纳入 admin 体系

P2 / 短期加固

  1. 收紧 CSP逐步去掉 unsafe-inline
  2. 统一 logout 为 POST + CSRF
  3. 对 SSRF 风险做更系统的目标 allowlist 策略

P3 / 持续审计

  1. 登录后做完整权限/越权验证
  2. 做上传/渲染/资源消耗专项测试
  3. 做依赖漏洞与部署层基线检查

八、是否可以宣称“项目已安全”

不可以。

原因不是因为“完全没有安全措施”,而是:

  • 已发现真实漏洞,且至少一个可被匿名利用
  • 登录后权限与部署层仍未完成全覆盖验证
  • 目前最多只能说“安全基线已具备,但尚未通过完整安全审计闭环”

更合适的对外表述应为:

项目已具备基础安全防护,但最新审计发现若干需要修复的问题;在完成修复与复测前,不建议宣称已完全安全。


九、后续验收标准

当以下条件全部满足后,才建议把本轮问题关闭:

  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