CyberFix Note
セキュアコーディング

セキュリティHTTPレスポンスヘッダで多層防御を固める方法

対象の目安: Webアプリ開発者と運用担当 / 実務

アオイ防御・運用担当
・ 約14分で読めます
セキュリティHTTPレスポンスヘッダで多層防御を固める方法

Webアプリケーションは、レスポンスにいくつかのHTTPヘッダを添えることで、ブラウザ側の防御を追加で有効にできます。これがセキュリティHTTPレスポンスヘッダです。サーバーが返すヘッダを見て、ブラウザは「このページではインラインスクリプトを実行しない」「このサイトは常にHTTPSで開く」「他サイトのフレームに埋め込ませない」といった振る舞いへ切り替えます。アプリ本体のコードを守るのは開発側の実装ですが、ヘッダはその実装が一部破られたときに被害を抑える、もう一枚の層として働きます。

この記事では、Content-Security-Policy(CSP)を軸に、Strict-Transport-Security(HSTS)やX-Content-Type-Options、クリックジャッキング対策のヘッダ、Referrer-Policy、Permissions-Policyが、それぞれ何を防ぎ、どう設定するのかを整理します。前提として押さえておきたいのは、これらのヘッダは多層防御の一枚であって、根本対策の代わりにはならないという点です。たとえばXSSの根本対策は出力時の文脈別エスケープであり、CSPはそれが破られた場合に被害を狭める保険として重ねるものです。

主要なセキュリティヘッダの早見表

先に全体像をつかむため、主なヘッダと防ぐ対象、最小の設定例を一覧にします。詳細は後続の章で扱います。

ヘッダ主に防ぐもの最小の設定例
Content-Security-PolicyXSS、リソースの不正読み込みdefault-src 'self'
Strict-Transport-SecurityHTTPへのダウングレード、中間者による介入(初回接続は対象外)max-age=31536000; includeSubDomains
X-Content-Type-OptionsMIMEタイプの推測による誤実行nosniff
Content-Security-Policy(frame-ancestors)クリックジャッキングframe-ancestors 'self'
Referrer-Policyリファラ経由の情報漏れstrict-origin-when-cross-origin
Permissions-Policyカメラやマイク等の機能の濫用camera=(), microphone=()

これらは組み合わせて使うものです。1つを入れれば足りるという性質のものではなく、防ぐ対象が異なるヘッダを重ねて、はじめて面としての防御になります。

CSPで読み込み元を宣言して制限する

Content-Security-Policyは、そのページがどのオリジンからどの種類のリソースを読み込んでよいかを、サーバーがブラウザへ宣言するヘッダです。ブラウザは宣言に反する読み込みや実行を拒否します。仮に攻撃者がページへスクリプトを注入できても、そのスクリプトの読み込み元や実行方法がCSPで許可されていなければ、ブラウザ側で止まります。これがXSSに対する被害低減の仕組みです。

ポリシーはディレクティブの並びで書きます。基点になるのがdefault-srcで、種類別の指定がない場合の既定の読み込み元を決めます。スクリプトはscript-src、スタイルはstyle-src、画像はimg-srcというように、リソースの種類ごとに許可元を細かく分けられます。値には'self'(同一オリジン)や具体的なドメイン、'none'(すべて禁止)などを指定します。

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; object-src 'none'; frame-ancestors 'self'

CSPで避けたいのが、script-srcへ'unsafe-inline'を加えることです。これはインラインの<script>やイベントハンドラ属性の実行を許すため、注入されたスクリプトも一緒に通ってしまい、スクリプトXSS対策としてのCSPの効果を大きく損ないます。インラインスクリプトを安全に許可したい場合は、nonce(リクエストごとに生成する使い捨ての値)かhash(スクリプト内容のハッシュ)を使います。nonceを使うと、サーバーが発行した値を持つ<script nonce="...">だけが実行され、攻撃者が注入したインラインスクリプトはnonceを知らないため実行されません。

MDNは、CSPの主な用途をクロスサイトスクリプティングの緩和とし、信頼できる読み込み元だけを許可すること、インライン実行を許す'unsafe-inline'を避けてnonceやhashで個別に許可する方式を説明しています。(執筆時点)

