11 KiB
ephron.ren 全站安全审计 PRD
- 日期:2026-05-16
- 审计方式:源码静态审计 + 浏览器验证 + HTTP 探测 + 安全边界内渗透式验证
- 审计对象:auth / blog / canvas / prompt / home / shared
- 代码基线:
ephron_ren/ephron.rencommitbca0e08efdd33de85550680496528c40588a7501
一、结论摘要
本轮无法证明“整个站点已经安全”,但已经可以确认:
-
项目具备一套基础安全基线:
- 服务端 admin 鉴权
- 权限检查
- CSRF 防护(大部分后台写接口)
- 统一安全头
- 登录限流 / 若干业务限流
- Cookie 基本属性(HttpOnly / SameSite / Secure)
-
但本轮也发现了至少 2 个可确认安全漏洞,其中 1 个优先级较高:
prompt存在匿名 SSRF 探针接口prompt存在已登录态下缺少 CSRF 的写/调用接口
-
此外还有一些中低风险加固项:
- CSP 允许
unsafe-inline,显著削弱 XSS 防线 - logout 设计允许 GET/无 CSRF,存在强制登出面
- 若干 prompt 调试/测试能力暴露边界偏宽
- CSP 允许
因此,当前状态不能下“已确保项目安全”的结论。更准确的说法是:
站点不是明显裸奔,有基础防护,但仍存在已确认漏洞,尚未达到可宣称“安全”的状态。
二、审计范围
覆盖内容
- 认证 / 授权 / 会话 / Cookie
- CSRF
- 路由保护与 admin 边界
- 重定向与登录回跳
- XSS / Markdown / HTML 渲染
- 上传
- Prompt 调试与 provider 调用链
- service token / service API 信任边界
- 安全头 / 限流
- 公开 HTTP 行为与浏览器链路
未覆盖或仅部分覆盖
- 登录后真实管理员态的全量人工操作验证
- 大规模自动目录爆破 / 口令喷洒 / 破坏性测试
- 依赖库 CVE 全量 SBOM 扫描
- 生产基础设施层(Nginx/WAF/容器/DB/内网 ACL)
- 外部云资源 / 内网元数据可达性验证
三、已确认漏洞
漏洞 1:Prompt 存在匿名 SSRF 探针接口
级别
高
位置
prompt/src/routes/api.py:272-336- 前端调用入口:
prompt/templates/admin/settings.html:601-611
问题描述
POST /api/test-connection 允许调用方提交:
providerbase_urlapi_keymodel
后端会直接基于传入的 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"
动态验证证据
未登录直接请求:
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 ...>
说明:
- 接口对匿名用户开放
- 后端确实向
https://example.com/chat/completions发起了请求 - 目标响应片段被带回调用方
影响
- 可用于探测服务器对外网络访问能力
- 可作为 SSRF 探针访问内网/控制面/云元数据等目标(实际影响取决于部署网络)
- 可帮助攻击者绘制内网/出口访问面
- 若未来返回信息更多,可能升级为更强的信息泄露
判定
已确认漏洞
修复目标
- 该接口必须至少要求管理员鉴权
- 同时建议加入 CSRF 防护
- 对
base_url做 allowlist 或严格限制为已保存 provider 域名 - 不应把上游原始响应文本直接回显给前端
漏洞 2:Prompt 测试 / 保存示例接口缺少 CSRF 防护
级别
中
位置
prompt/src/routes/api.py:141-262- 前端调用:
prompt/static/js/test-prompt.js:104-108
问题描述
以下两个接口依赖 Cookie 登录态,但没有做 CSRF 校验:
POST /api/prompts/{key}/testPOST /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 路由保护体系下
四、低风险 / 加固项
加固项 A:CSP 允许 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 具备:
HttpOnlySameSite=LaxSecureDomain=.ephron.ren
5) 安全头存在
线上已观测到:
Content-Security-PolicyX-Frame-Options: DENYX-Content-Type-Options: nosniffReferrer-Policy: strict-origin-when-cross-origin
6) 登录与若干业务限流存在
- auth 登录限流
- blog 上传/评论/点赞限流
- home/prompt 若干 admin 写接口限流
七、优先级建议
P0 / 立即处理
- 关闭或修复
POST /api/test-connection- 必须加鉴权
- 必须限制可请求目标
- 必须减少错误回显
P1 / 尽快处理
- 为 prompt 测试与保存示例接口补齐 CSRF
- 复查 prompt 调试 API 是否应全部纳入 admin 体系
P2 / 短期加固
- 收紧 CSP,逐步去掉
unsafe-inline - 统一 logout 为 POST + CSRF
- 对 SSRF 风险做更系统的目标 allowlist 策略
P3 / 持续审计
- 登录后做完整权限/越权验证
- 做上传/渲染/资源消耗专项测试
- 做依赖漏洞与部署层基线检查
八、是否可以宣称“项目已安全”
不可以。
原因不是因为“完全没有安全措施”,而是:
- 已发现真实漏洞,且至少一个可被匿名利用
- 登录后权限与部署层仍未完成全覆盖验证
- 目前最多只能说“安全基线已具备,但尚未通过完整安全审计闭环”
更合适的对外表述应为:
项目已具备基础安全防护,但最新审计发现若干需要修复的问题;在完成修复与复测前,不建议宣称已完全安全。
九、后续验收标准
当以下条件全部满足后,才建议把本轮问题关闭:
/api/test-connection需要管理员身份,且目标地址受严格限制- prompt 调试 / 保存示例相关接口补全 CSRF
- 上述修复具备自动化测试覆盖
- 复测确认匿名 SSRF 不再成立
- 复测确认跨站无法再触发 prompt 测试/保存示例
- 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 + 清 CookieGET https://canvas.ephron.ren/logout?next=/admin→ 303 + 清 CookieGET https://prompt.ephron.ren/logout?next=/admin→ 303 + 清 Cookie