我们的 AI 引擎 Neutron 在加州大学伯克利分校的 CyberGym 基准测试中取得了 97.1% 的成绩。 了解更多

深度解析

深度解析

python-jose 空 HMAC 密钥导致 JWT 伪造 (CVE-2026-120459)

python-jose 3.5.0 及更早版本接受空 HMAC 密钥,当应用程序的密钥为空时伪造的 JWT 会通过验证;暂无修复版本。

TL;DR

  • python-jose 接受长度为零的 HMAC 密钥。当应用程序的 HS256、HS384 或 HS512 验证密钥解析为空时,攻击者可以用该空密钥计算签名,并让伪造的声明通过验证。
  • 3.5.0 及之前的所有版本都受影响。最新版本仍是 2025-05-28 发布的 3.5.0,截至 2026-11-09 没有修复版本。
  • CVE 记录将其评为 CVSS v4.0 9.1 严重和 CVSS v3.1 7.4 高。报告者的概念验证已在 issue #398 中公开;没有已知利用,截至 2026-11-09 不在 CISA KEV 中。
  • 在您自己的代码中拒绝空密钥,或迁移到会拒绝它的库。PyJWT 2.15.1 会以 InvalidKeyError 拒绝空 HMAC 密钥。

我受影响吗?

以下条件必须全部成立:

  • 使用 python-jose 3.5.0 或更早版本,以 HS256、HS384 或 HS512 验证 JSON Web Token。
  • 验证密钥可以解析为空值:空字符串、空字节,或 k 成员为空的 oct JWK。
  • 应用程序信任攻击者可以提供的令牌中的声明。

固定且非空的密钥不会被利用。该缺陷在密钥于运行时组装时才有影响。示例包括默认值为空的环境变量、返回空 k 的 JWKS 端点,或从未填充的数据库列。

报告者的 issue 还指出,jwt.decode() 默认将 algorithms 设为 None,从而跳过算法允许列表。这是另一个弱点,不是空密钥缺陷,但应用程序可以将两者结合,让攻击者选择算法。

库 受影响版本 修复版本
python-jose 0 至 3.5.0 截至 2026-11-09 无

检查已安装的版本:

pip show python-jose
pip freeze | grep -i jose
grep -rn "python-jose" requirements*.txt pyproject.toml Pipfile.lock poetry.lock 2>/dev/null

然后找出每个验证调用及其密钥来源:

grep -rn "jwt.decode\|jws.verify" --include="*.py" .

如何修复?

没有 python-jose 的修复版本。实际修复是停止向库传递空密钥材料,并迁移到会拒绝它的维护中库。

  1. 在每个验证位置添加检查,在 jwt.decode() 运行之前拒绝空字符串、空字节或 k 为空的 oct JWK:
# Reject empty HMAC key material before python-jose sees it.
from jose.exceptions import JWKError

def require_nonempty_hmac_key(key):
    if isinstance(key, (str, bytes)) and len(key) == 0:
        raise JWKError("HMAC key must not be empty")
    if isinstance(key, dict) and key.get("kty") == "oct":
        if not key.get("k"):
            raise JWKError("oct JWK k must not be empty")
    return key
  1. 在每次调用中传入明确的算法允许列表,即使这不是空密钥缺陷本身:
claims = jwt.decode(token, require_nonempty_hmac_key(key), algorithms=["HS256"])
  1. 迁移到 PyJWT,它在准备密钥时拒绝空密钥。检查位于 HMACAlgorithm.prepare_key,并抛出 InvalidKeyError("HMAC key must not be empty.")。
import jwt as pyjwt

claims = pyjwt.decode(token, key, algorithms=["HS256"])
  1. 如果密钥可能来自 JWKS 文档,请在获取时验证文档,并拒绝任何 k 为空的 oct 密钥。不要等到验证调用失败。

