ctfshow-jwt
开个栏目记录一下ctfshow刷题,毕竟大几百,少做一题都感觉心在滴血,希望能借此走出新手村吧
jwt介绍
了解
jwt就是json web token,简单来说就是一种登录凭证,基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息(不像传统的session认证)。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利
JWT鉴权的流程是这样的:
- 用户使用用户名密码来请求服务器
- 服务器进行验证用户的信息
- 服务器通过验证发送给用户一个token
- 客户端存储token,并在每次请求时附送上这个token值
- 服务端验证token值,并返回数据
这个token必须要在每次请求时传递给服务端,它应该保存在请求头里
构成
JWT由三段信息构成,将这三段信息的文本用.
连接在一起就成了JWT字符串
另外JWT事实上就是经过base64加密的json,通过base64解密可以直观看到前两段的具体内容,不过还是更推荐官方网站,支持更多操作https://jwt.io
第一部分被称为头部header,头部承载了两部分信息:声明类型,这是JWT,并且声明加密的算法
1 | { |
如果alg是none,则第三段不会存在,可以伪造token随意访问(有些题目会涉及)
第二部分被称为载荷payload,载荷是存放有效信息的地方,放了很多声明,包括用户名,是否为管理员之类的,我们伪造token就是通过修改payload来访问管理员的账号
第三部分被称为签证signature,检测JWT前两段的有效性,这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。也就是说我们伪造JWT的重中之重就是要破解secret,因为我们现在已经有了header和payload。
JWTString = Base64(Header).Base64(Payload).HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload), secret)
web345
看源代码提示/admin
,直接访问只会跳转到index.php。
emmm,那就看cookie部分吧,有个名称为auth的,值很像jwt,拿去https://jwt.io/解密
发现没有后面蓝色部分,就是不需要第三部分的签证,也就不需要知道密钥
把sub后的值改为admin再重新传给auth,同时访问/admin得到flag
web346
这时有了密钥
法一,算法改为none
,空算法
JWT支持将算法设定为“None”。如果“alg”字段设为“ None”,那么签名会被置空,这样任何token都是有效的。
设定该功能的最初目的是为了方便调试。但是,若不在生产环境中关闭该功能,攻击者可以通过将alg字段设置为“None”来伪造他们想要的任何token,接着便可以使用伪造的token冒充任意用户登陆网站。
解码
1 | { |
我们需要把sub字段改为admin
但是如果把签名算法改为none的化jwt.io那个网站就无法生成,这个时候可以使用python生成
1 | //注意,py文件名不能是jwt.py,这样会引发报错 |
法二,常规解法
爆破出密钥为123456
照上题解法即可
web347
题目提示弱口令
本来应该用jwt-cracker工具爆破密钥,当时太慢了,直接上网看结果了,和上题一样是123456,直接上jwt.io改,解法和上题法二没区别
如何配置使用jwt-cracker工具可以看这里https://sxz-oi.github.io/2022/06/07/c-jwt-cracker/
web348
jwt-cracker工具爆破,密钥是aaab
,秒出
web349
还是JWT,不过这次变成了安全度更高的公私钥加密
题目自带一个附件app.js,估计是源码
1 | /* GET home page. */ |
可以看到,题目把私钥和公钥放在了web目录,我们访问(/private.key 和 /public.key)就可以直接下载,造成了私钥公钥的泄露
法一
脚本伪造1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22import base64
import json
import jwt
jwt_origin = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidXNlciIsImlhdCI6MTcyMjQ3OTIyM30.mFoGd-WAcSzadfTufPoaDVm6tdE3Arw_bBTG151-lZCaP7NfH1YM87ItMWVmCir0aFiPE6xdse-iWbWDfXMSgn1ljAs-PqUtQSc9Zm-_kaBnDN4gMCm6x8aDIvzG8TRiTajXSRd9YBB84cCA7boQHz97OVamLR0AKpBykoGtrwk"
# secret = "aaab"
with open("D:/firefox_download/private.key", "rb") as f:
private_key = f.read()
with open("D:/firefox_download/public.key", "rb") as f:
public_key = f.read()
alg = json.loads(base64.b64decode(jwt_origin.split(".")[0]))["alg"]
payload = jwt.decode(jwt_origin, public_key, algorithms=alg)
print("before: ", payload)
payload["user"] = "admin"
print("after: ", payload)
jwt_payload = jwt.encode(payload, private_key, algorithm=alg)
print(jwt_payload)
或者
1 | import jwt |
法二
推荐一个jwt工具
https://github.com/Aiyflowers/JWT_GUI
里面的readme.md文档有详细解题思路
注意POST方法,cookie后面跟着=
,不能用:
,当时没注意这点,死活没结果
web350
考点:公私钥泄密之【公钥】泄密。
原jwt是RS256加密,只有公钥泄露。可以根据公钥,修改算法从非对称算法(比如RS256)到对称密算法(HS256)
双方都使用公钥验签,顺利篡改数据
当公钥可以拿到时,如果使用对称密码,则对面使用相同的公钥进行解密
实现验签通过
要有nodejs的知识,有两个相似的脚本
1 | var jwt = require('jsonwebtoken'); |
或者
1 | const jwt = require('jsonwebtoken'); |
如果运行报错Error: Cannot find module 'jsonwebtoken'
,直接npm install jsonwebtoken --save
安装的时候不能用npm install -g jsonwebtoken --save
,不使用-g标签全局安装它,只需将其本地安装在当前工作目录中即可。只需这样做:npm install jsonwebtoken --save
,这是因为不能在代码中直接require全局安装的包。
运行之后得到jwt,在靶机页面刷新抓包,改GET为POST,再改cookie值,发送即可