導入時は、いきなり本番のCSPで通信をブロックすると正規のリソースまで止まりかねません。そこで段階導入に使うのがContent-Security-Policy-Report-Onlyヘッダです。これは違反を実際にはブロックせず、ブラウザのコンソールに違反を表示し、報告先を指定しておけば違反レポートを送るモードで、既存サイトへ影響を与えずに「本番適用したら何がブロックされるか」を観測できます。レポートを集める場合はReporting-Endpointsヘッダとreport-toディレクティブ(または互換目的のreport-uri)で送信先を指定します。表示された違反や集めたレポートを見て許可元を調整し、問題がなくなってから通常のContent-Security-Policyへ切り替える流れが安全です。

CSPはあくまで出力エスケープが破られたときの保険です。根本対策である文脈別エスケープと入力の扱いについては、次の記事とあわせて設計してください。

あわせて読みたい

XSS(クロスサイトスクリプティング)の仕組みと対策。反射型・格納型・DOM型を原理から整理

あわせて読みたい

入力バリデーションと出力エスケープの原則。入口で検証し、出口で文脈別にエスケープする

HSTSで常時HTTPS接続を強制する

Strict-Transport-Security(HSTS)は、このサイトへは以後HTTPSでのみ接続するように、とブラウザへ指示するヘッダです。仕様はRFC 6797で定義されています。一度このヘッダを受け取ったブラウザは、指定期間中はユーザーがhttp://で入力しても自動的にHTTPSへ切り替え、証明書エラーが出ても回避を許しません。これにより、一度このヘッダを受け取った以降のHTTPへのダウングレードや、それに伴う中間者による介入の余地を狭めます。ヘッダを一度も受け取っていない初回接続は保護の対象外で、プリロードリストへの登録がその隙をふさぐ手段になります。

値はmax-ageで有効期間を秒で指定します。includeSubDomainsを付けるとサブドメインにも同じ強制が及びます。preloadは、ブラウザにあらかじめ組み込まれるHSTSプリロードリストへの登録を意図する指定です。

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

注意したいのはincludeSubDomainsとpreloadの扱いです。includeSubDomainsを付けると、その時点でHTTPSに対応していないサブドメインまで一律にHTTPS強制の対象になり、接続できなくなる場合があります。preloadはさらに影響が大きく、プリロードリストへ載ると各ブラウザに設定が焼き込まれるため、あとから外すのに時間がかかります。まずは短めのmax-ageで挙動を確かめ、全サブドメインのHTTPS対応を確認してから期間を延ばし、preloadは最後に検討するのが無難です。証明書とHTTPS自体の基礎は関連記事で整理しています。

あわせて読みたい

HTTPSと電子証明書が守る範囲と、鍵マークが示さないこと

クリックジャッキングを防ぐframe-ancestors

クリックジャッキングは、攻撃者が用意したページに標的サイトを透明なフレームで重ね、利用者が気づかないうちに標的サイト上のボタンを押させる手法です。対策は、自サイトを他サイトのフレームへ埋め込ませないことです。

この用途で長く使われてきたのがX-Frame-Optionsヘッダです。DENYはあらゆるフレーム埋め込みを禁止し、SAMEORIGINは同一オリジンのページからの埋め込みだけを許します。ただしこのヘッダは歴史的な位置づけで、任意のオリジンを許可するALLOW-FROMは廃止されており、現在のブラウザはこの値を無視します。MDNは、より柔軟な指定ができるCSPのframe-ancestorsディレクティブの利用を勧めています。

frame-ancestorsは、どのオリジンが自ページをフレームに埋め込んでよいかをCSP側で指定するディレクティブです。'none'ですべて禁止、'self'で同一オリジンのみ許可、複数オリジンの列挙にも対応します。X-Frame-Optionsが単一の値しか取れないのに対し、frame-ancestorsは複数の許可元を書ける点が実務で扱いやすいところです。

Content-Security-Policy: frame-ancestors 'none'

MDNは、X-Frame-OptionsのALLOW-FROMが廃止済みで現在のブラウザに無視されること、許可元を柔軟に指定するにはCSPのframe-ancestorsディレクティブを使うことを示しています。DENYSAMEORIGINは引き続き有効です。(執筆時点)

古いブラウザへの配慮としてX-Frame-Optionsを併記する構成も見られますが、新規に設計するならframe-ancestorsを主にし、必要に応じてX-Frame-Optionsを補助として添える形が素直です。

MIME推測とリファラと機能を制御する

短い設定で確実な効果があるヘッダをまとめます。まずX-Content-Type-Optionsです。値はnosniffの一択で、ブラウザがレスポンスのContent-Typeを無視して中身からMIMEタイプを推測する挙動を止めます。推測が働くと、テキストとして返したファイルがスクリプトとして実行されるといった誤りが起き得るため、これを固定して防ぎます。

