nullオリジンへの認証情報付きCORS応答の許可

nullオリジンへの認証情報付きCORS応答の許可

説明

Access-Control-Allow-Origin: null と Access-Control-Allow-Credentials: true を返すと、null として直列化される不透明なオリジンにも応答の読み取りを許可する場合があります。攻撃者はsandbox付きiframeやdata URLなどで、このようなオリジンを作れます。クライアントの認証情報オプションとCookieポリシーが送信を許可すると、認証済みの応答から機密情報を読み取られるおそれがあります。

CORSヘッダーは応答を読み取るための条件を設定します。認証情報を作り出したり、すべてのクロスオリジンリクエストを阻止するサーバー側のアクセス制御を提供したりするものではありません。

想定される影響

  • 被害者のCookieを含むリクエストの応答を読まれ、アカウント情報、個人情報、CSRFトークンなどが漏えいするおそれがあります。
  • null をアクセス制御の境界として信頼すると、内部APIや管理用エンドポイントの保護が弱まる可能性があります。
  • 応答の読み取りによりCSRF攻撃を自動化しやすくなり、他のアクセス制御の問題と組み合わせて悪用されるおそれがあります。

対処方法

  • 機密の応答を null オリジンに公開しないでください。認証情報付きリクエストには、許可した具体的なオリジンを返してください。* では認証情報付き応答の読み取りを許可できません。
  • 信頼する正確なオリジンの許可リストと照合してから Access-Control-Allow-Origin を設定してください。オリジンに応じて応答が変わる場合は Vary: Origin も返してください。
  • 一致しない場合はallow-originとallow-credentialsのヘッダーを省略し、OPTIONSプリフライトを403で拒否してください。
  • 必要な経路とオリジンにだけ認証情報付き応答を許可し、サーバーの認証・認可とCSRF対策を別途適用してください。
  • SameSiteでクロスサイトのCookie送信を制限し、Secureで転送経路、HttpOnlyでスクリプトからのアクセスを保護してください。

例

ヘッダー設定と /me のプリフライト処理を比較する抜粋です。認証・認可とCSRFの処理は省略しています。

変更前

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

// nullオリジンと認証情報を許可
app.use((req, res, next) => {
  res.header("Access-Control-Allow-Origin", "null"); // BAD
  res.header("Access-Control-Allow-Credentials", "true"); // BAD
  next();
});

app.get("/me", (req, res) => {
  res.json({ user: "alice", email: "alice@example.com" });
});

変更後

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

const ALLOWLIST = new Set([
  "https://app.example.com",
  "https://admin.example.com",
]);

app.use((req, res, next) => {
  const origin = req.headers.origin;
  if (origin && ALLOWLIST.has(origin)) {
    res.setHeader("Access-Control-Allow-Origin", origin);
    res.setHeader("Access-Control-Allow-Credentials", "true");
    res.setHeader("Vary", "Origin");
  } else {
    // 未承認のオリジン: CORSの許可を省略し、認証情報付きの読み取りを許可しない
    res.removeHeader("Access-Control-Allow-Credentials");
  }
  next();
});

// プリフライト処理の例
app.options("/me", (req, res) => {
  const origin = req.headers.origin;
  if (origin && ALLOWLIST.has(origin)) {
    res.set({
      "Access-Control-Allow-Origin": origin,
      "Access-Control-Allow-Credentials": "true",
      "Access-Control-Allow-Methods": "GET",
      "Access-Control-Allow-Headers": "Content-Type",
      Vary: "Origin",
    });
    return res.sendStatus(204);
  }
  return res.sendStatus(403);
});

app.get("/me", (req, res) => {
  res.json({ user: "alice", email: "alice@example.com" });
});

解説:

  • 変更前: 攻撃者はsandbox付きiframeやdata URLで null オリジンを作れます。このオリジンと認証情報を許可すると、ブラウザーやCookieのポリシーが送信を認める場合に、攻撃者のスクリプトが認証済みの応答を読めるおそれがあります。
  • 変更後: 設定された許可リスト内のオリジンにだけCORSの許可を返します。それ以外のオリジンからは、CORSによる応答の読み取りができません。Vary: Origin により、キャッシュがオリジン別の応答を区別できます。

参考資料