# 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 = `