即使在迁移之后,验证 JWKS 文档也值得做。返回 {"kty":"oct","k":""} 的 JWKS 端点本身就是配置错误,任何将其接受为密钥材料的验证器都会失败开放。当对称密钥的 k 为空时,拒绝该文档并发出警报。

如果无法立即更改代码,请将受影响的验证密钥更换为强随机值。只有在每个部署都收到新值时才能关闭暴露;在配置仍解析为空的任何地方,空密钥仍然可被利用。

根本原因是什么?

python-jose 验证 HMAC 密钥的类型,却从不验证长度,而 JWK 路径会把空的 k 成员转换为空字节。HMAC 构造随后使用该空密钥,任何人都能计算其签名。

在 3.5.0 的 jose/backends/native.py 中,HMACKey.__init__ 接受任何字符串或字节值:

def __init__(self, key, algorithm):
    if algorithm not in ALGORITHMS.HMAC:
        raise JWKError("hash_alg: %s is not a valid hash algorithm" % algorithm)
    self._algorithm = algorithm
    self._hash_alg = self.HASHES.get(algorithm)

    if isinstance(key, dict):
        self.prepared_key = self._process_jwk(key)
        return

    if not isinstance(key, str) and not isinstance(key, bytes):
        raise JWKError("Expecting a string- or bytes-formatted key.")

    if isinstance(key, str):
        key = key.encode("utf-8")

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

    self.prepared_key = key

没有 len(key) == 0 检查。_process_jwk 用 base64url_decode 解码 k 成员,而该函数对空输入返回空字节:

def _process_jwk(self, jwk_dict):
    if not jwk_dict.get("kty") == "oct":
        raise JWKError("Incorrect key type. Expected: 'oct', Received: %s" % jwk_dict.get("kty"))

    k = jwk_dict.get("k")
    k = k.encode("utf-8")
    k = bytes(k)
    k = base64url_decode(k)

    return k

jose/backends/cryptography_backend.py 中的 CryptographyHMACKey 重复相同的构造逻辑,也有同样的缺口。两个后端随后计算 hmac.new(self.prepared_key, msg, self._hash_alg),并用 hmac.compare_digest 或 h.verify 比较结果。使用空密钥时,该计算是确定且公开的,因此攻击者生成的签名也能通过比较。

JWK 路径使该缺陷可以从配置触发,而不是来自明显的空字符串。jws._get_keys() 将带 kty 的映射视为单个 JWK,jwk.construct() 为 HS 算法返回 HMAC 密钥类,空的 k 在验证前变为 b""。

jws._verify_signature() 从令牌头读取 alg,为每个候选构建密钥并调用 key.verify()。使用空密钥时,该调用对攻击者的签名返回真,JWT 验证的其余部分继续处理攻击者选择的声明。

如何被利用?

攻击者需要一个令牌端点或任何携带 JWT 的请求,并需要知道或推测部署的密钥为空:

  1. 攻击者找到一个使用 python-jose 验证 HS256 令牌且密钥解析为空的应用程序,例如 JWT_SECRET 未设置而代码传入 os.environ.get("JWT_SECRET", "")。
  2. 攻击者构造一个带有应用程序所信任声明的令牌,例如 {"sub": "admin", "role": "admin"},并用空 HMAC 密钥签名。
  3. 攻击者发送该令牌。python-jose 构造 HMACKey(b"", "HS256"),计算相同的签名并接受该令牌。
  4. 应用程序根据伪造的声明采取行动,因为它看不到密钥问题。

报告者的概念验证已在 issue #398 中公开,并按原样运行:

import jose.jwt as jwt

# This succeeds and should raise an error.
token = jwt.encode({"admin": True}, "", algorithm="HS256")
jwt.decode(token, "", algorithms=["HS256"])  # Returns {"admin": True}

JWK 形式同样有效,这是由 JWKS 驱动的部署所使用的形式:

jwt.decode(token, {"kty": "oct", "k": ""}, algorithms=["HS256"])

攻击如何展开?

让 HMAC 验证密钥解析为空的应用程序将长度为零的密钥交给 python-jose,而 python-jose 用它进行签名和验证,而不是拒绝该配置。

