栖王ヴァルハラ3丁目 ヘッダーイラスト
Illustration by ruder

明日になったら今日じゃない

栖王ヴァルハラ3丁目

すばら3 / Subara3

放置してた自サイトのCSPがReport-Onlyだったので、ちゃんと有効化した

カテゴリ: tech

タグ: CSP, Content-Security-Policy, セキュリティ, AdSense, 個人サイト

CSPのリストアップを眺めてて、「へ~大変だな~、そういえばうちのサイトってどうなってんだっけ」と思ったら、有効ではあったけど言うだけいったろくらいの設定だったので、しっかり直しました。

なんでセキュリティヘッダーなんてちゃんと入れようとしてたんだっけ? あれか? Claudeに「良いサイトにして♡」とだけ丸投げしていろいろやらせていた時の名残か?

CSPとは

CSP(Content Security Policy)とは、Webページが「どこのサーバーからリソースを読み込んでよいか」をサーバー側が宣言するHTTPヘッダーです。

Content-Security-Policy: script-src 'self' https://cdn.example.com;

ホワイトリストに載っていないドメインからのJS・CSSなどの読み込みを、ブラウザがブロックします(どのリソース種別をどこまで縛るかはディレクティブごとに設定でき、たとえば画像は緩めにする、といった運用も可能です)。XSS(クロスサイトスクリプティング)への防御として有効で、攻撃者が悪意あるスクリプトを注入しても、許可されていないドメインからの読み込みは実行されません。

なお、CSPには試験用の Content-Security-Policy-Report-Only というヘッダーがあります。これはブロックせず、違反をログに記録するだけのモードで、本番適用前の動作確認に使います。

まずレスポンスヘッダーを確認

curl -sI でヘッダーだけ取ってみます。

curl -sI https://subara3.com/

curl でヘッダーを確認したところ。content-security-policy-report-only が設定されていた

……あれ、CSP設定されてる。しかも結構ちゃんとした内容で。AdSense(googlesyndication.com 等)やCDN(cdn.jsdelivr.netcdnjs.cloudflare.com)まで一通りホワイトリストに入っていました。他のセキュリティヘッダーも設定されてますね。X-Frame-Optionsとか。そーなんだ。

ヘッダー 設定値
Strict-Transport-Security max-age=31536000; includeSubDomains; preload
X-Frame-Options SAMEORIGIN
X-Content-Type-Options nosniff
Referrer-Policy strict-origin-when-cross-origin
Permissions-Policy geolocation=(), camera=(), microphone=()

うちはロリポップ(共有レンタルサーバー)なので、CSPはCDNやWAFではなくPHP側の header() で出していましたincludes/security-headers.php を各エントリーポイントの先頭で require する形)。なので後で出てくる修正も、Cloudflareの管理画面ではなくPHPファイルの編集になります。

問題点①:Report-Onlyのまま

ヘッダー名が content-security-policy-report-only になっています。つまりブロックは一切していない状態です。

ヘッダー 挙動
Content-Security-Policy 違反をブロックする(本番)
Content-Security-Policy-Report-Only ブロックせず違反を記録するだけ

Report-Onlyは「本番適用前に何が引っかかるか確認するためのモード」なので、いつまでも置いておくものではありません。観測 → 修正 → 本番、と進めて初めて意味があります。

問題点②:report-uriが無い

CSP違反をサーバー側で集めるには report-uri(または report-to)でエンドポイントを指定する必要があります。これが無いと、違反はユーザーのブラウザのコンソールにしか出ない=こちらからは永遠に見えません。

違反を実際に洗い出す

F12のコンソールを目視してもいいのですが、今回はヘッドレスブラウザ(Playwright)で実際にページを読み込み、securitypolicyviolation イベントとコンソール出力を機械的に集めました。出ていた違反がこちらです。

コンソールに出ていたCSP違反(実データを整形)。fundingchoicesmessages.google.com と adtrafficquality.google が複数ディレクティブで弾かれている

不足していたのは次の2系統のドメインでした。

  • fundingchoicesmessages.google.com … AdSenseの同意管理(「広告を表示してよいですか?」のGDPR等対応UI)が使う
  • ep1/ep2.adtrafficquality.google … Google広告の不正クリック検出(sodar)が使う

ハマりどころ:1つのドメインが複数ディレクティブで弾かれる

「同意管理はスクリプトだから script-src に足せばいい」「不正クリック検出はiframeだから frame-src だけ」みたいなことはないようです。

ドメイン 実際に違反したディレクティブ
fundingchoicesmessages.google.com script-src connect-src
*.adtrafficquality.google script-src connect-src frame-src

