JWT の署名検証の不備

none アルゴリズムによる JWT 署名検証の不備

説明

認証用 JWT で署名のない none アルゴリズムを許可すると、攻撃者が変更したペイロードを有効なトークンとして受け入れるおそれがあります。jsonwebtoken 9.x では、空のキーを渡すだけで自動的に署名検証が省略されるわけではありません。変更前の例は、空のキーと併せて algorithms に none を明示的に許可しています。署名を検証せずにユーザー識別子や権限を信頼すると、認証・認可の回避につながる可能性があります。

想定される影響

  • 偽造したトークンで認証済みユーザーになりすますおそれがあります。
  • role や admin の値を変更し、過剰な権限を得られる可能性があります。
  • 保護された API や機密データにアクセスされるおそれがあります。
  • 他のユーザーを装って、サービスのリソースを継続的に使われる可能性があります。

対処方法

  • verify() には、有効で信頼できるシークレットまたは公開キーを渡してください。
  • アルゴリズムの許可リストを指定し、'none' を除外してください。例は { algorithms: ["HS256"] } や { algorithms: ["RS256"] } です。
  • decode() はトークンを解析するだけです。その結果を認証・認可で信頼せず、verify() と信頼するキーを使用してください。
  • iss(発行者)、aud(対象)、exp(有効期限)などを検証してください。有効期限が必須なら、exp が存在することも確認してください。

例

変更前

javascript
const express = require("express");
const jwt = require("jsonwebtoken");
const app = express();

// 変更前: キーなし(空文字列など)で検証
app.get("/profile", (req, res) => {
  const auth = req.headers.authorization || "";
  const token = auth.replace(/^Bearer\s+/i, "");

  // BAD: 空のキーと明示的な none の許可により、署名のないトークンを受け入れる
  const payload = jwt.verify(token, "", { algorithms: ["HS256", "none"] });

  // 偽造したペイロードでもアクセスできる可能性
  res.json({ user: payload.sub, admin: payload.admin });
});

app.listen(3000);

変更後

javascript
const express = require("express");
const jwt = require("jsonwebtoken");
const app = express();

const JWT_SECRET = process.env.JWT_SECRET; // 安全に保管すること

app.get("/profile", (req, res) => {
  const m = (req.headers.authorization || "").match(/^Bearer\s+(.+)$/i);
  if (!m) return res.status(401).send("Missing token");

  try {
    const payload = jwt.verify(m[1], JWT_SECRET, {
      algorithms: ["HS256"], // none を許可せず、必要なアルゴリズムだけを指定
      issuer: "auth.example.com", // iss を検証
      audience: "my-api", // aud を検証
    });
    return res.json({ user: payload.sub });
  } catch (e) {
    return res.status(401).send("Invalid token");
  }
});

app.listen(3000);

説明:

  • 変更前: 空のキーと algorithms 内の none の許可により、署名のないトークンを受け入れます。攻撃者が指定したペイロードを認証済みの情報として信頼するおそれがあります。
  • 変更後: 信頼するキーと限定したアルゴリズムで none を除外し、iss と aud も確認して検証に失敗したリクエストを拒否します。JWT_SECRET には十分に強いシークレットを設定してください。exp があれば有効期限を検証しますが、この抜粋はその存在自体を必須にしていません。

参考資料