結論(ページ間のセキュリティヘッダー整合性)
ページ間のセキュリティヘッダー整合性とは、4つのヘッダーがページ間で同じ値で一貫して返っているかという品質です。対象はContent-Security-Policy、X-Content-Type-Options、Referrer-Policy、X-Frame-Optionsで、トップページと下層ページ・404ページ・アプリ経由のページを比べます。攻撃者は最も守りの弱いページを狙うため、1ページでも欠ければそのページは無防備です。ヘッダーはページ単位ではなく、Webサーバー全体やCDNで一括付与し、エラー応答にも付くようにします。
この記事のポイント
- 比較するのはCSP・X-Content-Type-Options・Referrer-Policy・X-Frame-Optionsの4つのヘッダーです。
- OWASP ASVS 5.0.0はnosniffやframe-ancestorsを「すべての応答」に付けるよう求めており、要件自体が一貫性を前提にしています。
- 差が出る主な原因は、Apacheでalwaysを付けない記述、nginxのadd_header継承の切れ目、CMSと別アプリの混在、metaタグでの設定です。
- セキュリティAIチェッカーはトップページと同一オリジンの下層ページ(合計最大5ページ)を比べ、差があれば△(要確認)にします。×は出しません。
- CSPのnonceは応答ごとに変わるのが正しいため、比較の前に正規化しています。
| 診断項目名 | ページ間のセキュリティヘッダー整合性 |
|---|---|
| 項目ID | crawl.security-header-consistency |
| 分類 | セキュリティヘッダー |
| 重大度・配点の重み | 中(medium) 重み 2 |
| 関連する標準・ガイドライン | OWASP ASVS 5.0.0 V3.4.3(CSP)、V3.4.4(全応答に nosniff)、V3.4.5(Referrer-Policy)、V3.4.6(全応答に frame-ancestors)/OWASP WSTG-CONF-12(Content Security Policy)、WSTG-CONF-14(Other HTTP Security Header Misconfigurations)/CWE-693(Protection Mechanism Failure)、CWE-1021(Improper Restriction of Rendered UI Layers or Frames) |
ページ間のセキュリティヘッダー整合性とは
ページ間のセキュリティヘッダー整合性とは、同じサイトの中で、トップページ・下層ページ・お知らせ詳細・検索結果・フォームの確認画面・404ページなどが、同じセキュリティヘッダーを同じ値で返しているかという、設定の一貫性のことです。
トップページにだけ CSP や X-Frame-Options を付けて安心してしまい、下層ページでは同じヘッダーが出ていない、というのは自治体サイトで非常によく見られる状態です。攻撃者はもっとも守りの弱いページを狙うため、「1ページでも欠けていればそのページは無防備」という性質があります。
セキュリティAIチェッカーのこの項目は、トップページと、同一オリジン内で巡回した下層ページ(合計最大5ページ)の間で、主要なセキュリティヘッダー4種の値が揃っているかを比較します。値の良し悪し(例:CSPが十分に厳しいか)は別の項目で評価し、ここでは「設定が全ページへ一貫して適用されているか」という運用・構成面の品質だけを見ます。
比較対象の4つのセキュリティヘッダー
| ヘッダー | 役割 | OWASP ASVS 5.0.0 の要求 |
|---|---|---|
Content-Security-Policy | スクリプト・スタイル等の読込元制限、frame-ancestors による埋め込み制限 | V3.4.3、V3.4.6(frame-ancestors は「すべての HTTP 応答」) |
X-Content-Type-Options | nosniff で MIME スニッフィングを禁止 | V3.4.4「all HTTP responses」 |
Referrer-Policy | Referer で送る URL 情報の範囲を制御 | V3.4.5 |
X-Frame-Options | 旧来のクリックジャッキング対策(DENY/SAMEORIGIN) | V3.4.6 は obsolete(廃れた方式)で依拠不可と注記(旧ブラウザ向けの補完) |
ASVS 5.0.0 は、nosniff や frame-ancestors について「すべての応答」に付与することを明示しています。つまり要件そのものが「一貫性」を前提にしており、トップページだけで満たしても要件を満たしたことにはなりません。
なぜページごとにセキュリティヘッダーの差が出るのか
ページ間でヘッダーが揃わない原因の多くは、設定の書き方と配信経路の違いです。代表的な4つの原因を押さえておくと、診断で差が見つかったときに原因を特定しやすくなります。
1. Apache の Header set と Header always set の違い
Apache の mod_headers は、既定の条件 onsuccess では成功応答にしかヘッダーを付与しません。always を付けると、エラー応答や ErrorDocument による内部リダイレクトにも付与されます。「404 ページだけヘッダーが欠ける」という状態の典型的な原因です。
2. nginx の add_header の継承
nginx 公式ドキュメントは、add_header は「現在のレベルに add_header が1つもない場合に限り」上位レベルから継承されると説明しています。location に1行でも add_header を書くと、server で設定したセキュリティヘッダーがその location では全部消えます。
また always を付けないと、200/201/204/206/301/302/303/304/307/308 以外の応答(404 や 500 など)にはヘッダーが付きません。なお nginx 1.29.3 以降では、add_header_inherit merge でこの継承の挙動を変更できます。
3. CMS とアプリの混在
静的HTMLは Web サーバーの設定、問い合わせフォームや施設予約は別の PHP/Java アプリ、検索は外部 ASP、というように配信経路が分かれていると、経路ごとにヘッダーを付ける仕組みが異なります。同じホスト名の下にあっても、アプリ側の応答だけヘッダーが無い、値が違う、ということが起こります。
4. meta http-equiv による設定
テンプレートに meta タグで CSP を書いている場合、そのテンプレートを使わないページでは欠落します。さらに、meta で指定した CSP では frame-ancestors が無効で、X-Frame-Options は meta では機能しません。HTTPヘッダーで返す必要があります。
ページ間のセキュリティヘッダー整合性についてよくある誤解
- 「トップページの診断で A 評価なら全体も安全」:下層ページは別の応答であり、ヘッダーは応答ごとに評価されます。トップページの結果は下層ページの安全性を保証しません。
- 「CSP の nonce が毎回違うので不一致に見える」:nonce は応答ごとに変わるのが正しい動作です。この項目は nonce の値を正規化してから比較するため、nonce の違いだけで差ありとは判定しません。
- 「ページによって CSP を変えるのは悪いこと」:地図や動画の埋め込みページだけ
frame-srcを追加するなど、意図的な差分は正当な場合があります。そのためこの項目は fail(×)ではなく warn(△ 要確認)にしています。
ヘッダーが欠けていること自体の評価は、「nosniffを全応答へ適用」、「クリックジャッキング対策を全HTMLへ適用」、「CSP frame-ancestorsを全HTMLへ適用」などの別項目が担当します。このページの項目は「差があるかどうか」に集中しています。
ページ間のセキュリティヘッダー整合性の診断基準(セキュリティAIチェッカー)
取得対象
- 入力 URL の最終到達ページ(トップページ)。
- トップページ内の
a要素の href、GET フォームの action、/api/宛ての URL(フォームの action と HTML 中の"/api/…"という文字列)から抽出した同一オリジンの URL を、ログイン・管理・アカウント系リンク → GET フォーム →/api/→ その他のリンクの優先順で最大4件(トップページと合わせて最大5ページ)。 - 画像・CSS・JS・JSON・PDF 等の拡張子のURLと、logout/delete 等の状態変更を連想させるパスは除外します。
- 取得は GET のみで、タイムアウト7秒、リダイレクトは最大3回まで追跡します。取得に失敗したページ、500KB を超える応答、同一オリジン外へ転送されたページは比較から除きます。
- HTTPステータスは問わないため、404 のページや API の JSON 応答も比較対象になります。
比較方法(正規化のルール)
各ページの4ヘッダーについて、次の正規化を行った値の集合を作り、ヘッダーごとに値の種類が2つ以上あれば「差がある」と判定します。
| ヘッダー | 正規化 |
|---|---|
| CSP | 'nonce-…' を 'nonce-*' に置換し、連続する空白を1つにまとめ、末尾のセミコロン・空白を除去 |
| その他3種 | 前後の空白を除去し、小文字化し、カンマ前後の空白を除去 |
| 未送信 | <missing> として扱う(あるページは送信し、別のページは未送信なら差あり) |
判定条件
| 判定 | 条件 | 得点への影響 |
|---|---|---|
| ○ 問題なし(pass) | 2ページ以上を取得し、4ヘッダーすべてが全ページで同一 | 重み2 × 1.00 |
| △ 要確認(warn) | いずれかのヘッダーでページ間に差がある(証拠欄に差のあるヘッダー名を列挙) | 重み2 × 0.45 |
| 対象外(na) | 追加ページを1件も取得できず比較できない | 計算対象外 |
- この項目は × 要改善(fail)を出しません。欠落そのものは
crawl.nosniff-coverage、crawl.frame-coverage、crawl.csp-frame-ancestors-coverage等の別項目が fail として評価します。 - 意図的な差分がありうるため △ にとどめています。△ の場合は、差が「意図した設定」か「設定漏れ」かを確認してください。
自治体サイトでの判定例
| サイトの状態 | 判定 |
|---|---|
トップページは CMS テンプレートで Referrer-Policy: strict-origin-when-cross-origin、/kurashi/ 配下も同じ値 | ○ |
404 になったリンク先ページだけ X-Content-Type-Options が無い(Apache の Header set を always なしで記述) | △「ページ間で差があるヘッダー: x-content-type-options」 |
| 施設予約システム(同一ホストの別アプリ)が CSP を返さない | △「content-security-policy」 |
ページ間でセキュリティヘッダーが揃っていないとどんなリスクがあるか
- 最弱ページの悪用:CSP の無いページに XSS(クロスサイトスクリプティング)があれば、他のページの CSP は何の役にも立ちません。同一オリジンのため、そのページで実行されたスクリプトは Cookie や同一オリジンの API へアクセスできます。
- クリックジャッキング:frame-ancestors や X-Frame-Options の欠けたフォームページ(申請・アンケート等)が、外部サイトに透明な iframe で埋め込まれ、利用者が意図しない送信をさせられる恐れがあります。
- MIME スニッフィング:nosniff が無い応答では、利用者がアップロードしたファイルや JSON が HTML やスクリプトとして解釈されるケースがあります。
- Referer 経由の情報漏えい:Referrer-Policy の無いページから外部リンクを踏むと、検索語やパラメータ付きの URL が外部へ送られる場合があります。現在の主要ブラウザの既定は
strict-origin-when-cross-originですが、明示することが推奨されます。 - 監査上の指摘:第三者診断ではページ単位で指摘されるため、「一部ページで欠落」は是正対象として繰り返し報告されます。
ページ間のセキュリティヘッダーを揃える設定方法(Apache・nginx・IIS・Next.js)
基本方針
ヘッダーはページ単位ではなく、サーバー全体(またはリバースプロキシ・CDN)で一括付与し、エラー応答にも付くようにします。個別のページで値を変える必要がある場合は、その差分を文書化しておきます。
Apache(.htaccess / httpd.conf)
<IfModule mod_headers.c>
# always を付けてエラー応答・ErrorDocument にも付与する
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Content-Security-Policy "frame-ancestors 'self'; object-src 'none'; base-uri 'self'"
</IfModule>
- 下層ディレクトリの .htaccess に
Header setを追加すると、意図せず別の値で上書きされることがあります。変更前にgrep -r "Header" --include=.htaccessでサイト全体を棚卸ししてください。 - CSP を全ページへ一気に強化すると、インラインスクリプトを使うページが壊れます。まず
Content-Security-Policy-Report-Onlyで影響を確認し、frame-ancestors など副作用の小さいディレクティブから適用します。
nginx
server {
# 全ヘッダーに always を付ける
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Content-Security-Policy "frame-ancestors 'self'; object-src 'none'; base-uri 'self'" always;
location /api/ {
# ここで add_header を1つでも書くと server の add_header は継承されない。
# 共通ヘッダーは include ファイルにまとめ、各 location でも include する。
include /etc/nginx/snippets/security-headers.conf;
add_header Cache-Control "no-store" always;
}
}
nginx 1.29.3 以降であれば、add_header_inherit merge; で上位レベルの値を合成する方法もあります。
IIS(web.config)
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="X-Content-Type-Options" value="nosniff" />
<add name="Referrer-Policy" value="strict-origin-when-cross-origin" />
<add name="X-Frame-Options" value="SAMEORIGIN" />
</customHeaders>
</httpProtocol>
</system.webServer>
サブアプリケーションの web.config に <clear /> があると、親で設定したヘッダーが消える点に注意してください。
Next.js(Vercel)
// next.config.js
module.exports = {
async headers() {
return [{
source: "/:path*",
headers: [
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{ key: "X-Frame-Options", value: "SAMEORIGIN" },
],
}];
},
};
nonce 付き CSP を Middleware(proxy)で付与している場合は、matcher が静的ページやエラーページを除外していないか確認します。
CMS
テンプレートの meta http-equiv ではなく、サーバー設定またはプラグインの「HTTP ヘッダー出力」機能を使います。meta では frame-ancestors や X-Frame-Options が機能しないためです。
設定後の確認方法
for u in / /kurashi/ /not-exist-page /contact/; do
echo "== $u"
curl -s -o /dev/null -D - "https://www.city.example.lg.jp$u" \
| grep -iE '^(content-security-policy|x-content-type-options|referrer-policy|x-frame-options):'
done
存在しないパス(404)と、フォーム・検索などアプリ経由のページを必ず含めて比較してください。設定を変えたら、セキュリティAIチェッカーで再診断し、証拠欄に差のあるヘッダー名が出なくなったことを確認します。
サイトを壊さないための注意点(ガードレール)
- 一部のページの CSP を意図的に緩めている場合は、その理由と対象パスを設定ファイルのコメントに残し、△ の根拠として説明できるようにします。
- X-Frame-Options を
DENYにすると、自サイト内で iframe 表示しているページ(地図・広報誌ビューア等)が表示されなくなります。まずSAMEORIGINを基準にします。 - 外部委託の別システム(施設予約・電子申請)も、同じオリジンなら同じ評価を受けます。委託先へヘッダーの要件を仕様書で示してください。
よくある質問(FAQ)
トップページのセキュリティヘッダー診断でA評価なら、サイト全体も安全ですか?
安全とは限りません。セキュリティヘッダーは応答ごとに評価されるため、下層ページや404ページ、別アプリの画面で欠けていれば、そのページは無防備です。下層ページとエラー応答も含めて確認してください。
404ページだけセキュリティヘッダーが出ないのはなぜですか?
Apacheで always を付けずに Header set と書いている場合が典型です。mod_headers の既定条件 onsuccess は成功応答にしか付与しないため、Header always set に変えるとエラー応答や ErrorDocument にも付くようになります。
nginxでlocationを追加したらセキュリティヘッダーが消えました。原因は何ですか?
nginxの add_header は、現在のレベルに add_header が1つもない場合に限って上位から継承されます。location に1行でも add_header を書くと server の設定が継承されなくなるため、共通ヘッダーを include ファイルにまとめて各 location でも読み込みます。nginx 1.29.3以降では add_header_inherit merge も使えます。
CSPのnonceが毎回違うと、不一致と判定されますか?
判定されません。nonceは応答ごとに変わるのが正しい動作なので、セキュリティAIチェッカーは 'nonce-…' を 'nonce-*' に置き換えてから比較します。
ページによってCSPを変えるのは間違いですか?
間違いとは限りません。地図や動画を埋め込むページだけ frame-src を追加するなど、意図的な差分は正当な場合があります。そのためこの項目は×ではなく△(要確認)とし、理由と対象パスを設定ファイルのコメントに残すことを勧めています。
metaタグでCSPやX-Frame-Optionsを設定すれば全ページに効きますか?
効きません。metaで書くとそのテンプレートを使わないページでは欠落しますし、metaのCSPでは frame-ancestors が無効で、X-Frame-Options はmetaでは機能しません。サーバー設定かHTTPヘッダー出力機能を使ってください。
この項目を含む127項目を、URLを入れるだけで無料診断します。