sast-jwt について
sast-jwtは、安全でないJWT実装を調べるセキュリティ監査スキルです。トークンの発行、検証、設定を整理してから検証箇所を分析し、事前に `sast/architecture.md` が必要です。
主な機能
- 012段階のJWT監査
- 02JWTライフサイクルの把握
- 03トークン検証箇所の分析
- 04一般的なJWTの弱点確認
活用シーン
- 01JWT認証コードの監査
- 02署名とクレーム検証の確認
- 03安全でない鍵やアルゴリズム処理の発見
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ utkusen/ sast-skills --skill sast-jwtインストール後、Claude Code で/sast-jwtを入力して読み込まれたか確認します。
詳細
JWT の脆弱性検出
安全でない JSON Web Token(JWT)の実装を見つけるため、対象を絞ったセキュリティ評価を実施します。このスキルでは、サブエージェントを使った 2 段階のアプローチを採用します。まず 調査 で JWT のライフサイクル全体(発行、検証、設定)を把握し、次に 分析 で検証箇所にある悪用可能な弱点をすべて特定します。
前提条件:sast/architecture.md が存在している必要があります。存在しない場合は、先に分析スキルを実行してください。
安全でない JWT 実装とは
JWT は、Base64URL でエンコードされた 3 つの部分、header.payload.signature で構成されます。ヘッダーには署名アルゴリズム(alg)、ペイロードにはクレーム(例:sub、role、exp)が含まれ、署名は整合性を証明する暗号学的な証拠です。サーバーがトークン自身に含まれる署名方法に関するクレームを信頼したり、署名をまったく検証しなかったり、推測可能なシークレットを使用したり、トークン自体に埋め込まれた攻撃者が制御する鍵素材を信頼したりすると、脆弱性が発生します。
核心となるパターンは、クレームを信頼する前に、サーバーが JWT の真正性と整合性を十分に検証していないことです。
JWT の脆弱性とは
1. アルゴリズム混同 — alg: none
サーバーが、ヘッダーで"alg": "none"を宣言したJWTを受け入れ、署名検証を完全に回避します。攻撃者は任意のペイロードを作成し、algをnoneに設定して、署名を省略します。ライブラリがこれを処理すると、偽造されたトークンが受け入れられます。
2. アルゴリズム混同 — RS256 → HS256 RS256(非対称方式:秘密鍵で署名し、公開鍵で検証)用に設定されたサーバーでも、ライブラリがトークンによるアルゴリズムの指定を許可している場合、HS256 モードに誘導される可能性があります。公開鍵は取得できることが多いため、攻撃者はサーバーの公開鍵をHMACシークレットとして使い、HS256で偽造トークンに署名します。サーバーは同じ公開鍵を使ってHMACを検証し、トークンを受け入れます。
3. 署名検証の欠落または無効化 サーバーが署名を実際に検証せずにJWTペイロードをデコードします。よくあるパターン:
- Python(PyJWT):
jwt.decode(token, options={"verify_signature": False}) - Node.js(jsonwebtoken):
jwt.verify(token, secret)の代わりにjwt.decode(token) - 署名チェックを行わず、ペイロードを手動でbase64デコードする
- デコード呼び出しで
algorithms=["none"]を受け入れる
4. 弱い、またはハードコードされたHMACシークレット
サーバーが、短い、推測しやすい、またはハードコードされたシークレット(例:"secret"、"password"、"changeme"、"jwt-secret-key")でトークンに署名します。有効なトークンを入手した攻撃者は、hashcatやjwt_toolなどのツールを使ってオフラインでシークレットを総当たりし、任意のトークンを偽造できます。
5. 埋め込みJWK(jwkヘッダーインジェクション)
トークンのヘッダーに、埋め込みJSON Web Key(jwkパラメーター)が含まれています。検証コードが埋め込みキーを信頼してトークン自体の署名を検証する場合、攻撃者は独自の鍵ペアを生成し、秘密鍵で偽造トークンに署名して、公開鍵をヘッダーに埋め込みます。サーバーは攻撃者が埋め込んだ公開鍵を使って署名を検証し、トークンを受け入れます。
6. JKU/X5Uヘッダーインジェクション
jku(JWK Set URL)またはx5u(X.509証明書URL)ヘッダーの値を使って、URLから検証鍵を取得します。サーバーが許可リストに照らしてURLを検証しない場合、攻撃者は独自のサーバー上でホストする細工した鍵セットを参照させることができます。
7. キーID(kid)ヘッダーインジェクション
kidヘッダーは署名鍵の検索に使われ、多くの場合、データベースやファイルシステムから取得します。kidの値がサニタイズされずにSQLクエリへ埋め込まれると、SQLインジェクションの経路になります。ファイルパスへ連結されると、パストラバーサルの経路になります。
8. クレーム検証の欠落
expがチェックされない → 期限切れトークンが永久に有効なままになるiss(発行者)がチェックされない → 他のサービスが発行したトークンが受け入れられるaud(対象者)がチェックされない → 他のサービス向けのトークンが受け入れられるnbf(有効期間開始時刻)がチェックされない → 有効期間の開始前にトークンが使用される
9. トークン失効機能がない トークンのブラックリストや失効の仕組みがありません。盗まれたトークンやログアウト後のトークンは、期限切れになるまで有効なままです。トークンの有効期間が長い場合、特に問題になります。
JWT の脆弱性ではないもの
以下をJWTの脆弱性として報告しないでください。
- IDOR:別のユーザーのデータにアクセスするために
user_idクレームを変更するのは認可上の欠陥であり、JWTの偽造ではありません。トークン自体を偽造できる場合に限り報告してください - JWTペイロード経由のXSS:後でエスケープせずにレンダリングされるクレームへ
<script>を挿入するのはXSSであり、JWTのバグではありません - CSRF:
SameSiteなしでJWTをCookieに格納するのはCSRFの問題であり、JWTの完全性に関する問題ではありません - 適切に制限された検証:強力なシークレットを使った
jwt.verify(token, secret, { algorithms: ['HS256'] })は脆弱ではありません
JWT の脆弱性を防ぐパターン
1. 検証呼び出しでのアルゴリズム許可リスト
# Python — PyJWT: explicitly specify allowed algorithms
payload = jwt.decode(token, secret, algorithms=["HS256"])
# Node.js — jsonwebtoken: restrict algorithms
jwt.verify(token, secret, { algorithms: ['HS256'] })
# Java — jjwt: specify expected algorithm
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token)
# (jjwt does not use the header's alg; it uses the key type)
2. 強力なランダム生成シークレット
# Strong secret: at least 256 bits of entropy, not hardcoded
import secrets
SECRET_KEY = secrets.token_hex(32) # load from env in production
3. すべてのクレームを検証する
payload = jwt.decode(
token, secret, algorithms=["HS256"],
options={"require": ["exp", "iss", "aud"]},
issuer="https://myapp.example.com",
audience="myapp-api"
)
4. アルゴリズムの曖昧さがない非対称鍵
// Use RS256 with public key for verification; never accept HS256 on the same endpoint
jwt.verify(token, publicKey, { algorithms: ['RS256'] })
5. JWK/JKU URLの許可リスト
# Only fetch keys from a known, trusted JWKS endpoint
ALLOWED_JWKS_URLS = {"https://accounts.google.com/.well-known/jwks.json"}
if jku not in ALLOWED_JWKS_URLS:
raise ValueError("Untrusted JWK URL")
脆弱な例と安全な例
Python — PyJWT
# VULNERABLE: signature verification disabled
def get_current_user(token: str):
payload = jwt.decode(token, options={"verify_signature": False})
return payload["user_id"]
# VULNERABLE: accepts alg:none because no algorithm restriction
def get_current_user(token: str):
payload = jwt.decode(token, SECRET_KEY) # PyJWT < 2.x default: accepts any alg
return payload["user_id"]
# VULNERABLE: weak hardcoded secret
SECRET_KEY = "secret"
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
# SECURE: algorithm restricted, strong secret from env
SECRET_KEY = os.environ["JWT_SECRET"] # strong, random, from environment
def get_current_user(token: str):
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
return payload["user_id"]
Node.js — jsonwebtoken
// VULNERABLE: jwt.decode() — no signature verification
function getUser(token) {
const payload = jwt.decode(token); // decode only, never verify
return payload.userId;
}
// VULNERABLE: algorithms not restricted — susceptible to alg:none or RS256→HS256
function getUser(token) {
const payload = jwt.verify(token, SECRET); // no algorithms option
return payload.userId;
}
// VULNERABLE: weak hardcoded secret
const SECRET = "password123";
jwt.verify(token, SECRET, { algorithms: ['HS256'] });
// SECURE: algorithm restricted, strong secret from env
const SECRET = process.env.JWT_SECRET;
function getUser(token) {
const payload = jwt.verify(token, SECRET, { algorithms: ['HS256'] });
return payload.userId;
}
Java — jjwt
// VULNERABLE: deprecated parser (accepts alg from header)
Jwts.parser().setSigningKey(key).parseClaimsJws(token);
// VULNERABLE: no expiry check — the library default may not enforce exp
Claims claims = Jwts.parserBuilder()
.setSigningKey(key).build()
.parseClaimsJws(token).getBody();
// claims.getExpiration() never checked
// SECURE: parserBuilder (does not trust header alg; uses key type)
Claims claims = Jwts.parserBuilder()
.requireIssuer("myapp")
.requireAudience("myapp-api")
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
Go — golang-jwt / dgrijalva/jwt-go
// VULNERABLE: accepts any algorithm including "none"
token, _ := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte(secret), nil // no algorithm check
})
// VULNERABLE: weak secret
var jwtKey = []byte("secret")
// SECURE: validate signing method before returning key
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return jwtKey, nil
})
kid ヘッダーの SQL インジェクション
# VULNERABLE: kid used in SQL query without sanitization
def get_signing_key(kid):
result = db.execute(f"SELECT key FROM jwt_keys WHERE id = '{kid}'")
return result.fetchone()[0]
token_header = jwt.get_unverified_header(token)
key = get_signing_key(token_header["kid"]) # attacker controls kid
jwt.decode(token, key, algorithms=["HS256"])
# SECURE: kid validated against allowlist or parameterized lookup
def get_signing_key(kid):
result = db.execute("SELECT key FROM jwt_keys WHERE id = %s", (kid,))
row = result.fetchone()
if not row:
raise ValueError("Unknown key id")
return row[0]
埋め込み JWK インジェクション
// VULNERABLE: trusts the jwk embedded in the token header
const { publicKey } = getPublicKeyFromHeader(decoded.header); // attacker-supplied
jwt.verify(token, publicKey);
// SECURE: only use keys from a pre-configured, trusted source
const trustedKey = loadKeyFromConfig();
jwt.verify(token, trustedKey, { algorithms: ['RS256'] });
実行
このスキルは、サブエージェントを使用して 2 つのフェーズで実行されます。sast/architecture.md の内容を両方のサブエージェントにコンテキストとして渡してください。
フェーズ 1:JWT のライフサイクルをマッピングする
次の指示でサブエージェントを起動してください。
目標:アプリケーションが JWT をどのように作成、送信、検証するかを調査します。JWT の発行および検証が行われるすべての箇所、使用されているライブラリ、署名アルゴリズムとキー/シークレットの設定、認可に使用されるクレームを特定します。結果を
sast/jwt-recon.mdに記録してください。コンテキスト:プロジェクトのアーキテクチャ概要が提供されます。技術スタック、認証レイヤー、ミドルウェアのパターンを理解するために活用してください。
検索対象:
1. JWT ライブラリのインポート — 使用されている JWT ライブラリを特定します。
- Python:
import jwt、from jose import、from authlib import、import python_jose- Node.js:
require('jsonwebtoken')、import jwt from 'jsonwebtoken'、jose、@nestjs/jwt- Java:
io.jsonwebtoken、com.auth0.jwt、nimbus-jose-jwt- Go:
github.com/golang-jwt/jwt、github.com/dgrijalva/jwt-go、github.com/lestrrat-go/jwx- Ruby:
jwtgem(require 'jwt')- PHP:
firebase/php-jwt、lcobucci/jwt- C#:
System.IdentityModel.Tokens.Jwt、Microsoft.AspNetCore.Authentication.JwtBearer2. JWT の署名/発行箇所 — トークンが作成される場所:
jwt.encode(...)、jwt.sign(...)、Jwts.builder().signWith(...)、JWT.create().sign(...)- 使用されているアルゴリズム(
HS256、RS256など)と、シークレット/キーの取得元(環境変数、設定、ハードコード)を記録します。3. JWT の検証/デコード箇所 — トークンが使用される場所:
jwt.decode(...)、jwt.verify(...)、Jwts.parserBuilder()...parseClaimsJws(...)、JWT::decode(...)- 渡されるオプション(
algorithms、options、verify_signature、verify_expなど)を記録します。- 生の
decode(検証なし)なのか、verify呼び出しなのかを記録します。4. トークンの抽出 — 受信リクエストからトークンを読み取る場所:
- Authorization ヘッダー:
request.headers.get("Authorization")、req.headers['authorization']- Cookie:
request.cookies.get("token")、req.cookies.token- クエリパラメーター:
request.args.get("token")、req.query.token5. 認可ミドルウェア/デコレーター — 集中的な JWT チェック:
@jwt_required、@login_required、requireAuth、JwtAuthGuard、[Authorize]、ミドルウェア関数- 保護されているルートと、保護されていないルートを記録します。
6. 署名シークレット/キーの設定:
- HMAC シークレットまたは RSA/EC キーがどこで定義され、読み込まれるか(環境変数、設定ファイル、ハードコードされた文字列)
- 強度が高そうか(長いランダム文字列)、または弱そうか(短い、よく使われる単語)
7. クレームの使用状況:
- 認可のためにどのクレームが抽出され、使用されているか(
user_id、role、permissions、sub)exp、iss、aud、nbfが検証されているか出力形式 —
sast/jwt-recon.mdに記録します。# JWT Recon: [プロジェクト名] ## 概要 このコードベースでは JWT が[使用されている/使用されていない]。 ライブラリ:[ライブラリ名。確認できる場合はバージョンも記載] アルゴリズム:[HS256/RS256 など] ## 発行箇所 ### 1. [説明的な名前 — 例:「ログインエンドポイントでのトークン生成」] - **ファイル**:`path/to/file.ext`(行 X~Y) - **関数/エンドポイント**:[関数名またはルート] - **アルゴリズム**:[例:HS256] - **シークレット/キーの取得元**:[環境変数名/ハードコードされた文字列/設定キー] - **設定されたクレーム**:[ペイロードに追加されるクレームの一覧] - **コードスニペット**:[署名呼び出し]
## 検証箇所 ### 1. [説明的な名前 — 例:「認証ミドルウェアでのトークン検証」] - **ファイル**:`path/to/file.ext`(行 X~Y) - **関数/ミドルウェア**:[関数名] - **検証呼び出し**:[jwt.decode/jwt.verify/parseClaimsJws など] - **アルゴリズムの制限**:[algorithms=["HS256"]/制限なし/不明] - **署名検証**:[有効/無効/不明] - **検証されるクレーム**:[exp/iss/aud/なし/不明] - **トークンの取得元**:[Authorization ヘッダー/Cookie/クエリパラメーター] - **kid/jwk/jku の使用**:[はい — 使用方法を説明/いいえ] - **コードスニペット**:[検証呼び出しとその前後のコード]
## シークレット/キーの設定 - **シークレットの取得元**:[環境変数/ハードコード/設定ファイル] - **推定される強度**:[強い(長いランダム文字列)/弱い(短い/一般的)/不明] - **コードスニペット**(ハードコードされている、または疑わしい場合):[該当するコード]
## 認可ミドルウェアの適用範囲 - **保護されているルート**:[一覧または説明] - **保護されていないルート**:[一覧または「確認されず」]
フェーズ 1 の後:続行する前に JWT の使用状況を確認する
フェーズ 1 の完了後、sast/jwt-recon.md を読みます。要約に JWT が使用されていない(発行箇所も検証箇所も見つからなかった)と記載されている場合は、フェーズ 2 を完全にスキップしてください。代わりに、次の内容を sast/jwt-results.md に書き込み、そこで停止します。
# JWT Analysis Results
No JWT usage detected in this codebase.
フェーズ 1 で JWT の検証箇所が少なくとも 1 つ見つかった場合に限り、フェーズ 2 に進んでください。
フェーズ 2:JWT の検証箇所を分析して脆弱性を調べる
フェーズ 1 の完了後、次の指示で 2 つ目のサブエージェントを起動します。
目標:
sast/jwt-recon.mdに記載されている各 JWT 検証箇所について、悪用可能かどうかを判断してください。アルゴリズムの混同、署名検証の欠如、弱いシークレット、ヘッダーインジェクション攻撃、クレーム検証の欠如を確認してください。最終結果をsast/jwt-results.mdに記述してください。コンテキスト:プロジェクトのアーキテクチャ概要とフェーズ 1 の調査結果が提供されます。両方を使ってトークンのライフサイクル全体を理解したうえで、各箇所を分析してください。
各検証箇所で、以下を確認してください:
確認 1 — アルゴリズムの制限
- 検証呼び出しで許可するアルゴリズムが明示的に指定されていますか?
- アルゴリズムの制限がない場合、トークンの
algヘッダーをnoneに設定して署名検証をスキップできますか?- サーバーが非対称アルゴリズム(RS256、ES256)を使用している場合、検証コードは HMAC アルゴリズム(HS256)も受け入れますか?受け入れる場合、サーバーは RS256→HS256 の混同攻撃に対して脆弱な可能性があります。
確認 2 — 署名検証の有効化
- トークンは実際に署名を検証する verify/parse 呼び出しに渡されていますか、それとも decode のみを行う呼び出しに渡されていますか?
verify_signature: False、complete=False、または Node.js でjwt.verify()の代わりにjwt.decode()を使用している箇所を探してください。- 署名をまったく検証せず、ペイロードを手動で Base64 デコードしている場合は、常に脆弱です。
確認 3 — HMAC シークレットの強度
- シークレットはソースコードにハードコードされていますか?その場合、よく使われる単語や短い文字列ですか?
- シークレットは環境変数または設定から読み込まれていますか?その場合でも、デフォルト値やサンプル値が弱くないか確認してください。
- 32 文字未満、または辞書に載っている単語で構成されたシークレットは、ブルートフォースで解読される可能性が高いです。
確認 4 — 埋め込み JWK/JKU/X5U ヘッダーインジェクション
- 検証コードはトークンヘッダーの
jwkフィールドを読み取り、同じトークンの検証に使用しますか?jkuまたはx5uヘッダーで指定された URL からキーを取得し、その URL を許可リストと照合せずに使用しますか?- いずれかに該当する場合、検証は完全に回避可能です。
確認 5 —
kidヘッダーインジェクション
- 検証前にトークンから
kidヘッダー値を取り出し、キーの検索に使用しますか?kid値は、パラメーター化せずに SQL クエリへ埋め込まれていますか?→ SQL インジェクションkid値は、サニタイズせずにファイルパスの構築に使用されていますか?→ パストラバーサル/キーの差し替え確認 6 — クレーム検証
exp(有効期限)は確認されていますか?確認されていない場合、期限切れトークンが永久に有効になります。iss(発行者)は確認されていますか?確認されていない場合、他の発行者のトークンも受け入れられます。aud(対象者)は確認されていますか?確認されていない場合、他のサービス向けのトークンも受け入れられます。roleやpermissionsのようなセキュリティ上重要なクレームが含まれている場合、サーバー側の情報と照合して検証されていますか?確認 7 — トークンの失効
- トークンブラックリスト、失効エンドポイント、または有効期間の短いトークンとリフレッシュトークンのパターンはありますか?
- トークンの有効期間が長く(数時間以上)、失効の仕組みがない場合、盗まれたトークンは有効なままです。
分類:
- 脆弱:有効な緩和策がなく、弱点が明らかに存在します。攻撃経路を直接悪用できます。
- 脆弱の可能性が高い:弱点が存在する可能性は高いものの、二次的な条件(例:ライブラリのバージョンによる動作、デフォルトのオプション値)の確認が必要です。
- 脆弱ではない:実装はこの確認項目に適切に対処しています。
- 手動レビューが必要:静的解析だけでは、脆弱性の有無を確信をもって判断できません。
出力形式 —
sast/jwt-results.mdに記述してください:# JWT 分析結果:[Project Name] ## エグゼクティブサマリー - 分析した検証箇所:[N] - 脆弱:[N] - 脆弱の可能性が高い:[N] - 脆弱ではない:[N] - 手動レビューが必要:[N] ## 検出結果 ### [VULNERABLE] 説明的な名前 - **ファイル**:`path/to/file.ext`(行 X-Y) - **エンドポイント/関数**:[ルートまたは関数名] - **脆弱性の種類**:[例:「署名検証の欠如」/「alg:none の受け入れ」/「弱い HMAC シークレット」/「JWK ヘッダーインジェクション」/「kid SQL インジェクション」/「exp 検証の欠如」] - **問題点**:[問題の明確な説明] - **攻撃シナリオ**:[手順:攻撃者が行うこと、作成または改変するトークン、獲得するアクセス権] - **影響**:[攻撃者が実現できること — 任意の ID の偽造、権限昇格、他のユーザーのデータへのアクセスなど] - **修正方法**:[具体的な修正 — アルゴリズム制限の追加、verify_signature の有効化、環境変数からのシークレット読み込み、JWKS URL の固定、kid 検索のパラメーター化、exp 検証の追加など] - **動的テスト**:[jwt_tool、hashcat、または curl を使った概念実証。 問題を再現するための正確なコマンドを示してください。 例:
- jwt_tool -X a (alg:none をテスト)
- jwt_tool -X s (RS256→HS256 の混同をテスト)
- hashcat -a 0 -m 16500 wordlist.txt (HMAC シークレットをブルートフォースで解読)
- 手動:ペイロードを変更し、alg:none を設定して、エンドポイントに送信]
### [LIKELY VULNERABLE] 説明的な名前 - **ファイル**:`path/to/file.ext`(行 X-Y) - **エンドポイント/関数**:[ルートまたは関数名] - **脆弱性の種類**:[種類] - **問題点**:[問題と思われる内容] - **不確実な点**:[確認が必要な事項 — 例:「デフォルトの動作はライブラリのバージョンによって異なる」] - **修正方法**:[修正] - **動的テスト**:[悪用を試みるためのペイロードまたはコマンド]
### [NOT VULNERABLE] 説明的な名前 - **ファイル**:`path/to/file.ext`(行 X-Y) - **エンドポイント/関数**:[ルートまたは関数名] - **理由**:[例:「アルゴリズムが HS256 に制限され、強力なシークレットが環境変数から読み込まれ、exp も検証されている」] ### [NEEDS MANUAL REVIEW] 説明的な名前 - **ファイル**:`path/to/file.ext`(行 X-Y) - **エンドポイント/関数**:[ルートまたは関数名] - **不確実な点**:[静的解析だけでは脆弱性の有無を判断できない理由] - **提案**:[手動で確認する内容 — 例:「インストールされている JWT ライブラリのバージョンを確認してください。PyJWT の古いバージョンは、デフォルトで alg:none を受け入れます」]
重要な注意事項
sast/architecture.mdを読み、その内容をコンテキストとして両方のサブエージェントに渡してください。- Phase 2 は Phase 1 の完了後に実行する必要があります。Phase 2 は調査結果に依存します。
- Phase 1 は検出のみを行います。すべての JWT の発行、検証、設定箇所を特定してください。Phase 1 ではセキュリティ評価を行わないでください。それは Phase 2 の役割です。
- Phase 2 は分析のみを行います。Phase 1 で見つかった各検証箇所について、すべての脆弱性クラスを体系的に確認してください。Phase 2 では新たな箇所を検索せず、Phase 1 で見つかった箇所に集中してください。
- Phase 1 で JWT の使用が見つからなかった場合は、Phase 2 を完全にスキップし、「JWT の使用は検出されませんでした」という結果ファイルを作成してください。
- 最も重要な確認項目は、署名検証が無効になっていないか、アルゴリズムが制限されていないか(alg:none/RS256→HS256 の混同)、HMAC シークレットが弱い、またはハードコードされていないかです。これらは認証の完全なバイパスに直結します。
- Node.js の
jsonwebtokenライブラリにおけるjwt.decode()は、デコードのみを行う関数で、署名の検証は行いません。署名を検証するのはjwt.verify()のみです。この2つを混同するのは、よくある重大な間違いです。 - Python の PyJWT は、バージョン 2.0 より前ではデフォルトで
alg: noneを受け入れ、algorithmsパラメーターも必須ではありませんでした。コードベースでバージョンが固定されておらず、アルゴリズムも制限されていない場合は、問題として指摘してください。 - アルゴリズムの混同(RS256→HS256)が成立するには、(a)サーバーが鍵ペアで RS256 を使用していること、(b)公開鍵にアクセスできること、(c)検証コードでアルゴリズムが制限されていないことが必要です。3つすべてに該当する必要があります。
kidインジェクションは見落とされがちです。トークンヘッダーにkidが含まれている場合は、鍵の検索がどのように実装されているかを必ず確認してください。- 判断に迷う場合は、「脆弱ではない」ではなく「手動レビューが必要」に分類してください。セキュリティ評価では、偽陰性は偽陽性よりも深刻です。