同じドメインがスクリプトの読み込み・fetch/beacon 通信・iframe表示と、複数の用途で使われるため、複数のディレクティブに同時に引っかかります。「どのディレクティブに足すか」は推測ではなく実際の違反ログで確認するのが鉄則だと痛感しました。

ポイント:広告系ドメインは多段ロードで増える

AdSenseを置くと、HTMLに書いた pagead2.googlesyndication.com だけでなく、そのJSがさらに別ドメインを動的に読み込みます。fundingchoicesmessagesadtrafficquality も、HTMLソースには一切書いていません。HTMLを目視するだけでは絶対に見つかりません。 F12のNetworkタブの Initiator列(このリクエストはどのJSが起点か)や、今回のように違反イベントを集める方法が要ります。

対応①:ホワイトリストを直す

違反ログに基づいて、PHPのCSP文字列に不足ドメインを追加しました。

// script-src と connect-src の両方に追加
... https://fundingchoicesmessages.google.com https://*.adtrafficquality.google
// frame-src にも追加
frame-src ... https://*.adtrafficquality.google;

対応②:report-uriエンドポイントを用意する

外部サービス(Report URI など)を使う手もありますが、今回は違反を自前のログに落とすだけの軽いPHPを置きました。公開エンドポイントなので、本文サイズとログファイルサイズに上限を設けてフラッディング対策をしています(ログ置き場は .htaccess で直接アクセス禁止済み)。

// /csp-report.php(要点)
$raw = file_get_contents('php://input', false, null, 0, 16384); // 16KB上限
if (is_file($logFile) && filesize($logFile) > 5 * 1024 * 1024) { exit; } // 5MB上限
// report-uri形式 {"csp-report":{...}} と report-to形式の両方をパースして1行に整形

そしてCSPに report-uri /csp-report.php; を追加します。

対応③:本番を触らずに「切り替えても大丈夫か」を先に確認

Content-Security-Policy-Report-OnlyContent-Security-Policy に切り替える=ブロックが始まるので、ホワイトリストに抜けがあると正規のリソースまで止まります。広告が消えたら本末転倒です。

そこで、本番のヘッダーを書き換える前に、Playwrightでレスポンスヘッダーだけを新ポリシー(enforcing)に差し替えてライブのページを読み込み、何かブロックされないかを観測しました。

新ポリシーをenforcingで注入したシミュレーション。トップと記事ページで違反0件

トップページと記事ページで違反0件。これで「切り替えても何も壊れない」と確認できてから、ようやく本番のPHPを書き換えました。

切り替え完了

Content-Security-Policy-Report-OnlyContent-Security-Policy に変更してデプロイ。切り替え後のヘッダーがこちらです。

切り替え後のヘッダー。content-security-policy(enforcing)になり report-uri も付いた

切り替え後、改めてライブを読み込んで違反0件・広告も同意UIも正常表示、report-uri のエンドポイントもテスト送信で正しくログに記録されることを確認しました。

なお余談ですが、今回いじったCSPはPHPで描画しているページ(トップ・記事など)にしか乗っていません/tool/ 以下の静的HTMLツール群にはそもそもCSPヘッダーが付いていない、という現状も確認できました。ここは今後の課題です。

注意:unsafe-inline について

今回の設定には 'unsafe-inline' が含まれています。

script-src 'self' 'unsafe-inline' ...

unsafe-inline はHTMLに直接書いた <script> の中身を全部許可するので、これがあるとXSS対策としてのCSPの効果はかなり薄れます。本来は nonce 方式(リクエストごとにランダムなトークンを発行してscriptタグに付ける)に移行するのがベストですが、既存テンプレート全体の改修が必要なのでコスト相談……ということで、今回はここまで。

まとめ

  • CSPは設定して終わりではなく、Report-Onlyで観測 → ホワイトリスト修正 → enforcing切替まで進めて初めて機能する
  • 広告系スクリプトは多段ロードでドメインが増える。HTMLソースだけでは全量が把握できない
  • 1つのドメインが script-srcconnect-srcframe-src と複数ディレクティブで弾かれることがある。足すディレクティブは推測せず違反ログで確認
  • enforcingへの切替は、本番を触る前にヘッダー差し替えでシミュレーションしておくと安全
  • report-uri が無いと違反はユーザーのコンソールにしか出ない。本番運用では用意しておきたい
  • unsafe-inline は応急処置。本格対応はnonce方式への移行が推奨

個人サイトだから……と後回しにしがちですが、広告を貼っている時点でマルバタイジング(広告経由のマルウェア配信)のリスクはゼロではありません。CSPは静的寄りのサイトでも有効な防御の一枚です。

記事

プライバシーポリシー Last Update: 2025/10/14

個人情報の取り扱いについて

