WordPressで例えるHTTPセキュリティヘッダーまとめ
2013年頃にはContent-Security-Policy(CSP)が使えるようになり、2019年頃までにはCOEP, COOP, CORP, CORSが使えるようになりました。
これらのヘッダーは度々アップデートされており、新しい値が設定できるようになっています。
Expect-CTのように不要になったヘッダーもあります。
セキュリティヘッダーについて調べると古い情報が出てきてしまいます。
しかも年々複雑化していて、今となっては何を設定すれば分からないという事態に陥りがちです。
それらを自分なりに整理してみました。
HTTPセキュリティヘッダーとは
Webサーバーは、接続してきたユーザーに対してページを返却します。
このページを正しく制御するために付与するのがHTTPヘッダーです。

HTTPヘッダーは自由に追加することができ、中にはセキュリティを強化するものもあります。
これがセキュリティヘッダーと言われています。
HTTPヘッダーの恩恵を受けられるのはサーバー側ではなくクライアント側です。
つまりここでのセキュリティ強化とは、接続してきたユーザーが安全にサイトを閲覧できるように保護することです。
セキュリティヘッダーの概要
このサイト(WordPress)に設定しているセキュリティヘッダーです。
手っ取り早く正解を知りたい方向けです。
Apache
Header always set X-Content-Type-Options "nosniff"
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
Header always set Content-Security-Policy "frame-ancestors 'self'"
Header always set Permissions-Policy "geolocation=(),microphone=(),camera=()"
Header always set Cross-Origin-Resource-Policy "same-origin"
Header always set Cross-Origin-Embedder-Policy "unsafe-none"
Header always set Cross-Origin-Opener-Policy "same-origin-allow-popups"
Nginx
add_header X-Content-Type-Options "nosniff" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header Content-Security-Policy "frame-ancestors 'self'" always;
add_header Permissions-Policy "geolocation=(),microphone=(),camera=()" always;
add_header Cross-Origin-Resource-Policy "same-origin" always;
add_header Cross-Origin-Embedder-Policy "unsafe-none" always;
add_header Cross-Origin-Opener-Policy "same-origin-allow-popups" always;
上記ApacheとNginxの設定値の要約
ヘッダー名 | 上記の設定の意味(要約) | 対策対象の攻撃 |
---|---|---|
X-Content-Type-Options | MIMEスニッフィングを無効化し、ファイル形式はContent-Typeで判定させる | XSS |
Content-Security-Policy | 通称CSP。別オリジンからの埋め込みコンテンツでの閲覧を禁止させる | クリックジャッキング |
Permissions-Policy | GPS、マイク、カメラの使用を禁止する | プライバシーの侵害 |
Cross-Origin Resource-Policy | 通称CORP。別オリジンから直接リソースを参照する場合、レスポンスを空にさせる | サイドチャネル攻撃、XS-Leaks |
Cross-Origin-Embedder-Policy | 通称COEP。unsafe-noneは規定値なので設定しなくともよい | ※規定値なのでなし |
Cross-Origin-Opener-Policy | 通称COOP。別オリジンからwindow.openerやpostMessageを利用できないようにさせる | サイドチャネル攻撃、XS-Leaks |
付与すべきヘッダー
X-Content-Type-Options
https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/X-Content-Type-Options
付与すべき理由:ブラウザにファイル形式を誤認させないため
ブラウザは、ファイルの中身を見てファイル形式を判別することがあります。
言い換えればファイル名がimage.pngでも、中身がJavaScriptっぽければJavaScriptとして解釈することがあります。
この仕様が脆弱性の原因になってしまいました。(特に昔のIEが悪名高かった)
ファイル形式がセットされているContent-Typeヘッダーを無視して勝手に判別することが問題だったので、Content-Typeヘッダーに強制的に従わせるようにするのが、このX-Content-Type-Optionsヘッダーです。
設定値
設定値は「nosniff」だけです。
WordPressに限らず、特に理由がなければ「X-Content-Type-Options "nosniff"」を設定していいでしょう。
Permissions-Policy
https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/X-Content-Type-Options
付与すべき理由:プライバシーの侵害を防ぐため
Permission-Policyは2017年頃から登場したヘッダーです。聞いたことがない方も多いと思います。
JavaScriptから位置情報などを取得できますが、それらを機能単位で禁止することができます。
位置情報とマイクとカメラの場合、もしこのヘッダーを設定しなくても、JavaScriptからアクセスがあった場合はユーザーに許可を求めるダイアログが表示されます。
このヘッダーを使用するとそれすらも表示されなくなり、JavaScriptのコード上はエラーが発生するようになります。
WordPressでは?
このサイトでは「Permissions-Policy "geolocation=(),microphone=(),camera=()"」を設定しています。
左から順に、位置情報、マイク、カメラを使用禁止にしています。
通常、WordPressではこれらの機能を使うことはないでしょうから、無効にすべきです。
もし使用するものがあれば、記述ごと削除してください。
ちなみにプライバシーの侵害を防ぐためにこのヘッダーを設定するとしましたが、本当は対象の機能がたくさんあります。詳しくは以下をどうぞ。(随時更新されるっぽいので見ておくといいかも)
上記のような情報を見ると、何を制限すべきか迷うかもしれません。
本当に重要な機能にアクセスする時はユーザー側に確認ダイアログが出ますから、必ずしも設定しなければならないものではない、ということを念頭に入れましょう。
Strict-Transport-Security (HSTS, STS)
https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/X-Content-Type-Options
付与すべき理由:HTTPS通信を強制させるため
1度でもHTTPSで通信したサイトなら、2回目以降もHTTPSを強制するヘッダーです。
なぜそうする必要があるかというと、一瞬でもHTTP通信をすると暗号化していないデータが漏えいする可能性があるからです。
最初から最後までHTTPS通信をさせることで、通信中の暗号化していないデータを減らします。
※Google Chrome 90からHTTPSを優先する仕様に変更されたので、このヘッダーもそのうち必要なくなるかもしれません。

