知っていたらちょっとだけどや顔できるレスポンスヘッダ3選

こんにちは、FREEMINDの開発部です。
先日、弊社で開発・運用しているシステムのWebAPIに対して脆弱性診断をする際に、AIを使用した脆弱性診断サービスを初めて使用する機会がありました。
脆弱性診断を実施した結果はほぼ問題なし——
ただ、脆弱性としては最低レベルの情報として、いくつか指摘をもらいました。
その多くがレスポンスヘッダの不備でした。
今回指摘されたレスポンスヘッダの中には見慣れたものもあれば、「え、なにそれ。そんなん知らん…」というレスポンスヘッダもちらほら。
脆弱性診断の結果として指摘されたからには、お客様への説明が必要です。
このため、今まで知らなかったレスポンスヘッダは何者かを調べることとなりました。
今回は調べた中から特に「こんな設定があったんだ」と感じた3つをご紹介します。
![]()
レスポンスヘッダとは?
Webの世界ではクライアント(ブラウザ)とサーバーが対話し、お互いに情報をやり取りすることでホームページを表示します。
この際、サーバーからクライアント(ブラウザ)にデータを返すときに「これから渡すデータをこういうルールで扱ってね」と伝えるものがレスポンスヘッダと呼ばれるものです。
身近な例として2つ:
Content-Type:渡すデータの種類(HTML、JPEG、JSONなど)をブラウザに伝えるヘッダです。指定を誤るとブラウザが想定外の動きをすることがあります。Cache-Control:受け取ったデータをブラウザに保存・再利用させるか制御するヘッダです。動的なページではあえてキャッシュしない設定にすることも多く、システムの特性によって適切な設定に変更する必要があります。
規格で定義されているものだけでも相当な種類があり、日々のWebの世界の進歩によってどんどん追加されたり変更されていきます。
知らないヘッダがあるのも無理はありません。
① Referrer-Policy ─ リンク元URLの漏洩を防ぐ
リファラとは
ホームページ上のリンクをクリックしたとき、ブラウザはリンク先のサーバーに「このページから来ました」という情報を送ります。
この情報というのがリファラ(referrer)です。
Referrer-Policy は、リファラとしてどのような情報を送るかを制御するためのヘッダです。
なぜ設定が必要か
昔々、このヘッダーが無かったころ、URLにセッションIDが含まれるページから外部サーバーへのリンクを踏むと……
<https://from.com/users?sessionid=asdfrewq> ← to.co.jp へのリンクがある
to.co.jp のサーバーはrefererの値として、このURLを丸ごと受け取ることになります。
この時、URL末尾の?以降のクエリパラメータと呼ばれる値も含まれることがあるため、上記例でいうto.co.jpサーバーの所有者にセッションIDがばれてしまいます。
セッションIDがばれてしまうとセッションハイジャックという攻撃が可能となり、情報漏洩やアカウントの乗っ取りにつながる恐れがあります。(こわいですね)
それだけでなく、REST APIのURL内にセンシティブなIDが含まれている場合、それが攻撃の足掛かりとなって情報漏洩につながるかもしれません。
設定値一覧
設定可能な値として定義されているのが下記の設定値で、いろんなバリエーションの動きを指定できます。(より詳細な情報はMDNのReferrer-Policy ヘッダーのページを確認してください)
| 設定値 | 同一オリジン | 別オリジン | HTTPS→HTTP |
|---|---|---|---|
no-referrer |
なし | なし | なし |
no-referrer-when-downgrade |
全URL | 全URL | なし |
origin |
オリジン | オリジン | オリジン |
origin-when-cross-origin |
全URL | オリジン | オリジン |
same-origin |
全URL | なし | なし |
strict-origin |
オリジン | オリジン | なし |
strict-origin-when-cross-origin |
全URL | オリジン | なし |
unsafe-url |
全URL | 全URL | 全URL |
※ オリジン=スキーム+ドメイン+ポート(例:https://from.com/)
💡 最近のブラウザはreferrer-policyに何も指定しなくてもデフォルトで
strict-origin-when-cross-originに相当する動作になります。
とはいえ、安全のため明示的に設定しておくほうが良いでしょう。
② COEP(Cross-Origin-Embedder-Policy)─ 埋め込みコンテンツを制限する
COEPは、ページ内に埋め込むコンテンツを制限するヘッダです。
| 設定値 | 意味 |
|---|---|
unsafe-none |
なんでも埋め込んでOK(デフォルト) |
require-corp |
相手方が「埋め込んでいい」と許可しているものだけOK |
credentialless |
Cookie等を付けずに取得しに行く(取得できたものだけ埋め込める) |
⚠️ このヘッダ単体ではあまりメリットがありません(意図しない外部のリソースの埋め込みを制限するだけ)。
COOPとセットで使うことで真価を発揮します——その理由は後ほど。
③ COOP(Cross-Origin-Opener-Policy)─ ウィンドウの親子関係を制御する
window.open() や新しいタブでページを開いた/開かれた際に、相手と自サイトの「親子関係」をどう扱うかを制御するヘッダです。
親子関係を維持すると、同一BCG(ブラウジングコンテキストグループ)で管理され、スクリプトによる操作が可能となります。
設定にはunsafe-none / same-origin / same-origin-allow-popups / noopener-allow-popupsの4つが指定できます。
これらの設定によって親子関係がどうなるかは「開く側と開いた側の設定の組み合わせ」と「開く方法(window.open() or 新しいタブで開く)」で変わります。
多くの場合は切断されますが、以下のパターンの場合は維持されます。(これらの組み合わせ以外は親子関係が切断され、スクリプトによる操作はできません。)
| 開き方 | 開く側の設定 | 開かれる側の設定 | 結果 |
|---|---|---|---|
| リンクで新しいタブを開く | unsafe-none | unsafe-none | ○ |
| リンクで新しいタブを開く | same-origin-allow-popups | same-origin-allow-popups | △ |
| リンクで新しいタブを開く | same-origin | same-origin | △ |
| リンクで新しいタブを開く | noopener-allow-popups | noopener-allow-popups | △ |
| Window.open() | unsafe-none | unsafe-none | ○ |
| Window.open() | same-origin-allow-popups | unsafe-none | ○ |
| Window.open() | same-origin-allow-popups | same-origin-allow-popups | △ |
| Window.open() | same-origin | same-origin | △ |
| Window.open() | noopener-allow-popups | unsafe-none | ○ |
○:同一BCGで開かれ、親子関係を維持する
△:互いにオリジンが同じの時にのみ同一BCGで開かれ、親子関係を維持する
「リンクで新しいタブを開く」の場合、親子関係を維持するには基本的に、開く側・開かれる側で同じ設定が必要なようです。
「Window.open()」の場合は組み合わせがとても複雑です。
より詳細な組み合わせごとの結果はMDNのCOOPのページでご確認ください。
![]()
COEP・COOPが必要な本当の理由 ─ Spectre攻撃との関係
この2つのヘッダーにより「埋め込みを制限して何が嬉しいの?」この疑問の答えが、Spectre(スペクター)攻撃にあります。
Spectre攻撃とは
Spectreは2018年ごろから問題化したCPUの脆弱性をついた攻撃です。
CPUはデータを処理するには、データをメモリからCPUのキャッシュメモリに一度読み込む必要があるのですが、メモリからデータを読み込む速度は、CPUの処理速度に比べてとても遅く、これを待っていると処理全体が遅くなってしまいます。
そこで、現代のCPUはこの待ち時間によって処理が遅くなる問題の対策として**投機的実行(先読み)**を行います。
if文などの分岐があった場合、if文の処理の完了の前に、true/falseどちらになるかを事前に予想し、その予想結果にともなってキャッシュメモリに読み込みます。
そして、if文の処理の結果使わなかった場合はキャッシュ上のデータはキャッシュメモリ上に放置して処理を実施しなおします。
このCPUの投機的実行は数十年前から使われ始め、現代のCPUではスタンダードな処理になっています。
この一度キャッシュメモリに保存されてしまったデータに目を付けたのがSpectre攻撃です。
CPUキャッシュから読み出すのは通常メモリより格段に速いというのは先にご説明した通りです。
工夫した方法でCPUに情報を1つずつ読み込ませながら、処理の応答時間の差を測定することで、if文などの分岐の先で使われずに放置していた情報を推測できてしまいます。
一般的にサイドチャネル攻撃のタイミング攻撃という攻撃手法です。
このif文がもし、認証や認可による分岐処理で、その直後にセッションクッキーを参照するような処理だったら・・・。
CPUのキャッシュメモリ上には投機的実行によってセッションクッキーの情報が読み込まれてしまっている可能性があり、Spectre攻撃で情報を推測することで本来はアクセスできないセッションクッキーの内容を盗み見ることができてしまいます。(こわいですね)
⚠️ 例えばログイン済みの銀行Aのサイトの認証済みページ(口座情報などが含まれるコンテンツ)を、悪意あるページがiframeや画像として自分のページに埋め込んだ場合を考えましょう。
通常はドメインが違うためスクリプトからその中身を読み取ることはできません。しかしSpectre攻撃は、同じプロセスのメモリに読み込まれたデータをドメインの壁を無視して推測できるため、本来アクセスできないはずの認証済みコンテンツの内容が盗み見られてしまう可能性があります
もし、セッションクッキーの値を推測されてしまうと、不正送金などの被害につながってしまいます。
ブラウザはどう対処したか
数十年間使われてきた投機的実行(先読み)に対する脆弱性であり、脆弱性のあるCPUをすべて拒否するなどをすれば、数多くのPCでブラウザが使えなくなっちゃいます。
ブラウザを起動しているプロセスは投機的実行(先読み)を禁止するとすればページ表示までの処理が遅くなり利用者離れにつながってしまいます。
それは利用者を一人でも増やしたいブラウザとしてもうれしくはないでしょう。
そこでブラウザは、Spectre攻撃の「処理の応答速度の差を見る」という点に注目しました。
「処理の応答速度の差を見る」ためにはナノ秒単位を図れるくらいの高精度なタイマーが必要です。
そこで、ブラウザは今までは標準で使えていた高精度なタイマーの精度を意図的に落とす(粗くする)ことで「処理の応答速度の差を見る」ことをできないようにすることで対策としました。
この時、高精度なタイマーと同時に使えなくなったのが「SharedArrayBuffer」というスレッド間共有メモリ機能です。
一見タイマーとは関係なさそうなスレッド間のメモリ機能である「SharedArrayBuffer」がなぜ使えなくなったのか。
原因は、メインスレッドとWeb Worker間でメモリを共有できるSharedArrayBuffer自体の機能にあります。
Spectre攻撃をしようとしているメインスレッドとは別にもう一つWeb Worker(別スレッド)を用意し、そこでループを回して「SharedArrayBuffer」の値を全力でひたすらインクリメントします。
インクリメント処理に使用するのはCPUのキャッシュメモリになるので、1回の読み書きの処理速度はメモリの読み込み速度より圧倒的に早くなります。
このインクリメントされる値をSpectre攻撃をしようとしているメインスレッドが覗き、メモリアクセス前後の値の変化量を読み取ることでSpectre攻撃に使用できる疑似高精度タイマーを作ることができてしまったため、同時に封印されることとなりました。
これらSpectre攻撃に必要な道具を封印することで、ブラウザは攻撃を大幅に困難にしました。
でも困った人たちがいた
高精度タイマーや共有メモリを必要とするのが、ゲームサイトなど処理速度を求めるサービスやスレッド間でメモリ共有処理をしたい複雑なサービスです。
Spectre攻撃対策による高精度タイマーと共有メモリ機能の封印によってパフォーマンスやうまく動かない等、影響が出てしまいました。
そこでまたブラウザは考えました。
「別オリジンのコンテンツは相手が許可したもの以外の埋め込みを拒否してもよい、別オリジンを新しいウィンドウで開いたときは親子関係も切ってくれてよい」——と宣言してくれるなら、高性能な機能を解放しよう。
これが Cross-Origin Isolation です。
前半の「別オリジンのコンテンツは相手が許可したもの以外の埋め込みを拒否してもよい」の宣言を行うのがCOEP。
後半の「別オリジンを新しいウィンドウで開いたときは親子関係も切ってくれてよい」の宣言を行うのがCOOPの役割です。
この2つのヘッダーの値を(COEP: require-corp or COEP:credentialless) + COOP: same-origin の組み合わせで宣言することで、Cross-Origin Isolation状態となり、ブラウザはそのページに対して高精度タイマーやSharedArrayBufferの封印を開放して、再び使えるようにしてくれます。
まとめ ─ 3つのヘッダと設定の優先度
| ヘッダ | 役割 | 設定は必要? |
|---|---|---|
Referrer-Policy |
リンク遷移で送るURL情報を制限 | 明示的な設定を推奨 |
COEP |
埋め込みクロスオリジンコンテンツを制限 | 高性能機能が必要なサイトのみ |
COOP |
open先、open元との親子関係を制御 | 明示的な設定を推奨 |
一定のセキュリティ効果は見込めるものの、センシティブな情報を扱わないような一般的なウェブアプリでは、COEPはデフォルト値のままでも大きな問題はありません。
COOPは今回説明したSpectre攻撃とは別のXS-Leaks攻撃に対する防御としても有効であるため、特に理由が無ければ「same-origin」は設定しておいたほうが良いでしょう。
Referrer-Policy はデフォルト設定に任せるのではなく明示的に設定しておくことが推奨されています。
![]()
最後に
今回脆弱性診断の結果を見て「知っていたら、ちょっとだけどや顔できる3選」というテーマで社内共有会を開いたのですが、発表者の私自身、3つとも初めて知りました。
必死に調べて何とか内容を理解したつもりではいますが、もし誤りがあるなどご指摘があれば教えていただけると嬉しいです。
脆弱性診断は「OK/NG」の結果を出すだけでなく、こうした学びの機会にもなります。FREEMINDでは、業務の中で得た知見をこうして発信し続けていきます。
そうそう、Spectre攻撃には兄弟分の Meltdown 攻撃というものもあります。
こちらももし興味があれば調べてみてください。
執筆者:南(プロフィール)
Search
よく読まれている記事
カテゴリ
アーカイブ
関連記事
あなたに最適な解決策を一緒に考えます。
状況に応じた最適なご提案で、お客様の課題解決をサポートいたします