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。
下图展示了伪造载荷如何获得应用程序的信任:
{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