説明
未検証のCookie、ヘッダー、クエリ、ルートパラメーターを認証や認可の根拠にすると、保護を回避されるおそれがあります。ユーザーが指定できる値同士を比較しても、本人確認や権限の証明にはなりません。攻撃者は role=admin というCookieを作ったり、Cookieの userId をURLの userId に合わせたりできます。
想定される影響
- 認証の回避や他人へのなりすまし
- 権限の昇格や他人のリソースへのアクセス
- 個人情報、決済情報、内部設定の漏えい
- 管理者向け操作の不正な実行
対処方法
- セッション(
req.session)、検証済みの署名付きCookie(req.signedCookies)、検証済みJWTのクレーム、DBの情報など、サーバー側の信頼できる情報を使ってください。 req.cookies.userIdとreq.params.userIdの比較だけで、本人確認や権限の判断を行わないでください。- 認証されたユーザーの識別情報と権限をサーバー側で確認し、サーバーが定めた認可ポリシーを適用してください。
- Cookieやトークンの改ざんを検証してください。署名付きCookieやサーバーセッションを使い、JWTの署名を検証して、平文の入力をそのまま信頼しないでください。
- 重要なルートに認証と認可のミドルウェアを一貫して適用してください。
- 必要な権限だけを付与し、許可リストを使ってください。
例
変更前
javascript
const express = require("express");
const cookieParser = require("cookie-parser");
const app = express();
app.use(cookieParser()); // 署名がないため、クライアントがCookie値を変更できます。
// 変更前: クライアントが指定するCookieとクエリで認可します。
app.get("/admin", (req, res) => {
// roleもuserもクライアントが指定できます。
if (req.cookies.role === "admin" && req.query.user === req.cookies.user) {
return res.send("admin page");
}
return res.status(403).send("forbidden");
});
変更後
COOKIE_SECRET には十分なエントロピーを持つ秘密値を安全に渡してください。この抜粋は、実際の認証後に発行したCookieを検証する例です。Cookieの発行と本番のDB接続は省略しています。
javascript
const express = require("express");
const cookieParser = require("cookie-parser");
const app = express();
const cookieSecret = process.env.COOKIE_SECRET;
if (!cookieSecret || cookieSecret.length < 32) {
throw new Error("COOKIE_SECRET must contain at least 32 characters");
}
app.use(cookieParser(cookieSecret)); // 署名付きCookieを有効にします。
// ユーザー検索の例です(仮の実装)。
async function findUserById(id) {
// 本番ではDBを検索します。
const mock = { id: "u123", role: "admin" };
return id === "u123" ? mock : null;
}
// 変更後: 検証済みCookieとサーバー管理のデータで認可します。
app.get("/admin", async (req, res) => {
const userId = req.signedCookies.userId; // 署名を検証した識別子です。
if (!userId) return res.status(401).send("login required");
const user = await findUserById(userId); // サーバー側のデータで権限を確認します。
if (!user || user.role !== "admin") return res.status(403).send("forbidden");
return res.send("admin page");
});
説明:
- 変更前: Cookieとクエリの両方をクライアントが指定できるため、値を一致させて役割を
adminにするだけで検査を回避できます。 - 変更後: 署名付きの識別子を検証し、サーバーが管理する情報からユーザーと役割を取得します。未検証のクライアント入力同士の一致を、認可の根拠にはしません。