ユーザー入力に依存したセキュリティ検査

信頼できない入力に依存したセキュリティ判断

説明

未検証の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 にするだけで検査を回避できます。
  • 変更後: 署名付きの識別子を検証し、サーバーが管理する情報からユーザーと役割を取得します。未検証のクライアント入力同士の一致を、認可の根拠にはしません。

参考資料