クリックジャッキングとは?frame-ancestorsで防ぐ実装と検証手順を解説
クリックジャッキングは、透明にしたiframeで被害サイトを重ね、利用者本人の指で正規の操作を実行させる攻撃です。サーバには本物のセッションを持った本物のリクエストが届くため、CSRFトークンもReferer検証も素通りします。この記事では、攻撃が成立する条件、ALLOW-FROMが現行ブラウザで無視される事情、frame-ancestorsを主に据えた指定、metaタグ配信では効かない制約、curlでのヘッダー実測、埋め込み要件が残る画面の許可設計までを実装視点でまとめました。ヘッダーで塞げない範囲の線引きも示します。
まとめ|クリックジャッキング対策をframe-ancestorsへ寄せる実装の結論
結論から置きます。これから設定するなら、主役はCSPの frame-ancestors で、X-Frame-Options は旧環境向けの併記に回すのが2026年8月時点の形です。X-Frame-Options で「特定のパートナーサイトにだけ埋め込みを許す」と書くには ALLOW-FROM が要りますが、MDNの記載どおり現行ブラウザはこのディレクティブを持つヘッダーを完全に無視します。無視された結果は「制限なし」であって「拒否」ではありません。設定した気になったまま無防備になる、というのが最も多い失敗です。
入れる順序も決まっています。既定を frame-ancestors 'none' にしてサイト全体へ配り、埋め込みが要る画面だけ許可元を列挙して例外を切る。「必要な画面から個別に足す」進め方では、後から増えた画面が漏れます。例外方式は、抜けたときに壊れる側へ倒れる分だけ安全です。
見送ってよい場面もあります。認証もなく、押しても状態が変わらない静的な公開ページ単体には、守るべき操作がありません。ただし、そのために配信設定を画面ごとに分岐させる価値はありません。サイト全体に既定で入れ、例外だけ書く運用にしてください。
クリックジャッキングの攻撃手順と透過iframeが操作を横取りする成立条件
攻撃者がやることは、通信の改ざんではありません。見た目の錯覚を作るだけです。
罠ページが被害サイトを重ねる3層構造とopacity指定で視覚を隠す手口
罠ページの構造は3層で説明できます。最下層に「無料で診断する」といった偽のボタンを置き、その上に被害サイトを iframe で読み込み、CSSで透明度をゼロにする。あとは偽ボタンの位置と、被害サイト側の本物のボタンの座標が重なるよう、iframe の表示位置をずらして合わせます。
必要なのは opacity と z-index、それに座標指定だけ。サーバへの侵入も通信の傍受もいりません。クリックされたのは被害サイトの本物のボタンで、送られるのは本人のCookieが付いた正規のリクエストです。攻撃コードが被害サイト側に1行も残らない点が、検知を難しくしています。
CSRFトークンや入力値検証では止まらない攻撃成立の条件の違い
CSRFは、攻撃者がリクエストそのものを組み立てて本人になりすます手口です。クリックジャッキングは違います。リクエストを作るのは被害サイトの正規の画面で、押すのは本人の指。隠しトークンは正しい値が埋め込まれ、Refererも被害サイト自身を指し、入力値のバリデーションも通過します。
止められない理由は「偽装を見破る」という前提が外れているからです。偽装は起きていません。起きているのは、本人が何に同意したかの認識のすり替えです。トークン方式そのものの実装トラブルはInvalid CSRF tokenの意味と直し方を整理した記事にまとめてあるので、両者は別の層の対策として重ねる前提で読み分けてください。
likejackingやカーソルジャッキングなど派生手口と被害が出る操作の種類
同じ原理の派生がいくつかあります。SNSの「いいね」を踏ませるlikejacking、カーソルの表示位置を偽の画像でずらすカーソルジャッキング、モバイルで別アプリの表示を重ねるタップジャッキング。Androidには覆われている間のタッチを破棄する filterTouchesWhenObscured があり、ネイティブ側は画面単位の防御が別途要ります。
被害が実際に出るのは、1クリックで完了し、確認画面を挟まない操作です。具体的にはOAuthの同意、権限やロールの付与、退会・アカウント削除、公開範囲の変更、そして残高からの送金確定。逆に、入力を伴う操作や2段階の確認がある操作は、この攻撃だけでは完走しません。優先度を決めるときは、この「1クリックで不可逆か」を基準にすると迷いません。
frame-ancestorsとX-Frame-Optionsの指定値の差と併記時に効く側
使えるヘッダーは2本です。役割が重なる部分と、片方にしかできないことがはっきり分かれています。
X-Frame-Optionsの3つの値とALLOW-FROMが無視される現行の挙動
指定できる値は DENY、SAMEORIGIN、ALLOW-FROM の3種です。DENY は同一オリジンからの埋め込みも含めて全面的に拒否し、SAMEORIGIN は祖先がすべて同一オリジンのときだけ表示を許します。
問題は3つ目です。MDNは ALLOW-FROM を「古いディレクティブ」とし、これを持つレスポンスヘッダーに遭遇した現行ブラウザは「そのヘッダーを完全に無視します」と明記しています。無視されるのは ALLOW-FROM の部分だけでなくヘッダーそのもの。X-Frame-Options: ALLOW-FROM https://partner.example.jp と書いたページは、拒否でも限定許可でもなく、どこからでも埋め込める状態で公開されます。OWASPも、保護のない側へ倒れるフェイルオープンとして注意喚起しています。
frame-ancestorsのソース式とnone・self・ホスト名指定の使い分け
frame-ancestors は Content-Security-Policy ヘッダーのディレクティブとして書きます。指定できるソース式は 'none'、'self'、ホスト名、スキームの4系統。複数のオリジンを空白区切りで並べられる点が、ALLOW-FROM との決定的な差です。
対象は frame・iframe・object・embed で、埋め込み経路をまとめて塞げます。注意したいのは、このディレクティブが default-src の設定で代替されないこと。default-src 'self' だけ書いて埋め込み制御まで効いていると誤解すると、そこが穴になります。nonceや段階導入を含むCSP全体の組み立てはコンテンツセキュリティポリシー(CSP)の設定と実装判断をまとめた記事で扱いました。
両方を返した場合にCSP対応ブラウザが優先する側と旧環境での併記
両方返したときどちらが効くかは、仕様側で整理されています。W3CのContent Security Policy Level 3(Working Draft・2026年7月29日版)は「Relation to X-Frame-Options」を独立した節に置き、OWASPのClickjacking Defense Cheat SheetもChrome 40・Firefox 35という旧版を除いて frame-ancestors が優先すると整理しています。
実務上の答えは「両方返す」です。frame-ancestors を理解するブラウザはそちらを見て、理解しないブラウザは X-Frame-Options に従う。ただし2本の内容は必ず揃えてください。片方がDENYで片方が許可リストという状態は、環境によって挙動が割れます。
| 観点 | X-Frame-Options | CSP frame-ancestors |
|---|---|---|
| 許可元の複数指定 | 不可(ALLOW-FROM廃止) | 可(ソース式で列挙) |
| 対象要素 | frame・iframeが中心 | frame・iframe・object・embed |
| meta要素での配信 | 効かない | 仕様上サポート外 |
| 併記時の扱い | CSP対応環境では無視 | 優先して評価される |
| 残す理由 | 旧環境向けの保険 | 主たる防御 |
metaタグ配信とReport-Onlyで防御が成立しない配信面の制約と確認
設定の中身が正しくても、配り方を間違えると1バイトも効きません。ここが最も静かに壊れます。
metaタグではframe-ancestorsが無視される仕様と生まれる無防備
CSP Level 3 は、meta要素の中では Content-Security-Policy-Report-Only ヘッダーがサポートされず、report-uri・frame-ancestors・sandbox の各ディレクティブも同様だと明記しています。HTMLに <meta http-equiv="Content-Security-Policy" content="frame-ancestors 'none'"> と書いても、ブラウザはこの指定を読み飛ばします。
厄介なのは、エラーも警告も出ないところです。ソースを見れば「対策済み」に見え、診断ツールにかけるまで気づけません。埋め込み制御に関しては、HTTPレスポンスヘッダー以外の選択肢はないと割り切ってください。
Report-Onlyでは埋め込みを止められない検証段階としての位置づけ
Content-Security-Policy-Report-Only は、違反を報告するだけのヘッダーです。埋め込みは通常どおり成立します。CSPを段階導入する流れの中で、既存の埋め込み実績を洗い出す用途には向きますが、これを本番の防御として置いている状態は無防備と同義です。
期限を決めて移行してください。埋め込み要件の棚卸しなら、1週間から2週間ぶんのレポートで傾向は見えます。集めた許可元をそのまま列挙せず、業務上まだ必要かを1件ずつ判定してから本適用へ移す順が安全です。
curlでレスポンスヘッダーを実測し二重設定の上書きを切り分ける手順
設定した内容が本番で返っているかは、必ず実測します。アプリ側の設定ファイルを読むだけでは、前段のWebサーバやCDNによる上書きを見落とします。
curl -Iで本番URLのレスポンスヘッダーを取得し、2本のヘッダーが返っているか確認する- 同じヘッダーが2回出ていないか見る。重複はアプリとWebサーバの二重設定を示す
- CDNやWAFを経由するURLと、オリジンへ直接向けたURLの両方で取得して差分を見る
- ログイン後にしか出ない画面は、Cookieを付けた状態で同じ手順を繰り返す
差が出たときの原因は、ほとんどが配信層の追加設定です。同じヘッダーが2本返った場合、CSPは両方のポリシーが個別に評価されて厳しい側へ寄りますが、X-Frame-Options の重複は扱いが割れます。返す本数は1本に整理してください。
埋め込みが業務要件として残る画面の許可設計と設定を置く層の選び方
すべてを ‘none’ で塞げるサイトばかりではありません。埋め込まれることが仕様の画面をどう扱うかで、設計の質が出ます。
決済フォームや外部ウィジェットを埋める画面で許可元を絞る書き方
まず区別すべきは向きです。frame-ancestors が制御するのは「自分が誰に埋め込まれるか」であって、「自分が誰を埋め込むか」ではありません。後者は frame-src の担当で、決済代行のiframeを自社ページに置く構成なら、そこで制限するのは frame-src 側になります。
自社の画面をパートナーのポータルへ埋め込ませる要件がある場合は、frame-ancestors https://portal.example.jp のようにオリジンを完全一致で列挙します。スキームだけの指定は、そのスキームのあらゆるサイトを許すため使いません。指定単位はオリジンで、パスまでは絞れない点にも注意が要ります。特定の画面だけ許すなら、パス単位でヘッダーを出し分ける構成にしてください。
アプリとWebサーバとCDNのどこにヘッダーを置くかの判断基準
置き場所は3つあり、選び方は例外の粒度で決まります。パスや利用者の状態によって許可元を変えるなら、判断材料を持っているアプリケーション層に置くしかありません。サイト全体で一律に ‘none’ を返すだけなら、Webサーバやその手前のCDNに寄せたほうが、アプリの実装から独立して残ります。
避けたいのは2箇所への分散です。nginxの add_header は、より内側のブロックに add_header が書かれていると外側の指定を引き継ぎません。特定のlocationにキャッシュ制御を足した瞬間、サーバ全体に入れていたセキュリティヘッダーが消える事故がここで起きます。1サイト1箇所を原則にしてください。
SAMEORIGINの多重ネストで子フレームが落ちる挙動と回避の指定
X-Frame-Options の SAMEORIGIN には、ネストしたフレームでの落とし穴があります。OWASPは、ネスト構成では判定が最上位のブラウジングコンテキストに対して行われ、直上の親フレームのコンテキストには適用されないため、子フレームが読み込まれない場合があると指摘しています。
自社ポータルの中に自社の別画面を2段で埋めている構成が該当します。frame-ancestors ‘self’ は祖先すべてを対象に判定するため、同じ意図をより正確に表現できる指定です。SAMEORIGIN で表示が崩れたら、実装差を追う前に frame-ancestors 側へ寄せて確かめるほうが早く片づきます。
frame bustingとSameSite Cookieで補える範囲と残る穴
ヘッダー以外の手も知られています。どこまで補えるかを見誤ると、ヘッダーの手抜きにつながります。
frame busting JavaScriptが回避される4手法と非推奨の根拠
自分がiframe内で表示されているかを判定し、最上位へ遷移させるスクリプトは frame buster と呼ばれます。OWASPは、これが回避可能であるとして4つの手法を挙げています。
- ダブルフレーミング。二重に入れ子にすると親フレームへのアクセスがセキュリティ違反となり、遷移処理が働かない
- onBeforeUnloadイベント。利用者に遷移の確認を出し、キャンセルさせて留まらせる
- No-Content Flushing。204応答でリクエストを流し、遷移先の読み込みを空振りさせる
- iframeのsandbox属性。埋め込む側がスクリプト実行を止めれば、そもそも判定コードが走らない
JavaScriptを無効化されただけで消える防御を、ヘッダーの代わりに据えないでください。frame buster の位置づけは、旧環境向けの保険までです。
SameSite属性が認証付き操作だけを緩和し未認証操作に効かない境界
セッションCookieに SameSite=Strict または Lax が付いていれば、他サイトのiframe内から出たリクエストにCookieが付きません。ログイン状態が前提の操作は、この時点で成立しなくなります。多くのフレームワークが既定でLaxを付ける現在、緩和として効いている場面は少なくありません。
ただしOWASPが明記するとおり、認証を必要としない攻撃には保護を提供しません。問い合わせフォームの送信、投票、外部サイトへの遷移を伴う導線などは、Cookieが落ちても完走します。SameSiteは被害の範囲を狭める補助であって、frame-ancestors の代替にはなりません。
window.confirmで再確認を挟む場面と過剰になる場面の線引き
埋め込みを許可せざるをえない画面には、確認を1枚挟む手があります。OWASPは window.confirm() がフレーム化できないポップアップを表示し、別ドメインのiframeから呼ばれた場合にはブラウザがダイアログのオリジンを示すため、利用者が操作の出所を判断できると説明しています。
置く対象は絞ってください。判断の基準は「取り消せるか」の一点です。送金確定、権限付与、アカウント削除のような不可逆な操作にだけ挟み、閲覧や検索条件の変更には付けない。全操作に確認を出す設計は、数日で読み飛ばされる壁になります。
対策を入れる画面の優先度と診断・防御の各層へ振り分ける導入判断
ここからは判断の話です。どこから入れ、どこで自分たちの手を離すかを決めます。
状態を変える画面から入れる優先度と見送ってよい静的ページの条件
優先順位は明快です。1クリックで不可逆な変更が起きる画面が最優先。次に認証後の画面全般、最後に公開ページという順で入れます。管理画面とOAuthの同意画面は、この分類で常に先頭に来ます。
見送ってよいのは、認証もなく、押しても状態が変わらない静的な公開ページだけです。ただし、そのために配信設定を画面ごとへ分岐させる意味はありません。既定で ‘none’ を配り、埋め込み要件のある画面だけ例外を書く。判断を画面ごとに繰り返さない設計が、結局いちばん安く済みます。
ヘッダー設定だけでは残る実装漏れを脆弱性診断へ回すときの線引き
ヘッダーの有無は機械的に判定できます。ZAPなどの動的検査をCI/CDに組み込めば、欠落は出荷前に落とせます。
機械が判定しないのは中身の妥当性です。許可元として並べたオリジンが今も業務上必要か、そこに乗っている第三者サイトが乗っ取られたら何が起きるか、埋め込み許可と権限設計の組み合わせで想定外の操作が通らないか。ここは設計を読める人間が見る領域です。検証の手が足りないなら、脆弱性診断・セキュリティ診断のように報告から修正後の再診断までを含む外部の工程へ回すほうが、抜けの残り方を管理できます。
WAFやRASPとの守備範囲の違いとクリックジャッキングを止める層
他の防御層との関係も整理しておきます。この攻撃で被害サイトへ届くリクエストは、本人のブラウザから出た正規のリクエストです。パターンとして異常な点がないため、リクエストを検査する層では判定材料がありません。
攻撃が成立しているのは、被害サイトのサーバでもアプリの内部でもなく、利用者のブラウザに描かれた画面の上です。そこへ指示を出せるのはレスポンスヘッダーだけで、この一点が frame-ancestors を代替不能にしています。境界での検査を担うWAFの仕組みとファイアウォール・IPS/IDSとの違いと役割が重ならないのは、そもそも検査点を通らないからです。多層防御の図の中で、この対策だけがブラウザ側に置かれます。
よくある質問
実装の現場で問い合わせの多い5点を挙げます。
X-Frame-Optionsだけ設定していれば十分ですか?
DENY または SAMEORIGIN で運用していて、埋め込み要件がまったくないサイトなら、防御としては成立しています。ただし特定の相手にだけ許可したい要件が出た瞬間に行き詰まります。ALLOW-FROM は現行ブラウザに無視され、その結果は「制限なし」になるためです。新規に設定するなら frame-ancestors を主に据え、X-Frame-Options は旧環境向けの併記として同じ内容で残す形をおすすめします。
frame-ancestorsを入れるとSNSの埋め込みや広告は壊れますか?
壊れません。frame-ancestors が制御するのは「自分のページが他所に埋め込まれること」で、自分のページの中に外部のiframeを置く動作には関与しないためです。後者を制限するのは frame-src になります。この2つを取り違えて frame-ancestors を緩める設定ミスは実際に起きるため、変更前に向きを確認してください。
WordPressやSaaS上のサイトでもクリックジャッキング対策はできますか?
HTTPレスポンスヘッダーを追加できるかどうかで決まります。WordPressならテーマの functions.php やプラグイン、前段のWebサーバやCDNでも付与できます。管理画面を持つCMSは標的になりやすいため、優先度は高めに置いてください。テンプレートしか触れないSaaSでは、事業者側にヘッダー設定の可否を確認する以外に方法がありません。
クリックジャッキングの被害はサーバのログで検知できますか?
アクセスログだけでは困難です。届くのは正規のセッションを持った正常なリクエストで、異常な痕跡が残りません。手がかりは Referer と、埋め込み文脈を示す Sec-Fetch-Dest: iframe の組み合わせで、想定外のオリジンからの枠内表示は調査の起点になります。事後の検知に頼るより、ヘッダーで成立自体を止めるほうが確実です。
すべてのページにframe-ancestors ‘none’を入れて問題ありませんか?
埋め込み要件がないサイトなら問題ありません。確認すべきは、自社サイトの画面を枠内で表示している外部サービスの有無です。ヘルプデスクの管理画面、社内ポータル、業務提携先のダッシュボードが該当しやすい箇所になります。導入前に Report-Only で違反を集め、実際に埋め込まれている経路を洗い出してから本適用へ進めてください。
関連記事
- CORSとは?仕組み・プリフライト・サーバー設定例・エラー解決を実装目線で解説:同じくブラウザ側で効くヘッダー制御で、オリジン間の読み取り可否を扱う層
- DASTとは?SASTとの違い・ZAPでのCI/CD組み込みと導入判断を実装視点で解説:セキュリティヘッダーの欠落を出荷前に機械検知する工程
- RASP(実行時アプリケーション自己保護)とは?WAFとの違いと導入判断を実装視点で解説:アプリ内部で止める層との守備範囲の違い