From 45cabb79c9876ed309a7ab7d1921afdf89859df6 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Sat, 16 May 2026 12:44:16 +0800 Subject: [PATCH] add second-round security audit for ephron.ren --- 2026-05-16-ephron-security-audit-round2.md | 318 +++++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 2026-05-16-ephron-security-audit-round2.md diff --git a/2026-05-16-ephron-security-audit-round2.md b/2026-05-16-ephron-security-audit-round2.md new file mode 100644 index 0000000..7612725 --- /dev/null +++ b/2026-05-16-ephron-security-audit-round2.md @@ -0,0 +1,318 @@ +# ephron.ren 2026-05-16 全面安全审计(第二轮) + +## 审计范围 + +本轮针对 `ephron.ren` 多服务单仓架构进行源码静态审计 + 线上非破坏性动态验证,覆盖: + +- `auth` +- `blog` +- `canvas` +- `home` +- `prompt` +- `shared` + +审计目标: + +1. 识别匿名可利用的高风险接口 +2. 核查 service token / admin / public 权限边界 +3. 识别 CSRF / XSS / SSRF / 重定向 / 信息泄露类问题 +4. 为修复提供可落地建议与优先级 + +--- + +## 结论摘要 + +本轮**确认 3 个需要处理的问题**,其中: + +- **高危 1 个**:`prompt` 存在匿名可利用的 SSRF / 外连探测接口 +- **中危 1 个**:`prompt` 调试流错误链存在潜在 HTML 注入 / XSS 风险 +- **中低危 1 个**:全局 CSP 仍允许 `'unsafe-inline'`,会显著降低前端注入后的利用门槛 + +同时复核了一项历史上容易误报的问题: + +- **开放重定向:本轮未确认成立**。`auth` 登录真正跳转前使用了 `validate_redirect()` 做同域/相对路径校验。 + +--- + +## 漏洞 1:`prompt /api/test-connection` 可匿名触发服务端外连(确认,高危) + +### 风险等级 + +高危 + +### 影响 + +匿名用户可直接调用 `https://prompt.ephron.ren/api/test-connection`,让服务端向任意 `base_url` 发起请求,并将网络行为结果或目标返回内容片段回显给调用者。这会带来: + +1. **SSRF / 内网探测**:可探测 `127.0.0.1`、RFC1918、169.254.169.254 等地址可达性 +2. **服务端网络画像泄露**:根据超时、拒绝连接、HTTP 响应差异判断服务端所处网络环境 +3. **目标响应内容回显**:接口会把 `response.text[:200]` 拼到返回 JSON 中,形成受控信息泄露 +4. **可被滥用为外连代理**:尽管当前固定了路径 `/chat/completions` 或 `/v1/messages`,仍可用于扫描和探测第三方或内部 HTTP 服务 + +### 源码证据 + +`prompt/src/routes/api.py` + +```python +@router.post("/test-connection") +async def test_connection(payload: TestConnectionRequest): +``` + +该接口没有任何登录、管理员、service token 或 CSRF 前置校验。 + +同时其直接使用用户提供的 `base_url` 发起外连: + +```python +response = await client.post( + f"{payload.base_url}/chat/completions", +``` + +或: + +```python +response = await client.post( + f"{payload.base_url}/v1/messages", +``` + +并把目标响应体前 200 字符回显: + +```python +return { + "success": False, + "error": f"HTTP {response.status_code}: {response.text[:200]}", +} +``` + +### 线上验证证据 + +匿名请求可直接成功命中该接口,且返回 200: + +- `base_url=https://example.com` → 返回 `HTTP 405` 与 HTML 片段 +- `base_url=http://127.0.0.1:9` → 返回 `All connection attempts failed` +- `base_url=http://169.254.169.254` → 返回 `连接超时` +- `base_url=http://10.255.255.1` → 返回 `连接超时` + +这说明: + +1. 接口对匿名用户开放 +2. 服务端确实对调用者指定地址发起了网络连接 +3. 不同目标的网络状态可以被外部观测和区分 + +### 复现方式 + +```bash +curl -s https://prompt.ephron.ren/api/test-connection \ + -H 'Content-Type: application/json' \ + -d '{ + "provider":"openai", + "base_url":"http://127.0.0.1:9", + "api_key":"x", + "model":"x" + }' +``` + +### 修复建议 + +按优先级建议: + +1. **立即加鉴权**:至少要求登录管理员;更稳妥的是仅限内部 admin UI 调用 +2. **禁止任意 base_url**:仅允许测试已保存配置中的候选地址,不能由请求体任意指定 +3. **加入 SSRF 防护**:拒绝私网、回环、链路本地、保留地址与裸 IP +4. **移除目标响应体回显**:只返回通用错误码/状态,不返回 `response.text` +5. **增加审计日志与限流**:记录调用者、目标 host、结果;对该接口做严格 rate limit + +--- + +## 漏洞 2:`prompt` 调试流错误消息存在潜在 HTML 注入 / XSS 链(确认,中危) + +### 风险等级 + +中危 + +### 影响 + +`prompt` 调试功能把上游 LLM/provider 返回的错误文本一路传到前端,并在浏览器中使用 `innerHTML` 渲染。如果攻击者能够控制: + +- provider 的错误内容 +- 或管理员配置的测试目标返回内容 +- 或某些异常消息中的 HTML 片段 + +则存在将 HTML/脚本片段注入到调试界面的风险。 + +这条链当前更像是**管理/调试面 XSS 风险**,而不是匿名立即拿下;但它与漏洞 1 组合后风险会放大:匿名攻击者可借 test-connection 验证回显形态,管理员再使用调试功能时可能触发注入。 + +### 源码证据 + +后端:`prompt/src/services/llm.py` + +```python +elif response.status_code != 200: + raise LLMError(f"API error: {response.text}", "api_error") +``` + +后端 SSE:`prompt/src/routes/api.py` + +```python +except LLMError as e: + yield f"data: {json.dumps({'type': 'error', 'detail': e.message, 'code': e.code})}\n\n" +except Exception as e: + yield f"data: {json.dumps({'type': 'error', 'detail': str(e), 'code': 'unknown'})}\n\n" +``` + +前端:`prompt/static/js/test-prompt.js` + +```javascript +contentDiv.innerHTML = `
${event.detail}
`; +``` + +以及: + +```javascript +contentDiv.innerHTML = `
${error.message}
`; +``` + +### 风险链说明 + +完整链路为: + +`provider response.text / exception text` +→ `LLMError.message` +→ SSE `event.detail` +→ 前端 `innerHTML` + +如果 `event.detail` 中包含 HTML,浏览器会按 HTML 解释,而不是纯文本显示。 + +### 修复建议 + +1. 前端错误展示统一改为 `textContent` +2. 若必须保留富文本,使用严格白名单 sanitizer,而不是裸 `innerHTML` +3. 后端不要原样透传上游 `response.text`,改为固定错误文案 + 内部日志记录详细信息 +4. 为该链路补充回归测试:确保 `` / `