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

278 lines
9.7 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`,当前**不作为确认漏洞**
但本轮也确认了数个真实安全问题,其中以 `prompt``home 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 专用 CSP`frame-ancestors 'self'`
- 用于受限 iframe 预览
虽然它允许 same-origin 预览,理论上仍值得长期关注,但**本轮证据不足以确认越界利用**,不应草率报漏洞。
## 3. blog Markdown 正文链当前不报直接存储型 XSS
`blog/src/utils/markdown.py`Markdown 渲染后经过 `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 admin``innerHTML +=` 数据拼接
### P1
3.`prompt` 调试链的错误输出 / markdown 渲染 XSS 面
4.`prompt` 登录态写接口补 CSRF
### P2
5. 调整所有服务的环境默认值为 production
6. 为以上问题补回归测试
---
## 五、本轮结论
如果问“现在是否可以说这个项目已经完整安全了”,答案仍然是:**不能**。但和前两轮相比,现在可以更明确地说:
- 项目具备基本安全基线
- 不是全面失守
- 但确实存在若干**已确认真实问题**,尤其集中在 `prompt``home admin`
- 这些问题已足以支持继续进入“修复 PR / 回归验证”阶段,而不是只停留在观察层