当サイトでは、お問い合わせフォームからご連絡いただいた際に、お名前やメールアドレスなどの個人情報をお預かりすることがあります。

これらの情報は、お問い合わせへの返信や必要なご連絡のためにのみ使用し、それ以外の目的で使用することはありません。また、法令に基づく場合を除き、第三者に開示・提供することはありません。

Cookieの使用について

当サイトでは、サイトの利便性向上やアクセス解析のためにCookieを使用しています。

Cookieはブラウザの設定により無効にすることができますが、一部の機能が正しく動作しなくなる場合があります。

アクセス解析ツールについて

当サイトでは、Googleによるアクセス解析ツール「Google Analytics」を使用しています。

Google Analyticsはトラフィックデータの収集のためにCookieを使用しており、このトラフィックデータは匿名で収集されています。個人を特定するものではありません。

この機能はCookieを無効にすることで収集を拒否することができますので、お使いのブラウザの設定をご確認ください。

詳細については、Google アナリティクス利用規約をご参照ください。

広告の配信について

当サイトでは、第三者配信の広告サービス「Google AdSense」を使用しています。

広告配信事業者は、ユーザーの興味に応じた広告を表示するためにCookieを使用することがあります。

Google AdSenseの詳細については、Google 広告に関するポリシーをご参照ください。

アフィリエイトについて

当サイトは、Amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイトプログラムである、Amazonアソシエイト・プログラムの参加者です。

その他、各種アフィリエイトプログラムに参加している場合があります。

著作権について

当サイトに掲載されている文章、画像、その他のコンテンツの著作権は、当サイト管理者または正当な権利を有する第三者に帰属します。

無断転載・複製はご遠慮ください。引用の際は、出典を明記のうえ、リンクを貼っていただけますようお願いいたします。

免責事項

当サイトの情報は、可能な限り正確な情報を掲載するよう努めていますが、その正確性や安全性を保証するものではありません。

当サイトに掲載された内容によって生じた損害等について、一切の責任を負いかねますのでご了承ください。

また、当サイトからリンクやバナーなどによって他のサイトに移動された場合、移動先サイトで提供される情報、サービス等について一切の責任を負いません。

プライバシーポリシーの変更について

当サイトは、個人情報に関して適用される日本の法令を遵守するとともに、本ポリシーの内容を適宜見直し、その改善に努めます。

修正された最新のプライバシーポリシーは常に本ページにて開示されます。

01

ここについて

栖王ヴァルハラ3丁目は、個人(頻子)が運営しているサイトです。小説、ゲーム、アプリケーションなどがあります。サイトのコンテンツは、予告なく改稿したり、削除されることがあります。

(2022/5/29)二次創作置き場を増設しました。


もし何かご用があれば、お問い合わせフォームからお声をおかけください(記名不要)。

応援・ご感想・誤字脱字報告などお気軽にどうぞ

お問い合わせ
02

自己紹介

頻子(ひんこ)です。
ゲームと文章を書くことが好きです。

info★subara3.com (★→@)

活動場所

投げ銭

03

このサイトへのリンク

2017/09/06 有料サーバーに移行 2025/10/10 独自ドメイン化
04

既刊情報

すばら3ロゴ

すばら3の既刊はBOOTH、DLsite、kindle、あるいは各種イベントで入手することができます。

一部の本は、国会図書館に預けてきました
寄稿したものは、それぞれの頒布先をご確認ください。

サークルロゴは高瀬川さんがつくってくださいました。ありがとう!

05

寄稿・お手伝い

06

お仕事

気まぐれに軽めの単発案件を受け付けています。
簡単なスクリプトなど、ご入用のものがありましたらぜひどうぞ!

info★subara3.com (★→@)

07

プライバシーポリシー

最終更新: 2025/10/14

個人情報の取り扱い

お問い合わせフォームからご連絡いただいたメッセージは、必要なご連絡のためにのみ使用します。許可なく第三者に開示・提供することはありません。

Cookieの使用

サイトの利便性向上やアクセス解析のためにCookieを使用しています。ブラウザ設定で無効にできますが、一部機能が正しく動作しなくなる場合があります。

アクセス解析

Google Analyticsを使用しています。トラフィックデータは匿名で収集され、個人を特定するものではありません。

広告配信

第三者配信の広告サービス「Google AdSense」を使用しています。ユーザーの興味に応じた広告表示のためにCookieを使用することがあります。

アフィリエイト

Amazonアソシエイト・プログラム等に参加しています。

著作権

掲載コンテンツの著作権は当サイト管理者または正当な権利者に帰属します。無断転載・複製はご遠慮ください。

免責事項

情報の正確性・安全性を保証するものではありません。当サイトの内容によって生じた損害等について責任を負いかねます。