設定値
このサイトでは「Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"」を設定しています。
max-ageにはHTTPSで強制的に接続する期間を設定します。63072000秒=2年間です。
includeSubDomainsはサブドメインにもHSTSを強制します。
1点だけ注意事項があって、example.comがHTTPSだけどsub.example.comはHTTP、という環境だとsub.example.comに接続できなくなるので注意が必要です。
3つ目のpreloadは、GoogleのHSTS Preloadサービスのための設定です。
これに登録しておくと、Chrome/Edge/Firefox/Safariなどの主要ブラウザで、2回目だけでなく初回アクセス時にもHTTPSで接続するようになります。
ただし、申請には以下の条件が必要です。
- max-ageは31536000以上であること (1年間。推奨は2年間の63072000)
- includeSubDomainsがあること
- preloadがあること
つまり「Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"」にして、HSTS Preloadに申請しておくと完璧です。
WordPressに限らず、これを設定していいでしょう。
Content-Security-Policy (CSP)
https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/X-Content-Type-Options
付与すべき理由:クリックジャッキングを防ぐため
iframeなどのページ内埋め込み機能を制限することで、クリックジャッキングを防ぐことができます。
クリックジャッキングというのは、埋め込み機能を悪用して見た目とは異なる場所をクリックしたことにしてしまう手法のことです。
設定値
- frame-ancestors 'self'
iframeなどの読み込み条件を同じドメイン(サブドメインを除く)に限定します。
異なるドメインを許可したい場合は、「frame-ancestors 'self' domain1.com domain2.com」のようにしてください。
後述しますが、CSPのframe-ancestorsはX-Frame-Optionsと同じものです。
より推奨されているCSPを紹介しました。
WordPressであってもなくても「frame-ancestors 'self'」は設定してよいでしょう。
ちなみにCSPにはupgrade-insecure-requestsという値もありますが、こちらはHSTSを設定している場合は不要です。
付与しなくていいヘッダー
X-XSS-Protection
https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/X-XSS-Protection
付与しない理由:主要なブラウザがもう対応していないため
過去にはFirefox以外の全てのブラウザが対応していましたが、2022年現在でサポートしている主要ブラウザはSafariのみです。近いうちにSafariからも削除されるでしょうから、もう設定しなくてよいでしょう。
なお対応していた頃には、設定するとXSS攻撃を防ぐためのフィルタが有効になりました。
しかし、XSSフィルタでも防げない攻撃があることから廃止されました。
メンテナンス上の問題も抱えていたようです。
詳しい事情は、当時X-XSS-Protectionに対応しなかったFirefoxの開発者によるやり取りが参考になります。
※英語
X-Frame-Options
https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/X-Frame-Options
付与しない理由:CSPのframe-ancestorsで代用できるため
「Content-Security-Policy: frame-ancestors 'self';」を指定することで「X-Frame-Options SAMEORIGIN;」と同等になります。そのため、W3CによってX-Frame-Optionsからframe-ancestorsに置き換えることが推奨されています。
frame-ancestors controls the protected resource’s ability be embedded in other documents. It is meant to supplant the X-Frame-Options HTTP request header.
1.1. Changes from Level 1
Referrer-Policy
https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/X-Content-Type-Options
付与しない理由:規定値が「strict-origin-when-cross-origin」になったため
リファラとは参照元のことです。
リンクをクリックしたときに、どのページから遷移したかの情報も送られます。
リファラには参照元のURLが含まれているので、URLにIDやメールアドレスなどの情報が入っていると、その情報まで送られてしまいます。
この情報を制御できるのがReferrer-Policyヘッダーです。
以前は「Referrer-Policy "strict-origin-when-cross-origin"」の設定が推奨されていました。この設定値は同じサイト内、かつ、HTTPSからHTTPS(もしくはHTTPからHTTP)に遷移する場合のみリファラを送信するものです。
2021年4月以降は主要な全てのブラウザの規定値が「strict-origin-when-cross-origin」に変更されました。


