セキュリティヘッダー

HTTP レスポンスヘッダーでブラウザのセキュリティ機能を制御し、XSS やクリックジャッキングを防ぐ仕組み

セキュリティHTTP

セキュリティヘッダーとは

セキュリティヘッダーは、HTTP レスポンスに付与するヘッダーで、ブラウザのセキュリティ機能を有効化し、XSS、クリックジャッキング、MIME スニッフィングなどの攻撃を防ぐ。

主要なヘッダー

どれも単体で攻撃を止めるものではなく、ブラウザ側の防御を上乗せする層である。値を練る必要があるのは Content-Security-Policy だけで、残りは事実上決まった値を付けるだけで済む。付け忘れが起きるのはトップページではなくエラーページやリダイレクト応答なので、画面ごとに書くよりも配信層でまとめて付けるほうが漏れにくい。

ヘッダー防ぐ攻撃推奨値
Content-Security-PolicyXSS、データインジェクションdefault-src 'self'
Strict-Transport-Securityダウングレード攻撃max-age=31536000; includeSubDomains
X-Content-Type-OptionsMIME スニッフィングnosniff
X-Frame-OptionsクリックジャッキングDENY
Referrer-Policyリファラー情報の漏洩strict-origin-when-cross-origin
Permissions-Policyブラウザ機能の制限camera=(), microphone=()

表の X-Frame-Options は、CSPframe-ancestors に置き換えられる位置にあるヘッダーである。埋め込みを許す相手を細かく指定したいなら frame-ancestors を使う。X-Frame-Options 側で許可元を列挙する ALLOW-FROM は廃止済みで、これを書くとヘッダーそのものが丸ごと無視されるため、実質的な選択肢は DENYSAMEORIGIN の 2 つしかない。

Content-Security-Policy (CSP)

ページが読み込めるリソースの出所を制限する。XSS 攻撃で注入されたスクリプトの実行を防ぐ。

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;
ディレクティブ制御対象
default-src全リソースのデフォルト
script-srcJavaScript
style-srcCSS
img-src画像
connect-srcfetch, XHR, WebSocket
frame-ancestorsiframe での埋め込み元

Strict-Transport-Security (HSTS)

ブラウザに「このドメインには常に HTTPS でアクセスせよ」と記憶させる。以降は http:// のリンクを踏んでも、平文のリクエストを出す前にブラウザ内部で HTTPS へ切り替わるため、リダイレクトを待つ間の平文通信を奪われる経路がふさがる。

弱点は、ヘッダーを一度受け取るまで効かないことである。そのドメインが HSTS 対象だとブラウザが知らない状態で http:// にアクセスした最初のリクエストは保護されない。ここを埋めるのが preload である。

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

ただし preload を付けただけではプリロードリストに入らない。リストは Google が運用する登録サービスへ自分でドメインを申請する形で管理されていて、申請の条件として max-age が 1 年 (31536000 秒) 以上であること、includeSubDomains が付いていること、サブドメインまで含めて HTTPS で配信されていることが要求される。登録されるとブラウザ本体に同梱される形になるため、外したいときに即座には外れない。HTTPS へ移行できていないサブドメインが 1 つでも残っているなら preload は後回しにする。

また、このヘッダーは HTTPS の応答にだけ付ける。平文の HTTP で送られた Strict-Transport-Security は、経路上で書き換えられた可能性を排除できないためブラウザに無視される。HTTP から HTTPS へのリダイレクト応答に付けても効果はない。

CloudFront での設定

CloudFront のレスポンスヘッダーポリシーを使うと、オリジンのアプリを触らずに配信層でヘッダーを付けられる。静的サイトでも同じ構成が取れる。

用意されているマネージドポリシー (SecurityHeadersPolicy) が付けるのは Referrer-PolicyStrict-Transport-SecurityX-Content-Type-OptionsX-Frame-OptionsX-XSS-Protection の 5 つで、上の表と見比べると Content-Security-PolicyPermissions-Policy が入っていない。HSTS も max-age=31536000 だけで includeSubDomains は付かない (2026 年 8 月時点)。マネージドポリシーは出発点であり、CSP まで効かせたいならカスタムポリシーを作ることになる。

もう 1 つの注意は上書きの扱いである。X-Content-Type-Options 以外は、オリジンが同じヘッダーを返していればオリジン側の値がそのまま使われる。アプリと配信層の両方で設定していると、ポリシーに書いた値で配信されているとは限らない。

マネージドポリシーに X-XSS-Protection が含まれている点も、そのまま受け取らないほうがよい。これはかつて Internet Explorer・Chrome・Safari が持っていた反射型 XSS の検出機能で、標準化されておらず、MDN では非推奨として扱われている (条件次第で逆に脆弱性を作り得るとも注意されている)。インラインスクリプトを禁じる CSP を書くほうが本筋である。

確認方法

「付けたつもりで付いていない」が最も多い失敗なので、実際の応答で確かめる。しかもトップページだけでは足りない。静的ファイル、API の応答、404 ページのように配信経路が違う応答は、それぞれ別に確認する。

# 経路ごとに応答ヘッダーを確認する
curl -I https://example.com/
curl -I https://example.com/assets/app.js
curl -I https://example.com/does-not-exist

CSP は既存のページを壊しやすいので、最初から本適用にしない。同じ値を Content-Security-Policy-Report-Only で配信すると、ブロックはせずに違反だけが報告されるので、報告が止まってから本適用のヘッダーへ切り替えられる。報告を送るには report-to ディレクティブと、報告先を定義する Reporting-Endpoints ヘッダーが必要で、これらが無いと Report-Only を付けても何も集まらない。

よくある失敗

  • CSP が厳しすぎてインラインスクリプトが動かない → noncehash で許可
  • HSTS を設定後に HTTP に戻せない → max-age を短くしてテスト
  • X-Frame-Options: DENY で自サイトの iframe 埋め込みも拒否 → frame-ancestors 'self' を使う

実践的な知識は関連書籍でも得られる。

この記事は役に立ちましたか?

関連用語

関連する記事