HFCTF2020-EasyLogin

  1. HFCTF2020-EasyLogin

HFCTF2020-EasyLogin

首先一打开是个登录界面

注册个号登录进去。

发现一个GET FLAG,点一下弹出信息permission denied,提示没有权限,我们在登录界面用bp抓个包

发现一串base64组成的jwt码

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZWNyZXRpZCI6MSwidXNlcm5hbWUiOiJtaXhicCIsInBhc3N3b3JkIjoiMTIzIiwiaWF0IjoxNzQzOTE5OTYzfQ.QsP3tYzvKrrPcnbB3QJ1QDeW69HpJYUb9nLF0x81NRE

https://jwt.io/中打开

然后在controllers/api.js访问主要逻辑代码

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
75
76
77
78
79
80
81
82
83
84
85
86
const crypto = require('crypto');
const fs = require('fs')
const jwt = require('jsonwebtoken')

const APIError = require('../rest').APIError;

module.exports = {
'POST /api/register': async (ctx, next) => {
const {username, password} = ctx.request.body;

if(!username || username === 'admin'){
throw new APIError('register error', 'wrong username');
}

if(global.secrets.length > 100000) {
global.secrets = [];
}

const secret = crypto.randomBytes(18).toString('hex');
const secretid = global.secrets.length;
global.secrets.push(secret)

const token = jwt.sign({secretid, username, password}, secret, {algorithm: 'HS256'});

ctx.rest({
token: token
});

await next();
},

'POST /api/login': async (ctx, next) => {
const {username, password} = ctx.request.body;

if(!username || !password) {
throw new APIError('login error', 'username or password is necessary');
}

const token = ctx.header.authorization || ctx.request.body.authorization || ctx.request.query.authorization;

const sid = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()).secretid;

console.log(sid)

if(sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) {
throw new APIError('login error', 'no such secret id');
}

const secret = global.secrets[sid];

const user = jwt.verify(token, secret, {algorithm: 'HS256'});

const status = username === user.username && password === user.password;

if(status) {
ctx.session.username = username;
}

ctx.rest({
status
});

await next();
},

'GET /api/flag': async (ctx, next) => {
if(ctx.session.username !== 'admin'){
throw new APIError('permission error', 'permission denied');
}

const flag = fs.readFileSync('/flag').toString();
ctx.rest({
flag
});

await next();
},

'GET /api/logout': async (ctx, next) => {
ctx.session.username = null;
ctx.rest({
status: true
})
await next();
}
};

主要就是这里,对登录的用户名进行了判断,只有用户名是admin时才可以读取flag:

1
2
3
4
5
6
7
8
9
10
11
12
'GET /api/flag': async (ctx, next) => {
if(ctx.session.username !== 'admin'){
throw new APIError('permission error', 'permission denied');
}

const flag = fs.readFileSync('/flag').toString();
ctx.rest({
flag
});

await next();
},

我们需要把alg改为none,username改为admin,secretid改为数组类型[],alg改为none表示不需要加密,username为admin获得登入权限。

1
2
3
4
5
6
7
利用nodejs的jwt缺陷,当jwt的secret为空,jwt会采用algorithm为none进行解密。 
js是弱语言类型,可以将secretid设置为一个小数或空数组(空数组与数字比较时为0)来绕过secretid的一个验证(不能为null&undefined

绕过代码
if(sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) {
throw new APIError('login error', 'no such secret id');
}

先在base64编码工具处修改alg

然后将其替换掉原来的第一部分(注意需要去掉=号),然后修改admin,secretid

由于不需要加密。jwt的第三部分就要删去,但是末尾的.需要保留,于是payload:

1
ewogICJhbGciOiAibm9uZSIsCiAgInR5cCI6ICJKV1QiCn0.eyJzZWNyZXRpZCI6W10sInVzZXJuYW1lIjoiYWRtaW4iLCJwYXNzd29yZCI6IjEyMyIsImlhdCI6MTc0MzkxOTk2M30.

bp改包

然后登录进去之后访问api/flag即可看到flag。

或者先将get flag的请求抓包把response中的set-cookie放入api/flag


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。
MIXBP github