説明
認証用 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があれば有効期限を検証しますが、この抜粋はその存在自体を必須にしていません。