python-jose の空の HMAC キーによる JWT 偽造 (CVE-2026-120459)
python-jose 3.5.0 以前は空の HMAC シークレットを受け入れ、アプリケーションの鍵が空のときに偽造 JWT が検証されます。修正版はありません。
要点
- python-jose は長さゼロの HMAC キーを受け入れます。アプリケーションの HS256、HS384、HS512 の検証キーが空になると、攻撃者はその空のキーで署名を計算し、偽造したクレームを受理させられます。
- 3.5.0 までのすべてのリリースが影響を受けます。最新リリースは 2025-05-28 に公開された 3.5.0 のままで、2026-11-09 時点で修正版はありません。
- CVE レコードは CVSS v4.0 9.1 の Critical、CVSS v3.1 7.4 の High と評価しています。報告者の実証コードは issue #398 で公開されており、悪用は確認されておらず、2026-11-09 時点で CISA KEV にはありません。
- 独自のコードで空のキーを拒否するか、それを拒否するライブラリへ移行してください。PyJWT 2.15.1 は空の HMAC キーを
InvalidKeyErrorで拒否します。
影響を受けますか?
次のすべてが当てはまる場合に影響を受けます。
python-joseの 3.5.0 以前で、HS256、HS384、HS512 を使って JSON Web Token を検証している。- 検証キーが空の値になる可能性がある。空文字列、空のバイト列、または
kメンバーが空のoctJWK です。 - アプリケーションが、攻撃者が提供できるトークンのクレームを信頼している。
固定された空でないシークレットは悪用できません。この不具合は、キーが実行時に組み立てられる場合に問題になります。例として、空の既定値を持つ環境変数、空の 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 の修正版はありません。実用的な修正は、空のキー材料をライブラリへ渡さないことと、それを拒否する保守されたライブラリへ移行することです。
- すべての検証箇所で、
jwt.decode()が実行される前に空文字列、空のバイト列、またはkが空のoctJWK を拒否するチェックを追加します。
# 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
- 空キーの不具合そのものではありませんが、すべての呼び出しでアルゴリズムの許可リストを明示します。
claims = jwt.decode(token, require_nonempty_hmac_key(key), algorithms=["HS256"])
- キーの準備時に空のキーを拒否する PyJWT へ移行します。チェックは
HMACAlgorithm.prepare_keyにあり、InvalidKeyError("HMAC key must not be empty.")を送出します。
import jwt as pyjwt
claims = pyjwt.decode(token, key, algorithms=["HS256"])
- キーが 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 を運ぶ任意のリクエストが必要で、デプロイのキーが空であることを知るか推測する必要があります。
- 攻撃者は、python-jose で HS256 トークンを検証し、キーが空になるアプリケーションを見つけます。たとえば
JWT_SECRETが未設定で、コードがos.environ.get("JWT_SECRET", "")を渡している場合です。 - 攻撃者はアプリケーションが信頼するクレーム、たとえば
{"sub": "admin", "role": "admin"}を持つトークンを作り、空の HMAC キーで署名します。 - 攻撃者はトークンを送ります。python-jose は
HMACKey(b"", "HS256")を構築し、同じ署名を計算してトークンを受理します。 - アプリケーションはキーの問題を見ないため、偽造されたクレームに基づいて動作します。
報告者の実証コードは 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}
JWKS で駆動するデプロイが使う JWK 形式でも同じです。
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["偽造クレームを受理"]
攻撃者に何ができますか?
偽造トークンが受理されると、攻撃者はアプリケーションが信頼するすべてのクレームを引き受けます。これには次が含まれます。
- アプリケーションがクレームに基づいて認可する場合、管理者を含む任意のユーザー ID。
- アプリケーションがデータの範囲を決める任意のテナントまたは組織 ID。
- 任意の有効期限。攻撃者が
expクレームを選ぶためです。 - アプリケーションがトークンから読む任意の権限またはロール。
攻撃者は実際の署名キーを回復したり、他のトークンを復号したり、正しく設定されたデプロイが受理するトークンに署名したりはできません。影響は、アプリケーションがクレームで何をするかと、空キーの条件が存在するかによります。
サービス間トークンも重要です。内部 API 向けの偽造トークンは、ユーザーを一度も見ない信頼境界を越えることがあります。バックグラウンドサービスの空のキーは、ログインエンドポイントより多くを露出させる可能性があります。
ベアラートークンを運ぶ任意のリクエストが候補で、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 は同じ報告にさらに 4 件の所見を挙げています。DER キーのアルゴリズム混同や algorithms=None の既定値などです。移行は空キーの不具合を修正し、その他について保守されていないプロジェクトへの依存を避けます。
侵害されたかどうかを確認するには?
偽造トークンは有効なトークンに見えるため、署名チェックでは偽造を判別できません。まず設定条件を確認し、その後に実際のユーザーが行っていないアカウント活動を探します。
- どのデプロイが空のキーを渡せるかを確認します。設定の既定値とキー読み込みコードを調べ、使用している JWKS エンドポイントに問い合わせます。
- 露出期間中にサービスが受理したトークンをデコードし、アカウントと一致しないクレーム、たとえば予期しない
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
- 認証ログで、そのアカウントにしては不自然な
iat、exp、subを持つトークンを探します。次に、実際のログインに結び付けられないセッションからの管理操作を探します。 - 検証キーを強いランダム値に変更し、既存のセッションを無効化します。キーが空だった場合、その期間に署名されたすべてのトークンを信頼できないものとして扱う必要があります。
- 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 は空の HMAC キーを InvalidKeyError("HMAC key must not be empty.") で拒否します。これは 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