Files
ephron-ren-prd/2026-05-16-ephron-security-audit-round3.md
2026-05-16 12:54:27 +08:00

9.7 KiB
Raw Permalink Blame History

ephron.ren 2026-05-16 第三轮完整收口版安全审计

结论摘要

本轮在前两轮基础上继续完成:

  • 路由与攻击面清单化
  • 认证 / 权限 / CSRF 逐路由审计
  • 前端注入与渲染链回溯
  • 上传 / iframe / raw 渲染专项
  • 运行时 / 配置风险复核

当前总体判断

ephron.ren 不是“裸奔”状态,具备以下明确安全基线:

  • 各服务统一安全响应头在线上已生效
  • admin 页面普遍具备服务端鉴权与权限检查
  • 大部分 admin 写操作具备 CSRF 防护
  • blog 图片上传链具有权限、CSRF、大小限制、Pillow 重编码 WebP
  • auth 登录跳转链带 validate_redirect(),本轮未确认开放重定向成立
  • canvas /raw/{slug} 具备专用 CSP + SAMEORIGIN,当前不作为确认漏洞

但本轮也确认了数个真实安全问题,其中以 prompthome admin 最值得优先修复。


一、确认问题

1. 高危:prompt /api/test-connection 可匿名触发服务端外连SSRF / 网络探测面)

风险等级

高危

证据

源码证据

prompt/src/routes/api.py

  • 存在公开 POST /api/test-connection
  • 该接口直接接受客户端传入的 provider/base_url/api_key/model
  • 服务端随后执行:
    • POST {payload.base_url}/chat/completions
    • 并带上 Authorization: Bearer {payload.api_key}
  • 未见登录校验、权限校验、CSRF 约束

线上验证

非破坏性 POST 验证:

  • POST https://prompt.ephron.ren/api/test-connection
  • 使用 base_url=http://127.0.0.1:9
  • 线上返回 200,正文为:{"success":false,"error":"All connection attempts failed"}

这说明:

  • 接口可匿名访问
  • 服务端实际尝试了到指定地址的连接
  • 攻击者可以利用该接口探测不同目标的网络可达性/响应差异

影响

  • 可被匿名用于对内网/本机/特定外部地址做探测
  • 若未来错误回显更详细,可能进一步暴露内部网络结构或上游服务信息
  • 若某些环境允许访问元数据地址/内部控制面,风险会进一步升级

修复建议

  1. 该接口至少改为 管理员鉴权,不要匿名暴露
  2. 严格限制 base_url
    • 白名单域名/白名单 provider
    • 禁止 localhost / 127.0.0.0/8 / RFC1918 内网 / link-local / metadata 地址
  3. 后端不要把原始网络错误细节直接返回给前端
  4. 增加审计日志与更严格限流

2. 中危:prompt 调试测试链存在潜在 HTML 注入 / XSS

风险等级

中危

证据

源码链路

prompt/static/js/test-prompt.js

  • event.type === "error" 时:
    • contentDiv.innerHTML = <div class="error">${event.detail}</div>
  • 普通异常分支也有:
    • contentDiv.innerHTML = <div class="error">${error.message}</div>
  • 流式内容渲染:
    • contentDiv.innerHTML = this.renderMarkdown(fullContent)
    • renderMarkdown() 调用 marked.parse(text)

prompt/src/services/llm.py / prompt/src/routes/api.py

  • 上游错误文本、连接失败信息、响应文本会被包装进错误链并送回前端

风险判断

目前已确认:

  • 前端把错误详情直接写入 innerHTML
  • 调试链中存在“外部返回内容 / 错误文本 → 前端 DOM”的直接通路

尚未在本轮做破坏性利用,但这已经足够构成潜在 XSS 风险,尤其在:

  • 上游 provider 返回可控 HTML 错误页
  • 配置中允许用户指定自定义 base_url
  • 管理员在调试页里测试恶意配置

影响

  • 可能形成管理员面自触发 / 存储后触发的 XSS
  • 结合当前 CSP 中保留 'unsafe-inline',利用门槛被进一步降低

修复建议

  1. 错误内容改用 textContent 输出,禁止 innerHTML 拼接错误文本
  2. 若要渲染 markdown先做服务端/前端 HTML sanitize
  3. 后端不要透传原始 response.text 或上游 HTML 错误页
  4. 若确需展示富文本,使用可信白名单 sanitizer

3. 中危:prompt 登录态写接口存在缺 CSRF 的静态确认问题

风险等级

中危

证据

源码证据

prompt/src/routes/api.py 中,以下接口使用 ephron_auth 登录态校验,但未见 verify_csrf_token(...)

  • /api/prompts/{key}/test
  • /api/prompts/{key}/test/save-example

其中:

  • /test 属于登录态调试触发接口
  • /test/save-example 明显属于状态变更接口,会保存测试样例

线上验证

匿名访问两者均返回 401 需要登录,说明它们依赖 cookie 登录态。由于本轮未使用真实登录态做有状态利用,因此把“缺 CSRF”认定为静态确认缺陷,而不是已完成线上利用。

影响

  • 若管理员/登录用户处于活跃登录态,第三方站点可诱导浏览器跨站发起状态变更请求
  • /test/save-example 的风险高于纯测试接口,因为它会修改数据

