Skip to content

[security][finding] 同一 break-glass 守卫的第三张面孔:/admin/remove-user 上全局 hooks.before 跑在 adminMiddleware 之前,已认证的非管理员即可触达其查找与拒绝 #11477

Description

@os-sam

Filed unassigned by the domain:services execution seat (session session_01APWX2AwT3a4xDcjPCe8bk4)。⛔ 不认领,定级归 triage;修法方向属 Clause-② 且是安全边界,维护者地板

出处:#11074 派发时要求的「同轮测量,只报不改」

#11074 时我写了一条同轮测量要求,并明确 ⛔ 不要把修复扩散到 /admin/* —— 扩散会把一个范围清晰的安全修复变成不可评审的。dev 照办了:PR #11475 只动 /delete-user,把 /admin/* 的排序追踪出来、写进报告、不碰。本卡就是那份测量。

⚠️从代码读出的排序,不是执行出来的利用。 下面的后果按此口径陈述。

机制

break-glass 守卫注册为全局 hooks.before。better-auth 在每个端点自己的 use: [...] 中间件之前运行全局 before-hook。

/admin/remove-user 由 better-auth 自己的路由器直接服务,它的 adminMiddleware只做会话检查,真正的角色判定还要更晚 —— 在端点处理器内部才发生。

⇒ 对一个已认证的非管理员调用者,守卫自身的查找与可能的拒绝,发生在上述两道检查都还没跑之前。

⭐ 与 /admin/ban-user 的对照,才是这张卡的精确之处

同一个守卫,两条路由,排序相反:

路由排序原因
/admin/remove-user守卫 先于 授权由 better-auth 路由器直接服务,无 ObjectStack 侧前置门
/admin/ban-user授权 先于 守卫#9652auth-plugin.ts 的 raw mount 遮蔽了它,gateAdmin(c) 在重跑守卫之前授权

⇒ 这不是「守卫设计得不好」,而是其中一条路由缺少另一条已经有的那层遮蔽挂载。差别来自 #9652 的补丁覆盖面,不是来自守卫本身。

⚠️因此本卡与 #9652 是耦合的:改动那些 raw mount(取消遮蔽、或改变遮蔽范围)会双向改变这里的排序。谁先动,另一张的结论就要重测。⛔ 不要在不看本卡的情况下改 #9652 的挂载面,反之亦然。

与已有三张卡的关系:同一守卫的第三张面孔

路由需要什么状态
#10776/delete-user无需认证✅ 已修已关
#11074/delete-user任意已认证调用者✅ PR #11475(在评审中)
本卡/admin/remove-user任意已认证非管理员🆕

前两张是同一条路由上门槛的收窄。本卡是另一条路由,同类形状:守卫在授权之前就对一个由请求指名的目标做了判断,而它的拒绝本身携带信息。

不要把本卡读成「#11475 没修好」。#11475 修的是 /delete-user,并且修完了;本卡从一开始就被排除在其范围之外,是刻意的,理由写在 #11074 的派发词里。

已被 #11475 排除的、以及本卡未做的

披露纪律

本卡只描述机制与排序,与 #10776 / #11074 已公开的范围一致。⛔ 不含请求形状、构造步骤或可复现的读取示例。

Refs:#11074 · PR #11475(测量出处)· #10776 · #9652(耦合)· #9969 · ADR-0068

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions