Add ephron.ren security audit PRD (2026-05-16)

This commit is contained in:
Ubuntu
2026-05-16 12:24:36 +08:00
parent ee8cddf8b8
commit 2f6209d94a

View File

@@ -0,0 +1,381 @@
# 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"`
### 动态验证证据
未登录直接请求:
```http
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` 逻辑完整
### 4) Cookie 基本属性合理
动态观测与源码表明认证 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 / 尽快处理
2. 为 prompt 测试与保存示例接口补齐 CSRF
3. 复查 prompt 调试 API 是否应全部纳入 admin 体系
## P2 / 短期加固
4. 收紧 CSP逐步去掉 `unsafe-inline`
5. 统一 logout 为 POST + CSRF
6. 对 SSRF 风险做更系统的目标 allowlist 策略
## P3 / 持续审计
7. 登录后做完整权限/越权验证
8. 做上传/渲染/资源消耗专项测试
9. 做依赖漏洞与部署层基线检查
---
## 八、是否可以宣称“项目已安全”
**不可以。**
原因不是因为“完全没有安全措施”,而是:
- 已发现真实漏洞,且至少一个可被匿名利用
- 登录后权限与部署层仍未完成全覆盖验证
- 目前最多只能说“安全基线已具备,但尚未通过完整安全审计闭环”
更合适的对外表述应为:
> 项目已具备基础安全防护,但最新审计发现若干需要修复的问题;在完成修复与复测前,不建议宣称已完全安全。
---
## 九、后续验收标准
当以下条件全部满足后,才建议把本轮问题关闭:
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