X-Content-Type-Options: nosniff

次にReferrer-Policyです。これは他ページへ遷移する際に送るRefererヘッダに、どこまでの情報を含めるかを制御します。既定値を明示しない場合でも、最近のブラウザはstrict-origin-when-cross-originを既定として動きます。この値は、同一オリジンへの遷移では完全なURLを送り、別オリジンへはオリジンのみを送り、HTTPSからHTTPへ下がる遷移では送らないという挙動で、URLのパスやクエリに含まれる情報が外部へ漏れるのを抑えます。

Referrer-Policy: strict-origin-when-cross-origin

最後にPermissions-Policyです。カメラやマイク、位置情報といったブラウザの機能を、自ページや埋め込みフレームがどこまで使えるかを制御します。使わない機能を明示的に無効化しておくと、スクリプトが注入された場合でもこれらの機能を勝手に呼び出させにくくなります。

Permissions-Policy: camera=(), microphone=(), geolocation=()

OWASP Secure Headers Projectは、主要なセキュリティ関連レスポンスヘッダの推奨値と、各ヘッダが対処する脅威を一覧で整理しており、設定の出発点として参照できます。(執筆時点)

ヘッダを導入して検証する手順

ヘッダの追加はWebサーバーやアプリケーションのレスポンスにヘッダを足すだけで始められますが、CSPは既存の読み込みを壊しやすいため、観測してから適用する順序を守ります。

セキュリティヘッダの導入手順

  1. 1

    現状を把握する

    ブラウザの開発者ツールのネットワークタブでレスポンスヘッダを確認し、既に付いているヘッダと不足しているヘッダを洗い出します。
  2. 2

    影響の小さいヘッダから入れる

    X-Content-Type-OptionsのnosniffやReferrer-Policyは副作用が小さく、先に導入して効果を確かめます。
  3. 3

    CSPはReport-Onlyで観測する

    Content-Security-Policy-Report-Onlyで違反レポートだけを集め、正規のリソースが誤ってブロック対象になっていないかを確認します。
  4. 4

    許可元を調整して本適用する

    レポートをもとに読み込み元を絞り込み、問題がなくなったらContent-Security-Policyへ切り替えます。
  5. 5

    HSTSは段階的に強める

    短いmax-ageで挙動を確認し、全サブドメインのHTTPS対応を確かめてからincludeSubDomainsや期間延長、preloadへ進みます。

検証には、ブラウザの開発者ツールでコンソールに出るCSP違反メッセージを読む方法と、外部のヘッダ検査サービスで設定の抜けを一覧する方法があります。適用後は主要な画面を一通り操作し、スタイルやスクリプト、画像が意図せずブロックされていないかを目視で確認してください。

まとめ

セキュリティHTTPレスポンスヘッダは、少ない実装量でブラウザ側の防御を積み増せる手段です。ただし根本対策の置き換えではなく、実装が破られたときに被害を狭める一枚として重ねるものだ、という位置づけを外さないことが大切です。

セキュリティヘッダ導入のチェックリスト

  • CSPをdefault-srcを基点に設定し、script-srcで'unsafe-inline'を避けてnonceかhashを使っているか
  • CSPはReport-Onlyで観測してから本適用へ切り替えたか
  • HSTSのmax-ageを設定し、includeSubDomainsとpreloadの影響を確認してから強めているか
  • クリックジャッキング対策をframe-ancestorsで指定し、必要ならX-Frame-Optionsを補助にしているか
  • X-Content-Type-Optionsにnosniffを設定しているか
  • Referrer-Policyを明示し、URLのパスやクエリの漏れを抑えているか
  • 使わない機能をPermissions-Policyで無効化しているか
  • CSPはXSSの根本対策である出力エスケープの代替ではなく、保険として重ねていると理解しているか

ヘッダによる多層防御は、出力エスケープやCSRF対策といったアプリ側の根本対策があってはじめて意味を持ちます。あわせて設計してください。

あわせて読みたい

CSRFとは何か。仕組みからトークン・SameSite Cookieによる対策まで実務目線で解説

出典・参考

この記事をシェア

関連する記事

防御・ハードニング

HTTPSと電子証明書が守る範囲と、鍵マークが示さないこと

HTTPSと電子証明書が通信の暗号化と接続先ドメインの確認をどう実現するかを整理し、認証局による信頼の仕組みとブラウザの検証手順を説明したうえで、鍵マークがサイトの正当性までは保証しないという誤解を、総務省やChromiumブログの一次情報をもとに入門者向けに解きほぐします。