そのため、特に設定せずともセキュアな状態を保てます。
必要がなくなったヘッダーではなく、他の設定値も設定可能ですし、2021年4月より前のブラウザに対応する場合は設定が必要です。
ケースバイケースで使うかどうかを決めましょう。
Expect-CT
https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Expect-CT
付与しない理由:2021年6月以降、Expect-CTが必要なくなったため
2015年以降、Certificate Transparencyという不正な証明書を検知する仕組みが有効になりました。
この仕組みの実現には、証明書にSCTと言われる情報の埋め込みが必要です。
SCTに対応した証明書が普及するまでに時間がかかるので、移行期間中はSCTに対応した証明書と対応していない証明書が入り乱れていました。
そこで、SCTに対応した証明書であることをExpect-CTを付けて伝えていた時期があります。
2021年6月でSCTに対応していない証明書が全て失効したため、現在はExpect-CTを付けなくても対応しているものとして認識するようになりました。
限られた状況で付与すべきヘッダー
ここで紹介するHTTPヘッダーは、主にJavaScriptで使える一部機能のために存在しています。
はじめに
「同一オリジン」という言葉の意味を理解しないと、この項のHTTPヘッダーは理解できないと思います。
以下が分かりやすいのでどうぞ。
https://www.tohoho-web.com/ex/same-origin-policy.html
CORSについて
https://developer.mozilla.org/ja/docs/Web/HTTP/CORS
JavaScriptの機能には、リアルタイムにサーバーと通信するためのXMLHttpRequestなどの機能があります。
同じサイト内の通信であればほぼ自由に通信できますが、そうでなければ決められた制約の中で通信をしなければなりません。
そこで生まれたのがCORS (Cross-Origin Resource Sharing)という考えです。
予め指定したオリジン間であれば制約を取り払った上で通信を行うことができます。
Access-Control-Allow-Origin
https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
CORSに基づいて許可を行うヘッダーがAccess-Control-Allow-Originヘッダーです。
JavaScriptを使って、サイトA(https://example-a.com) からサイトB(https://example-a.com) のリソースにアクセスするとき、異なるサイトなので、サイトBはアクセスを拒否することがあります。
この場合、サイトBはAccess-Control-Allow-Originヘッダーを設定していないものとします。
そこで、サイトBは以下のヘッダーを返すようにします。
Access-Control-Allow-Origin: https://example-a.com
上記は「サイトBはサイトAを許可する」という意味になります。
これでサイトA以外からのアクセスを拒否しつつ、サイトAだけを許可する設定が可能です。
WordPressでは?
基本的にCORSは設定しなくていいです。
というのも、通常、WordPress内のリソースのアクセスは同サイトの中だけで完結するからです。
CORSはシステムの管理者、開発者が真価を発揮できるヘッダーです。
ユーザー向けではありません。
CORP/COEP/COOPについて
CORSとは別に、JavaScriptに存在する機能のうち、2018年に突如発見されたSpectreという脆弱性の影響を受ける機能が使用できなくなる or 制限を受ける事件がありました。
それでは今まで動作したシステムが動作しなくなってしまいます。
そこで「特定の条件下ならば制限なく使ってもいい」という発想のもと策定されたのが以下です。
- CORP (Cross-Origin-Resource-Policy)
- COEP (Cross-Origin-Embedder-Policy)
- COOP (Cross-Origin-Opener-Policy)
この3つに関しては、以下の方(Chromeの開発者の1人)の記事が詳しいので、興味があればご覧ください。

Spectreって何? という方はこちらをご覧ください。

つまりCORP/COEP/COOPを使用しなければ「JavaScriptにおいてSpectreの影響を受ける機能」は使用できないということです。
しかしはっきり言ってしまうと、それらの機能はほとんどのWebアプリケーションで使用しません。
特殊なテーマやプラグインでも入れない限り、WordPressでも使用しないでしょう。
※もちろん、あなたが作っているWebシステムでSharedArrayBufferなどを使用する場合は別です
WordPressではCORP/COEP/COOPも設定しなくていいです。
あえてWordPressにCORP/COEP/COOPを設定してみる
外部サイトからWordPress内のリソースにアクセスする場合は、CORSで外部サイトを許可しなければいけないかもしれません。
そのような状況はWordPressにおいては通常ないので、このサイトでは設定していません。
しかし、CORP/COEP/COOPに関しては、設定する意味がないというわけではありません。
「JavaScriptにおいてSpectreの影響を受ける機能」を使っていないのならば設定する意味が全くないかと言われるとそうでもなく、これらのヘッダーはWebページ内のデータの取り扱いに厳しいルールを設けられるので、設定すればワンランク上のセキュリティを確保できます。
私のWordPressは、あえてCORP/COEP/COOPを設定して運用しています。
個人的な興味のためです。
しかし、得られるメリットに対してデメリットが大きいです。
これらのヘッダーは理解も運用も難しく、不具合を引き起こすことがあります。
CORP/COEP/COOPはその新しさと難解さゆえに資料がとても少ないです。
試しに設定してみたいと思っている方やWebアプリケーションを作っている方の参考資料になればいいと思って文字を起こしましたので、よく分からない場合は設定しないでください。
Cross-Origin-Resource-Policy (CORP)
https://developer.mozilla.org/ja/docs/Web/HTTP/Cross-Origin_Resource_Policy_(CORP)
2019年頃から使えるようになったヘッダーです。以下のいずれかを設定できます。
- cross-origin (デフォルト値)
いかなるオリジンからのリクエストでもリソースを読み込めます。 - same-site
同じサイトからのリクエストのみリソースを読み込めます。 - same-origin
同じオリジンからのリクエストのみリソースを読み込めます。
cross-originが最も緩く、same-originが最も厳しいです。
cross-originでは別オリジンからもリソースを参照できますが、same-originにするとそういったリソースのアクセスを拒否できます。
自分のサイトの画像のURLを勝手に使われることを禁止したい場合にも使用できます。
ただ、別オリジンからimgタグなどで参照できなくなるだけで、画像のURLを直接叩いて閲覧することは可能です。
WordPressでは?
same-origin推奨です。
通常の用途であれば、リソースの参照はWordPressを設置しているオリジン内で完結するからです。
ただし、ラッコツールズのOGP画像確認ツールのような外部サービスは使えなくなります。
このサービスはサイトへの直リンクをページに埋め込んでいるためです。
外部サービス周りの連携が取れなくなる恐れはあるので、心配な方は素直に設定しないかcross-originにしてください。
なお、LINEやTwitterやFacebookなどSNSのOGP画像には影響を及ぼしません。
OGP画像が表示されなくなる心配は不要です。
なぜかというと、これらはOGP画像を各社のCDNに保存するプログラムがサービス側で動作しており、実際のユーザーのブラウザにはCDNを通して配信されるからです。
ユーザーがTwitterを開いてOGP画像にアクセスする時には、Twitterのサーバーに画像が保存されており、それを参照するので問題なく閲覧できるというからくりです。
ちなみにOGP画像というのは、LINEやTwitterにURLを張り付けた時に表示される画像のことです。
この画像はサイトの運営者側が用意しています。
Cross-Origin-Embedder-Policy (COEP)
https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy
2020年から使えるようになったヘッダーです。
ページ内の埋め込みリソースに対して読み込み制限を付けられます。
以下のいずれかを設定できます。
- unsafe-none (デフォルト値)
CORSやCORPがなくても、異なるオリジンのリソースを取得することを許可します。 - credentialless
CORSやCORPがなかった場合、異なるオリジンのリソースを取得することを許可しますが、クレデンシャルを送信しません。CORSかCORPがあった場合はrequire-corpと同様です。 - require-corp
同一オリジンからのリソースか、異なるオリジンからロード可能であるとCORSかCORPで明示的に指定したリソースのみをロードすることができます。
unsafe-noneが最も緩く、require-corpが最も厳しいです。
require-corpの効力が何なのかというと、取得するリソースのHTTPヘッダーに対して以下を確認します。
- CORPもしくはCORSが付いているか?
- 付いていた場合、読み込みが許可されているか?
上記をどちらも満たした場合だけリソースが読み込まれます。満たさなければ読み込みが失敗します。
ちなみにcredentiallessのクレデンシャルとは、Cookie・クライアント証明書・Authorizationヘッダーによる認証情報のことです。
埋め込みリソースがそれらに依存しないことが分かっていればcredentiallessでいいでしょう。
WordPressでは?
unsafe-none推奨です。(つまり、WordPressでは付与すべきでないということです)
credentiallessやrequire-corpは大半の埋め込みコンテンツを表示できなくなるからです。
例えば、require-corpを指定するとTwitterとGoogle Mapが表示できなくなります。
credentiallessを指定するとTwitterは問題なく表示できますが、Google Mapは表示できません。
※2022年時点で確認。サービス側の対応が進めば改善されるかもしれません
また、Google Adsenseにも影響を及ぼす可能性もあります。
Adsense広告はCookieに依存しているため、Cookieを送信できなくなると不具合を起こすと思われます。
このような場合はunsafe-noneで運用しなければなりません。
Cross-Origin-Opener-Policy (COOP)
https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy
COEPと同時期に使えるようになったヘッダーです。以下のいずれかを設定できます。
- unsafe-none (デフォルト値)
異なるオリジンからでも、window.openerを介して元のサイトにアクセスできます。 - same-origin-allow-popups
same-originと同様に同一オリジン内に限定されますが、ポップアップウィンドウの場合のみwindow.openerを介して元のサイトのデータにアクセスできます。 - same-origin
同一オリジンの場合のみ、window.openerを介して元のサイトにアクセスできます。
unsafe-noneが最も緩く、same-originが最も厳しいです。
same-origin-allow-popupsですが、世の中にはポップアップウィンドウに依存した既存の処理(OAuth、支払いなど)が多いため、緩和策としてこの値が用意された経緯があります。
WordPressでは?
same-origin-allow-popups推奨です。
ポップアップウィンドウに依存しているプラグインでも問題なく動作させるようにするためです。(Googleなどの外部アカウントでログインする機能や決済機能があるプラグインが該当するでしょう)
余談ですが、COOPを付けなかった場合でも、記事内のリンクに関しては対策済みです。
ブロックエディタで記事内のリンクを作り、新しいタブで開く設定にした場合、WordPressは自動的にnoopenerを付けるので、記事内のリンク先からリンク元を参照することができません。
WordPressでCOOPを使う意味があるとすれば、プラグインなどが生成したリンクやスクリプトからwindow.open()でタブ/ウィンドウを開いた場合のデータ漏えい防止ということになるでしょう。