Kodokon kodokon.com

本番環境でAPIを保護する

本番環境のNode APIを堅牢にしましょう。正しいCORS、レート制限、セキュリティヘッダー、そして主要なOWASP APIリスクへの防御です。

11 分 · 3 問

このレッスンを Kodokon で開く

OWASPはAPIに特化したトップ10を管理しています。支配的な三つのリスクは次のとおりです。BOLA(Broken Object Level Authorization、つまりidを変えて他のユーザーのオブジェクトにアクセスすること)、Broken Authentication(不十分に検証されたトークン、弱い秘密鍵)、そしてUnrestricted Resource Consumption(レート、サイズ、時間への制限がないこと)です。この三つのどれも、ライブラリをインストールすれば直るものではないことに注目してください。これらはあなたの設計そのものの性質です。まずは横断的な仕組みから始めましょう。CORS、レート制限、そしてヘッダーです。

JAVASCRIPT
import { createServer } from "node:http";

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

const server = createServer((req, res) => {
  const origin = req.headers.origin;
  if (origin && allowed.has(origin)) {
    res.setHeader(
      "Access-Control-Allow-Origin",
      origin,
    );
    res.setHeader("Vary", "Origin");
  }
  if (req.method === "OPTIONS") {
    res.setHeader(
      "Access-Control-Allow-Methods",
      "GET,POST,PUT,DELETE",
    );
    res.setHeader(
      "Access-Control-Allow-Headers",
      "content-type,authorization",
    );
    res.setHeader("Access-Control-Max-Age", "86400");
    res.writeHead(204).end();
    return;
  }
  res.setHeader("Content-Type", "application/json");
  res.end(JSON.stringify({ ok: true }));
});

server.listen(3000);
手作業でのCORS:許可リストとプリフライト

CORSが何ではないかをはっきり理解しましょう。それはサーバーの保護ではありません。curlやサードパーティのバックエンドは、これらのヘッダーを完全に無視します。ブラウザだけがそれを強制し、自分のユーザーを守るのです。専門家の三つのルールがあります。許可リストから厳密なオリジンを返すこと(Originヘッダーを何も考えずにそのまま返してはいけません)、中間のキャッシュを汚染しないようVary: Originを加えること、そしてリクエストがクッキー(credentials)を運ぶやいなや*ワイルドカードがブラウザに拒否されることを知っておくことです。

JAVASCRIPT
const WINDOW_MS = 60_000;
const LIMIT = 100;
const hits = new Map();

function check(ip) {
  const now = Date.now();
  const entry = hits.get(ip);
  if (!entry || now - entry.start >= WINDOW_MS) {
    hits.set(ip, { start: now, count: 1 });
    return { ok: true, remaining: LIMIT - 1 };
  }
  entry.count += 1;
  const remaining = Math.max(LIMIT - entry.count, 0);
  return { ok: entry.count <= LIMIT, remaining };
}

console.log(check("203.0.113.7"));
メモリ内の固定ウィンドウ:okがfalseなら429を返す

セキュリティヘッダーは数行のコストで、攻撃のまるごと一群を防ぎます。HSTSは今後の訪問にHTTPSを強制し、X-Content-Type-Options: nosniffはブラウザがMIMEタイプを推測するのを防ぎ、制限的なCSPdefault-src 'none')は、誤ってブラウザで表示されてしまったAPIレスポンスの悪用を無力化します。さらに、あなたの技術スタックを明かすヘッダーはすべて取り除きましょう。ExpressはデフォルトでX-Powered-Byを追加します。これはapp.disable('x-powered-by')で無効化できます。

JAVASCRIPT
function setSecurityHeaders(res) {
  res.setHeader(
    "Strict-Transport-Security",
    "max-age=63072000; includeSubDomains",
  );
  res.setHeader("X-Content-Type-Options", "nosniff");
  res.setHeader(
    "Content-Security-Policy",
    "default-src 'none'; frame-ancestors 'none'",
  );
  res.setHeader("Cache-Control", "no-store");
}

export { setSecurityHeaders };
すべてのAPIレスポンスで呼び出すべきもの

理解度チェック

このレッスンの要点をしっかり覚えているか確認しましょう。

  1. CORSの仕組みは実際には何を保護しますか?
    • 自動化されたリクエスト(curl、スクリプト)からサーバーを
    • 許可されていないクロスオリジンの読み取りからブラウザのユーザーを
    • サービス拒否攻撃からAPIを
  2. Access-Control-Allow-Origin: * が、クッキーを送るリクエストと両立しないのはなぜですか?
    • ブラウザは、認証済みのセッションをどんなサイトにもさらさないよう、この組み合わせを拒否する
    • サーバーが自動的に500エラーを返す
    • ワイルドカードがサーバー側でクッキーを無効化する
  3. 認証済みのAPIが、その請求書を所有していないユーザーに GET /invoices/42 を返します。これはどのOWASP APIリスクですか?
    • インジェクション
    • Broken Object Level Authorization(BOLA)
    • セキュリティの設定ミス
    • サーバーサイドリクエストフォージェリ