9.7 KiB
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"}
这说明:
- 接口可匿名访问
- 服务端实际尝试了到指定地址的连接
- 攻击者可以利用该接口探测不同目标的网络可达性/响应差异
影响
- 可被匿名用于对内网/本机/特定外部地址做探测
- 若未来错误回显更详细,可能进一步暴露内部网络结构或上游服务信息
- 若某些环境允许访问元数据地址/内部控制面,风险会进一步升级
修复建议
- 该接口至少改为 管理员鉴权,不要匿名暴露
- 严格限制
base_url:- 白名单域名/白名单 provider
- 禁止
localhost/127.0.0.0/8/ RFC1918 内网 / link-local / metadata 地址
- 后端不要把原始网络错误细节直接返回给前端
- 增加审计日志与更严格限流
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',利用门槛被进一步降低
修复建议
- 错误内容改用
textContent输出,禁止innerHTML拼接错误文本 - 若要渲染 markdown,先做服务端/前端 HTML sanitize
- 后端不要透传原始
response.text或上游 HTML 错误页 - 若确需展示富文本,使用可信白名单 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的风险高于纯测试接口,因为它会修改数据
修复建议
- 对所有 cookie 登录态写接口统一加入 CSRF 校验
- 建议抽象通用依赖,避免后续再漏
- 将“登录态 + 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(管理员自己保存恶意内容,再次进入编辑器触发)
影响
- 劫持管理员会话上下文
- 发起后台操作、窃取页面可见数据
- 若存在其他高权限操作页面,可进一步横向利用
修复建议
- 禁止把不可信数据直接拼进
innerHTML - 改为:
document.createElement(...)input.value = ...textarea.value = ...textContent = ...
- 若必须模板化拼接,先做严格 HTML attribute escaping
- 为
home admin补一组带引号/标签 payload 的自动化回归测试
二、已复核但不应误报的点
1. 统一安全响应头:线上已确认存在
线上验证确认以下响应头存在于公开页与 API:
Content-Security-PolicyX-Frame-OptionsX-Content-Type-Options: nosniffReferrer-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 Noneerror_message = str(exc) if IS_DEVELOPMENT else None
这意味着:
- 如果部署时漏设
ENVIRONMENT,服务会默认按 development 行为启动 - 可能意外暴露
/docs - 500 页面可能泄露异常细节
判断
当前这是配置风险,不是已确认线上漏洞;但优先级应高,因为它属于“部署一失误就暴露”的类型。
建议
- 默认值改为 production
- 生产环境必须显式开启 development,而不是反过来
- 启动时对
ENVIRONMENT做严格枚举校验
四、优先修复顺序
P0
- 下线或严格鉴权
prompt /api/test-connection - 阻断
home admin的innerHTML +=数据拼接
P1
- 修
prompt调试链的错误输出 / markdown 渲染 XSS 面 - 给
prompt登录态写接口补 CSRF
P2
- 调整所有服务的环境默认值为 production
- 为以上问题补回归测试
五、本轮结论
如果问“现在是否可以说这个项目已经完整安全了”,答案仍然是:不能。但和前两轮相比,现在可以更明确地说:
- 项目具备基本安全基线
- 不是全面失守
- 但确实存在若干已确认真实问题,尤其集中在
prompt和home admin - 这些问题已足以支持继续进入“修复 PR / 回归验证”阶段,而不是只停留在观察层