flowchart LR
    A["应用密钥解析为空"] --> B["HMACKey 接受长度为零的密钥"]
    B --> C["攻击者用空密钥计算 HMAC"]
    C --> D["jwt.decode 比较签名"]
    D --> E["伪造声明被接受"]

CVE-2026-120459 的时间线:python-jose 3.5.0 于 2025 年 5 月 28 日发布,公开 issue 于 2026 年 3 月 8 日开启,CVE 于 2026 年 11 月 2 日发布
图 1:该缺陷于 2026 年 3 月报告,2026 年 11 月获得 CVE,期间没有修复版本。来源:GitHub issue、PyPI 发布历史和 CVE 记录,2026 年 11 月 9 日检查。

攻击者能做什么?

伪造令牌被接受后,攻击者就获得了应用程序所信任的每项声明。这可能包括:

  • 当应用程序根据声明授权时,任何用户身份,包括管理员。
  • 应用程序用于界定数据范围的任何租户或组织标识符。
  • 延长的过期时间,因为攻击者选择 exp 声明。
  • 应用程序从令牌读取的任何权限或角色。

攻击者无法恢复真实的签名密钥、解密其他令牌,也无法签署配置正确的部署会接受的令牌。影响取决于应用程序如何处理声明,以及是否存在空密钥条件。

服务到服务的令牌也很重要。为内部 API 伪造的令牌可以跨越从不接触用户的信任边界。后台服务中的空密钥因此可能比登录端点暴露更多。

任何携带 bearer 令牌的请求都是候选,包括 CI 任务和内部服务调用。具有强非空密钥和明确算法允许列表的部署不会通过此缺陷被利用。

为什么修复有效?

长度为零的 HMAC 密钥没有秘密:任何人都能计算相同的 MAC,因此收到它的验证器无法认证任何东西。修复方法是在应用程序侧进行长度检查,或迁移到执行该检查的库。

修复版本应添加的检查很小。在两个后端中,紧接类型检查之后:

     if not isinstance(key, str) and not isinstance(key, bytes):
         raise JWKError("Expecting a string- or bytes-formatted key.")

+    if len(key) == 0:
+        raise JWKError("HMAC key must not be empty.")
+
     if isinstance(key, str):
         key = key.encode("utf-8")

JWK 路径需要在 base64url_decode 之后单独检查,因为空的 k 永远不会到达字符串分支。

PyJWT 执行相同的长度检查,并于 2026-11-09 通过 HS256 编码和解码在本地验证。这使迁移成为该缺陷的修复,而不仅仅是更换库。

python-jose 的 issue 在同一报告中还列出另外四项发现,包括 DER 密钥算法混淆和 algorithms=None 默认值。迁移修复了空密钥缺陷,并避免在其他问题上依赖一个缺乏维护的项目。

如何检查是否已遭入侵?

伪造令牌看起来像有效令牌,因此签名检查无法告诉您它被伪造了。先查找配置条件,然后查找真实用户未执行的账户活动。

  1. 确认是否有部署可能传入空密钥。检查配置默认值和密钥加载代码,并查询您使用的 JWKS 端点。
  2. 解码服务在暴露窗口期间接受的令牌,查找与账户不匹配的声明,例如意外的 sub、角色或租户。载荷无需密钥即可读取。
# Show a token's header and claims without verifying it.
python - <<'PY'
import base64, json, sys
token = sys.stdin.read().strip()
for part in token.split(".")[:2]:
    part += "=" * (-len(part) % 4)
    print(json.dumps(json.loads(base64.urlsafe_b64decode(part)), indent=2))
PY
  1. 在认证日志中查找该账户异常 iat、exp 或 sub 的令牌。然后查找无法与真实登录关联的会话所执行的管理操作。
  2. 将验证密钥更换为强随机值并使现有会话失效。如果密钥为空,该期间签署的每个令牌都必须视为不可信。
  3. 迁移到 PyJWT 或添加检查,然后重新测试空密钥会被拒绝。

