[HFCTF2020]EasyLogin

神奇的题,使我的大脑旋转

image-20240605203717806

image-20240605203723254

这俩界面试过了啊

sql注入和ssti没啥用,不信可以试试,然后告诉我怎么写(请务必这样做)

所以开始看看源码

发现了css和js

一个个点进去看了,发现在app.js里有个东西

image-20240605203934241

然后?然后就卡住了

我知道需要进入/api/flag但是我进不去啊

所以去查找一下

发现是koa框架

koa框架

koa是一个基于node实现的一个新的web框架,它是由express框架的原班人马打造的。它的特点是优雅、简洁、表达力强、自由度高。它更express相比,它是一个更轻量的node框架,因为它所有功能都通过插件实现,这种插拔式的架构设计模式,很符合unix哲学。

emm

暂时别过多了解,脑子烂,会炸

可以先了解一下它的源码结构/文件框架

img

所以,我们使用常见框架去获取一下控制器文件

/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();
}

image-20240605204918499

image-20240605204942488

两个重点列出来

一个是有关username和password生成jwt

一个是读取flag的

我们发现username要等于admin

登录验证则是jwt

所以我们应该对于jwt进行破解

并且是通过hs256加密,我们需要改为加密方式改为none来进行破解

标题中的alg字段更改为none,有些JWT库支持无算法,即没有签名算法。当alg为none时,后端将不执行签名验证。 此外对于本题中验证采用的密匙secret值也需要为空或者undefined否则还是会触发验证,所以将JWT中secretid项修改为[]

所以,我们需要改变的是

alog、username、secrettid

我们先进行抓包

image-20240605212647168

那大概率就是说authorzation那个就是我们的jwt的值(因为也是验证身份的,应该是一起上传的)

去解密一下

image-20240605212704207

拿到了一些重要的信息比如iat

然后写脚本生成新的jwt

image-20240605212804698

生成了一串东西

也就是你的新authorization

之后在登录的时候抓个包修改一下

image-20240605212949579

我们就可以以admin的用户进入

之后再查询api/flag路径即可