#690·go-admin

通过未锚定的注销路径正则表达式检查完整请求 URL(包括查询字符串)来绕过授权

作者: geo-chen创建于 2026年9月14日更新于 2026年9月14日
标签🐛bug

报告日期: 2026年8月3日通过电子邮件发布的报告: 我报告了以下漏洞: https://GitHub.com/GoAdminGroup/go-admin 受影响的版本: v1.2.26 (HEAD da3ce351e5b7256e3d2745d8f11067d738145182) ## 概要 go-admin 对每个请求的权限检查都会特殊处理注销端点,使用不固定的正则表达式与用户请求中的子字符串进行匹配。 由于传递给该检查的值是完整的请求 URL 字符串(路径加查询字符串),而非仅仅是路由的路径,因此任何已授权用户,包括没有分配有意义权限的用户,都可以通过在任何管理 URL 后面添加一个查询参数,其值包含配置的管理前缀后跟"/logout"(例如?ref=/admin/logout)来绕过框架中的每个权限检查。 路由仍会根据 URL 路径将请求发送到真正的目标处理程序,而权限检查则会看到完整的路径+查询字符串,匹配注销子字符串,并无条件地授予访问权限。 这影响了只读页面(例如内置的用户管理表)和状态更改操作(例如删除用户),因为它们都通过相同的中间件。 ## 详细信息 modules/auth/middleware.go, Filter(): go func Filter(ctx *context.Context, conn db.Connection) (models.UserModel, bool, bool) { ... return user, true, CheckPermissions(user, ctx.Request.URL.String(), ctx.Method(), ctx.PostForm()) } ` `ctx.Request.URL.String()` 是 Go 的标准 `net/url.URL.String()`, 它以 `path?query` 形式重构完整的 URL,而不仅仅是路径。 `plugins/admin/models/user.go`, `CheckPermissionByUrlMethod()`: go func (t UserModel) CheckPermissionByUrlMethod(path, method string, formParams url.Values) bool { if t.IsSuperAdmin() { return true } if path == "" { return false } logoutCheck, _ := regexp.Compile(config.Url("/logout") + "(.*?)") if logoutCheck.MatchString(path) { return true } ... // 真正的权限匹配逻辑随后 config.Url("/logout") 会前缀配置的管理 URL 前缀(例如 /admin),生成类似 /admin/logout(.*?) 的模式。 此模式没有 ^/$ 锚,并且 MatchString 在输入中任意位置进行子字符串搜索。 结合调用者传递的完整 URL(路径和查询字符串一起)作为 path,任何请求的 URL 中包含子字符串 /admin/logout 的地方,包括在任意查询参数值中,都会导致此函数在真正的权限规则对该用户进行评估之前返回 true。 路由本身纯粹基于 URL.Path,因此添加查询字符串不会改变运行哪个处理程序;它只会改变权限检查看到的内容。 ## PoC(可根据要求提供) ## 影响

内容来源: GoAdminGroup/go-admin