クリックジャッキングとフレーミング防御(frame-ancestors)
見えないiframeで本物のボタンを踏ませる罠を、なぜ重ね合わせが成立するのかから解明。X-Frame-Options と CSP frame-ancestors の効きどころを見極め、埋め込みを正しく拒否する設計が手に入ります。
- 1.クリックジャッキングは攻撃者ページに被害サイトを透明 iframe で重ね、本物のボタンへのクリックを「視覚的には別物」に偽装して誘導する UI 偽装攻撃。
- 2.防御の主軸は「自分のページを誰に iframe 埋め込みさせるか」をブラウザに宣言すること。CSP の frame-ancestors が現行標準で、古い X-Frame-Options を置き換える。
- 3.frame-ancestors は許可リストで複数オリジンや自己埋め込みを表現でき、メタタグでは無効。レスポンスヘッダで配信し、SameSite Cookie や JS フレームバスターと多層で組む。
クリックジャッキング(clickjacking、UI redress attack)は「被害者が押したつもりのボタンと、実際に押させられたボタンが違う」攻撃です。攻撃者は自分のページに被害サイトを iframe で埋め込み、それを透明にして無関係なコンテンツの上に重ねます。被害者は表面のおとりを操作しているつもりで、裏に隠れた本物の「送金」「権限付与」ボタンをクリックさせられます。CSRF と違いリクエストを偽造するのではなく、被害者自身の本物のクリックを盗む点が本質です。
透明 iframe 重ね合わせの原理
攻撃の土台は「外部サイトを iframe で埋め込める」という Web 標準の機能と、CSS で要素を不可視化・重ね合わせできることの組み合わせです。攻撃者ページは被害サイトを iframe に読み込み、opacity: 0 で透明にしたうえで、position と z-index で前面に置きます。その真下に、被害者を誘導するおとり(「無料プレゼントを受け取る」等)を配置します。
<style>
iframe {
position: absolute; top: 0; left: 0;
width: 500px; height: 500px;
opacity: 0; /* 透明にして見えなくする */
z-index: 2; /* おとりより前面に重ねる */
}
#decoy { position: absolute; top: 240px; left: 60px; z-index: 1; }
</style>
<div id="decoy">無料プレゼントはこちら</div>
<!-- 被害サイト。透明だが、クリックは iframe 側に届く -->
<iframe src="https://bank.example/transfer-confirm"></iframe>
ここで効いている仕組みは、ヒットテスト(hit testing)です。ブラウザはクリック座標に対し、最前面にある要素を当たり判定の対象に選びます。opacity: 0 は要素を不可視にするだけで、レイアウト上は存在し続けポインタイベントも受け取ります。つまり被害者の目には「無料プレゼント」ボタンが見えていても、実際のクリックは最前面の透明 iframe 内の本物のボタンへ届きます。攻撃者は iframe の表示位置を CSS で微調整し、おとりの真上に本物のボタンが来るよう座標を合わせ込みます。
攻撃者は iframe の中身を読むことはできません。クロスオリジンの iframe は同一オリジンポリシーで隔離され、DOM もスクロール位置も覗けないからです。しかしクリックジャッキングは中身を読む必要がありません。被害者の指(クリック)が iframe に届きさえすればよい。だから「読ませない防御」である SOP では止まらず、「埋め込ませない防御」が別途必要になります。この切り分けは CSRFの成立条件と多層防御の原理 で扱う「読む防御と送る防御は別物」と同じ構図です。
クリック以外に、ドラッグ&ドロップでフォームに値を流し込ませる、キー入力を奪う(keyjacking)、pointer-events を切り替えてクリックの一部だけを透過させる、といった派生があります。いずれも「被害者の正規な操作を、別の標的へ向け直す」点で共通します。
防御の主軸 — 埋め込みをブラウザに拒否させる
クリックジャッキングは攻撃者ページが被害サイトを iframe に入れられることが前提です。したがって本質的な防御は、被害サイト側が「自分のページを iframe に埋め込んでよい相手」をブラウザへ宣言し、許可外の埋め込みをブラウザにレンダリング段階で拒否させることです。これを担うのが、古い X-Frame-Options ヘッダと、現行標準である CSP の frame-ancestors ディレクティブです。
Content-Security-Policy: frame-ancestors 'self' https://partner.example
このヘッダ付きのレスポンスは、自分自身('self')と partner.example 以外のページから iframe 埋め込みされた場合、ブラウザがフレームの描画自体を拒否します。攻撃者の evil.example からの埋め込みは許可リストに無いので空フレームになり、透明にしようがクリックは届きません。
X-Frame-Options と frame-ancestors の違い
両者は目的が同じですが、表現力と仕様上の位置づけが異なります。X-Frame-Options は事実上 3 系統の値しか持たず、複数の許可オリジンを表現できません。frame-ancestors はソースリストで任意個のオリジンを並べられ、CSP の一部として正式に標準化されています。
| 観点 | X-Frame-Options | CSP frame-ancestors |
|---|---|---|
| 標準の位置づけ | 事実上の慣習。新規仕様では非推奨 | CSP Level 2 以降の正式ディレクティブ |
| 取りうる値 | DENY / SAMEORIGIN /(旧)ALLOW-FROM | 'self' / 'none' / 任意個のオリジン許可リスト |
| 複数オリジン許可 | 不可(ALLOW-FROM も多くで非サポート) | 可。スキームやワイルドカードも記述可 |
| メタタグ指定 | 不可(ヘッダのみ) | 不可(frame-ancestors はヘッダ必須) |
| 優先順位 | 両方ある場合 frame-ancestors が優先 | CSP があれば XFO は無視されるべき |
値の対応関係は、X-Frame-Options: DENY が frame-ancestors 'none'、SAMEORIGIN が frame-ancestors 'self' に相当します。問題は ALLOW-FROM です。これは「この 1 オリジンだけ許可」を意図した値ですが、多くのブラウザが実装せず無視するか、指定すると逆に全拒否になる挙動差がありました。複数オリジンも書けません。つまり「特定パートナーだけ埋め込み許可」という現実的な要件は X-Frame-Options では満たせず、frame-ancestors でなければ正しく表現できません。
CSP のディレクティブには <meta http-equiv="Content-Security-Policy"> で指定できるものとできないものがあります。frame-ancestors・report-uri・sandbox はメタタグでは無効で、HTTP レスポンスヘッダでのみ機能します。理由は、これらが「ページがどう埋め込まれるか」というドキュメント取得・フレーミングの段階で判定される必要があり、HTML を構文解析してメタタグを見つけるより前に効かなければ意味がないからです。frame-ancestors をメタタグに書いても黙って無視されるため、必ずレスポンスヘッダで配信します。CSP 全体の評価規則は Content Security Policyの内部動作と回避耐性 を参照してください。
frame-ancestors は CSP の他のディレクティブと違い、default-src にフォールバックしません。default-src 'none' を書いても埋め込み制御はかからないので、フレーミング防御を意図するなら frame-ancestors を明示する必要があります。また CSP ヘッダが複数ある場合は全ポリシーの積(AND)で評価されるため、どれか 1 つでも厳しい frame-ancestors があればそれが効きます。
許可リストの設計と落とし穴
frame-ancestors のソースは「祖先(埋め込み元)ドキュメントのオリジン」と照合されます。多段にネストした iframe では、直接の親だけでなく全祖先が許可リストに合致する必要があります。途中に許可外オリジンが 1 つでも挟まれば拒否されます。これは「許可した相手の中にさらに攻撃者が iframe を仕込む」入れ子攻撃を塞ぐためです。
| 記述 | 意味 | 注意点 |
|---|---|---|
| 'none' | 誰にも埋め込ませない | XFO の DENY 相当。最も安全な既定 |
| 'self' | 同一オリジンのみ埋め込み許可 | scheme/host/port 一致。サブドメインは含まない |
| https://a.example | そのオリジンを祖先に許可 | 全祖先が許可集合に入る必要がある |
| https://*.example | サブドメイン全体を許可 | サブドメイン乗っ取りで穴になりうる |
設計の原則は「埋め込みを許す積極的な理由が無ければ 'none'(または 'self')にする」ことです。許可リストを広げるほど、許可先のいずれかが乗っ取られたりサブドメインを奪われたりした時の攻撃面が増えます。とりわけワイルドカードでサブドメイン全体を許す指定は、サブドメイン乗っ取り(dangling DNS など)と組み合わさると防御が骨抜きになります。許可は最小限のオリジンに絞り込みます。
frame-ancestors も X-Frame-Options も付けないと、ブラウザはそのページを誰でも iframe 埋め込みできるものとして扱います。フレーミング防御は明示的なオプトインであり、無指定は「全許可」と同義です。とくに状態変更や認証後の操作を行うページ、決済確認画面、管理画面は、全レスポンスに frame-ancestors 'none' か 'self' を付与するのを既定方針にします。一部のページだけ守って終わりにせず、サイト全体のレスポンスヘッダで一律に敷くのが安全です。
ヘッダが効かない場面と多層防御
frame-ancestors はブラウザがヘッダを解釈して初めて効くため、ヘッダが攻撃者に制御される、あるいはレガシー環境でヘッダが届かない場合に備えた補強も要ります。古典的な保険が JavaScript のフレームバスター(frame busting)です。
// 自分がトップレベルでない(=フレームに入れられている)なら脱出
if (window.self !== window.top) {
window.top.location = window.self.location;
}
ただしフレームバスターは万能ではありません。攻撃者が iframe に sandbox 属性を付けると、allow-top-navigation を与えない限り子フレームからの window.top.location 書き換えがブロックされ、脱出スクリプトが無力化されます。逆に言えば、JS 単体に頼ると sandbox で容易に回避されるため、あくまで frame-ancestors を主、JS を従とする位置づけにします。
クリックジャッキングが成立しても、副作用のあるリクエストにクロスサイトの Cookie が付かなければ被害は抑えられます。認証 Cookie に SameSite=Lax/Strict を付けておくと、別オリジンに埋め込まれたフレーム内からの操作で Cookie 送信が制限され、被害の前提を一段崩せます。SameSite の挙動は CSRFの成立条件と多層防御の原理 と Cookie とセッション を、なぜクロスオリジン iframe が隔離されるのかは 同一オリジンポリシーとサイト分離の信頼境界 を参照してください。
ヘッダ防御に加え、設計レベルの緩和も効きます。送金や権限付与のような不可逆な操作には、ワンクリックで完了させず「金額と宛先を読み上げての再入力」「直前に発行したトークンの確認」のような明示的ステップを挟むと、座標を合わせ込んだ単発クリックだけでは完遂できなくなります。UI 偽装攻撃は「被害者が文脈を誤認したまま 1 アクションする」ことに依存するため、文脈を確認させる工程そのものが防御になります。
まとめると、クリックジャッキング対策の本筋は「全レスポンスに frame-ancestors を明示し、埋め込みをブラウザに拒否させる」こと。'none' か 'self' を既定にし、どうしても外部埋め込みが必要なページだけ最小のオリジンを許可します。その上で SameSite Cookie・JS フレームバスター・重要操作の再確認を重ね、ヘッダが届かない経路や入れ子攻撃でも崩れない多層構成にするのが、堅い設計です。
Web/フロントエンド Article
クリックジャッキングとフレーミング防御(frame-ancestors)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
セキュリティ
比較で見る軸
難易度: advanced / カテゴリ: Web/フロントエンド / タグ数: 5
導入後に効く点
防御の主軸は「自分のページを誰に iframe 埋め込みさせるか」をブラウザに宣言すること。CSP の frame-ancestors が現行標準で、古い X-Frame-Options を置き換える。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- Web/フロントエンド
- タグ数
- 5
判断チェックリスト
- 自社の用途が「セキュリティ / Web」に近いか確認する。
- 強みである「クリックジャッキングは攻撃者ページに被害サイトを透明 iframe で重ね、本物のボタンへのクリックを「視覚的には別物」に偽装して誘導する UI 偽装攻撃。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。