修复建议

  1. 对所有 cookie 登录态写接口统一加入 CSRF 校验
  2. 建议抽象通用依赖,避免后续再漏
  3. 将“登录态 + POST + 改数据”作为测试基线补自动化测试

4. 中危:home admin 编辑器存在存储型 / 自触发型 DOM XSS

风险等级

中危

证据

服务端链路

home/src/routes/admin.py

  • 后端把数据库中的内容直接:
    • json.dumps(content, ensure_ascii=False)
    • 传入模板变量 content_json

home/src/services/content.py

  • save_draft(content, uid) / publish_content(content, uid)
  • 将传入 JSON 内容原样存库,未做字段级 HTML 清洗或输出编码

前端链路

home/templates/admin/index.html

  • const initialContent = JSON.parse({{ content_json | tojson }});
  • 随后大量使用:
    • list.innerHTML += ... value="${exp.title}" ...
    • value="${proj.title}"
    • textarea ...>${proj.bullets.join('\n')}</textarea>
    • value="${skill.category}"
  • 即:数据库中的内容被重新拼进 HTML/属性值/textarea 内容
  • 未见对引号、标签、事件属性进行转义

风险判断

这意味着:

  • 只要后台内容中包含特制字符串(如引号、闭合标签、事件属性片段)
  • 管理员再次打开编辑页时,就可能在其浏览器中落地为 DOM XSS

这更偏向:

  • 管理员面存储型 XSS
  • 自触发型 XSS(管理员自己保存恶意内容,再次进入编辑器触发)

影响

  • 劫持管理员会话上下文
  • 发起后台操作、窃取页面可见数据
  • 若存在其他高权限操作页面,可进一步横向利用

修复建议

  1. 禁止把不可信数据直接拼进 innerHTML
  2. 改为:
    • document.createElement(...)
    • input.value = ...
    • textarea.value = ...
    • textContent = ...
  3. 若必须模板化拼接,先做严格 HTML attribute escaping
  4. home admin 补一组带引号/标签 payload 的自动化回归测试

二、已复核但不应误报的点

1. 统一安全响应头:线上已确认存在

线上验证确认以下响应头存在于公开页与 API

  • Content-Security-Policy
  • X-Frame-Options
  • X-Content-Type-Options: nosniff
  • Referrer-Policy: strict-origin-when-cross-origin

因此“整站缺安全头”不成立

2. canvas /raw/{slug} 当前不作为确认漏洞

源码显示:

  • X-Frame-Options: SAMEORIGIN
  • raw 专用 CSPframe-ancestors 'self'
  • 用于受限 iframe 预览

虽然它允许 same-origin 预览,理论上仍值得长期关注,但本轮证据不足以确认越界利用,不应草率报漏洞。

3. blog Markdown 正文链当前不报直接存储型 XSS

blog/src/utils/markdown.pyMarkdown 渲染后经过 bleach.clean(...) 白名单净化,因此“博客正文可直接任意 HTML 执行”这一说法在当前证据下不成立

4. blog 图片上传链当前未见明显任意文件上传

已确认:

  • 权限检查
  • CSRF 校验
  • 大小限制
  • Pillow 解码并重编码为 WebP
  • 写入固定上传目录

本轮未发现明显的任意脚本上传路径。

5. auth 开放重定向本轮未确认成立

auth 登录成功跳转链调用了 validate_redirect(),因此不能把入口上可传 redirect 参数直接等同于“已存在开放重定向漏洞”。


三、配置风险 / 需持续关注

1. 开发环境默认值过于宽松(配置陷阱)

多个服务存在类似模式:

  • IS_DEVELOPMENT = os.getenv("ENVIRONMENT", "development") == "development"
  • docs_url="/docs" if IS_DEVELOPMENT else None
  • error_message = str(exc) if IS_DEVELOPMENT else None

这意味着:

  • 如果部署时漏设 ENVIRONMENT,服务会默认按 development 行为启动
  • 可能意外暴露 /docs
  • 500 页面可能泄露异常细节

判断

当前这是配置风险,不是已确认线上漏洞;但优先级应高,因为它属于“部署一失误就暴露”的类型。

建议

  • 默认值改为 production
  • 生产环境必须显式开启 development而不是反过来
  • 启动时对 ENVIRONMENT 做严格枚举校验

四、优先修复顺序

P0

  1. 下线或严格鉴权 prompt /api/test-connection
  2. 阻断 home admininnerHTML += 数据拼接

P1

  1. prompt 调试链的错误输出 / markdown 渲染 XSS 面
  2. prompt 登录态写接口补 CSRF

P2

  1. 调整所有服务的环境默认值为 production
  2. 为以上问题补回归测试

五、本轮结论

如果问“现在是否可以说这个项目已经完整安全了”,答案仍然是:不能。但和前两轮相比,现在可以更明确地说:

  • 项目具备基本安全基线
  • 不是全面失守
  • 但确实存在若干已确认真实问题,尤其集中在 prompthome admin
  • 这些问题已足以支持继续进入“修复 PR / 回归验证”阶段,而不是只停留在观察层