失败的伪造尝试除了请求本身外可能不留痕迹,因为被拒绝的签名和伪造的签名在比较之前走相同的代码路径。

时间线

  • 2025-05-28:python-jose 3.5.0 发布,是截至 2026-11-09 的最新版本。
  • 2026-03-08:报告者在 issue #398 中公开空密钥发现和概念验证。
  • 2026-11-02:VulnCheck 发布 CVE-2026-120459。

常见问题

如果我的 HMAC 密钥是强随机值,应用程序会受影响吗?

不会。该缺陷要求验证密钥解析为空值。强非空密钥使攻击者无法计算伪造签名,即使在受影响的 python-jose 版本上也是如此。

传入 algorithms=["HS256"] 能修复空密钥缺陷吗?

不能。允许列表限制验证器接受的算法,但不检查密钥长度。当应用程序传入空密钥并允许 HS256 时,使用 HS256 和空密钥签名的令牌仍会通过验证。

有修复的 python-jose 版本吗?

没有。最新版本仍是 2025-05-28 的 3.5.0,CVE 记录没有指明修复版本。使用应用程序侧检查或迁移到维护中的库。

PyJWT 有同样的问题吗?

没有。PyJWT 2.15.1 会以 InvalidKeyError("HMAC key must not be empty.") 拒绝空 HMAC 密钥。2026-11-09 已通过 HS256 编码和解码在本地验证。

更换密钥有帮助吗?

有,前提是每个部署都收到新值。更换为强随机密钥会消除伪造所依赖的空密钥条件,也会使使用旧密钥签署的令牌失效。在配置仍解析为空的任何地方,空密钥仍然可被利用,因此请将更换与检查结合使用。

攻击者不知道密钥为空也能利用吗?

攻击者需要空密钥条件成立。有些部署使其可以被推测,例如未设置且默认值为空的环境变量,或 k: "" 的 JWKS 条目。一旦条件成立,攻击者在本地计算签名,无需其他访问。

使用 Ostorlab 查找使用 python-jose 的服务

Ostorlab 的软件成分分析和 SBOM 清单会列出服务解析的 Python 软件包和版本。这样可以找出仍依赖 python-jose 的应用程序,并让它们经过空密钥检查或迁移。

参考文献

  • VulnCheck 公告,"python-jose through 3.5.0 Authentication Bypass via Empty HMAC Key"(2026-11-02):https://www.vulncheck.com/advisories/python-jose-through-3.5.0-authentication-bypass-via-empty-hmac-key(2026 年 11 月 9 日检查)
  • CVE 记录 CVE-2026-120459(2026-11-02):https://www.cve.org/CVERecord?id=CVE-2026-120459(2026 年 11 月 9 日检查)
  • python-jose issue #398,"Security: DER key algorithm confusion, empty HMAC key, and timing side-channels"(2026-03-08):https://github.com/mpdavis/python-jose/issues/398(2026 年 11 月 9 日检查)
  • python-jose 3.5.0,jose/backends/native.py(2025-05-28):https://github.com/mpdavis/python-jose/blob/3.5.0/jose/backends/native.py(2026 年 11 月 9 日检查)
  • python-jose 3.5.0,jose/backends/cryptography_backend.py(2025-05-28):https://github.com/mpdavis/python-jose/blob/3.5.0/jose/backends/cryptography_backend.py(2026 年 11 月 9 日检查)
  • PyJWT 2.15.1,HMACAlgorithm.prepare_key(2026 年 11 月 9 日检查):https://github.com/jpadilla/pyjwt/blob/2.15.1/jwt/algorithms.py
  • python-jose 的 PyPI 页面,发布历史(2026 年 11 月 9 日检查):https://pypi.org/project/python-jose/
  • pypistats,python-jose 每周下载量(2026 年 11 月 9 日检查):https://pypistats.org/packages/python-jose