リンドくん先生、「クリックジャッキング」って何ですか?最近セキュリティの勉強をしていて出てきたんですけど...
たなべクリックジャッキングは見えない罠をクリックさせる攻撃なんだ。
例えば、「無料でプレゼントをもらう」ボタンだと思ってクリックしたら、実は裏に隠された「お金を送金する」ボタンをクリックさせられていた...みたいなことが起こるんだよ。
リンドくんえっ、怖いですね!どうやってそんなことができるんですか?
たなべそれは透明なiframeを使ったトリックなんだ。今日はその仕組みと、フレームバスターという防御方法について詳しく説明していくよ。
Webアプリケーションのセキュリティを学ぶ上で、クリックジャッキング(Clickjacking)は必ず理解しておくべき攻撃手法の一つです。
クリックジャッキングは、別名「UI Redressing攻撃」とも呼ばれ、ユーザーを騙して意図しないクリックをさせる攻撃のことを指します。
透明なiframeを使って正規のWebサイトを重ね、ユーザーが別のボタンをクリックしていると思わせながら、攻撃者が意図した操作を実行させてしまうのです。
この記事では、クリックジャッキング攻撃の仕組みから、X-Frame-OptionsやCSP(Content Security Policy)、そしてフレームバスターといった防御方法まで、セキュリティ学習を始めたばかりの方でも理解できるよう、実例を交えながら解説していきます。
クリックジャッキング攻撃の仕組みを理解しよう
リンドくん透明なiframeって、どういうことなんですか?
たなべiframeっていうのは、Webページの中に別のWebページを埋め込む技術なんだ。
これ自体は便利な機能なんだけど、CSSで透明にして正規サイトの上に重ねることで、ユーザーには見えない罠を作れちゃうんだよ。
クリックジャッキングの基本的な仕組み
クリックジャッキング攻撃は、以下のような手順で実行されます。
- 攻撃者が罠サイトを用意する - 一見無害なWebページを作成します
- 正規サイトをiframeで埋め込む - 銀行サイトやSNSなどを透明なiframeとして配置します
- iframeを透明化して重ねる - CSSを使って完全に透明にします
- 偽のボタンを配置 - 魅力的な偽のボタンを罠として表示します
- ユーザーが偽ボタンをクリック - 実際には透明なiframe内のボタンをクリックすることになります
- 意図しない操作が実行される - 送金、フォロー、投稿などが勝手に行われます
具体的な攻撃コードの例
実際にどのようなコードで攻撃が行われるのか見てみましょう。
<!DOCTYPE html>
<html>
<head>
<title>無料プレゼントキャンペーン!</title>
<style>
/* 罠サイトのスタイル */
body {
font-family: Arial, sans-serif;
text-align: center;
padding: 50px;
}
.fake-button {
background-color: #ff6600;
color: white;
padding: 20px 40px;
font-size: 24px;
border: none;
border-radius: 10px;
cursor: pointer;
position: absolute;
top: 200px;
left: 50%;
transform: translateX(-50%);
z-index: 1; /* 偽ボタンは下に配置 */
}
/* 透明なiframeを上に重ねる */
.invisible-iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0; /* 完全に透明 */
z-index: 2; /* 透明iframeは上に配置 */
}
</style>
</head>
<body>
<h1>🎁 今すぐクリックして豪華プレゼントをゲット! 🎁</h1>
<!-- 魅力的な偽のボタン -->
<button class="fake-button">プレゼントを受け取る</button>
<!-- 透明な正規サイトのiframe(実際には送金ボタンなどがある) -->
<iframe class="invisible-iframe"
src="https://bank-____.com/transfer">
</iframe>
</body>
</html>このコードでは、ユーザーは「プレゼントを受け取る」ボタンをクリックしているつもりが、実際には透明なiframe内の銀行サイトの「送金」ボタンをクリックさせられてしまいます。
より高度な攻撃手法
攻撃者はさらに巧妙な手法を使うこともあります。
ダブルクリックを要求する手法
<style>
.overlay {
position: absolute;
opacity: 0;
z-index: 10;
}
</style>
<div>
<p>規約に同意する場合は、下のボタンを素早く2回クリックしてください</p>
<button>同意する</button>
<!-- 1回目のクリックでフォローボタン、2回目で確認ボタンをクリックさせる -->
<iframe class="overlay" src="https://sns-____.com/follow?user=attacker"></iframe>
</div>マウスカーソル位置のずらし
<style>
/* ユーザーのマウスカーソル位置を計算して、
意図したボタンの位置に透明iframeを動的に配置 */
.tracking-iframe {
position: absolute;
opacity: 0;
pointer-events: auto;
}
</style>
<script>
// マウスの動きを追跡して、iframeの位置を調整
document.addEventListener('mousemove', function(e) {
const iframe = document.querySelector('.tracking-iframe');
iframe.style.left = (e.pageX - 50) + 'px';
iframe.style.top = (e.pageY - 50) + 'px';
});
</script>クリックジャッキングによる被害例
実際にどのような被害が発生する可能性があるのでしょうか。
- SNSでの意図しないフォローや投稿 - 攻撃者のアカウントを勝手にフォローさせられる
- 銀行からの送金 - 本人確認済みの状態で送金処理が実行される
- 商品の購入 - ワンクリック購入が設定されている場合、勝手に商品が購入される
- 権限の変更 - 管理画面で権限を変更させられる
- 設定の変更 - プライバシー設定やセキュリティ設定を変更される
- Webカメラやマイクの許可 - デバイスへのアクセス許可を与えてしまう
クリックジャッキングは、ユーザーの操作そのものを乗っ取るため、非常に危険な攻撃手法なのです。
X-Frame-Optionsによる基本的な防御
リンドくんじゃあ、どうやって防げばいいんですか?
たなべ最も基本的で効果的な方法がX-Frame-OptionsというHTTPヘッダを設定することなんだ。
これを使えば、自分のサイトがiframeに埋め込まれること自体を防げるんだよ。
X-Frame-Optionsとは
X-Frame-Optionsは、ブラウザに対してiframeでの表示を制御する指示を出すHTTPレスポンスヘッダです。このヘッダを設定することで、自分のWebサイトが他のサイトのiframe内に表示されるのを防ぐことができます。
X-Frame-Optionsの3つの値
X-Frame-Optionsには以下の3つの設定値があります。
DENY(完全拒否)X-Frame-Options: DENY
どのサイトからもiframeでの埋め込みを拒否します。最も厳格な設定です。
SAMEORIGIN(同一オリジンのみ許可)X-Frame-Options: SAMEORIGIN
自分のサイトと同じドメインからのみiframe埋め込みを許可します。サブページを自サイト内でiframe表示したい場合などに使用します。
ALLOW-FROM(特定のオリジンのみ許可)X-Frame-Options: ALLOW-FROM https://____.com
指定した特定のサイトからのみiframe埋め込みを許可します。
※ただし、この値は現在多くのブラウザでサポートが終了しているため、後述のCSPの使用が推奨されます。
各サーバでの実装方法
実際に各種サーバでX-Frame-Optionsを設定する方法を見ていきましょう。
Apache(.htaccessまたはhttpd.conf)
# すべてのページでiframe埋め込みを拒否
Header always set X-Frame-Options "DENY"
# または、同一オリジンのみ許可
Header always set X-Frame-Options "SAMEORIGIN"Nginx(nginx.conf)
# すべてのレスポンスにヘッダを追加
add_header X-Frame-Options "DENY" always;
# または、同一オリジンのみ許可
add_header X-Frame-Options "SAMEORIGIN" always;PHP
<?php
// ページの最初でヘッダを設定
header('X-Frame-Options: DENY');
// または、同一オリジンのみ許可
header('X-Frame-Options: SAMEORIGIN');
?>Node.js(Express)
const express = require('express');
const app = express();
// すべてのレスポンスにヘッダを追加
app.use((req, res, next) => {
res.setHeader('X-Frame-Options', 'DENY');
// または: res.setHeader('X-Frame-Options', 'SAMEORIGIN');
next();
});Python(Flask)
from flask import Flask, make_response
app = Flask(__name__)
@app.after_request
def add_security_headers(response):
response.headers['X-Frame-Options'] = 'DENY'
# または: response.headers['X-Frame-Options'] = 'SAMEORIGIN'
return responseRuby on Rails
# config/application.rb
module YourApp
class Application < Rails::Application
# すべてのレスポンスにヘッダを追加
config.action_dispatch.default_headers = {
'X-Frame-Options' => 'SAMEORIGIN'
}
end
end設定の確認方法
X-Frame-Optionsが正しく設定されているか確認する方法はいくつかあります。
ブラウザの開発者ツールで確認
- ブラウザで自分のサイトを開く
- F12キーで開発者ツールを開く
- 「Network」タブを選択
- ページをリロード
- HTMLドキュメントのレスポンスヘッダを確認
curlコマンドで確認
curl -I https://____.com
# レスポンスヘッダに以下が含まれていればOK
# X-Frame-Options: DENYオンラインツールで確認
- Security Headers(https://securityheaders.com/)
- Mozilla Observatory(https://observatory.mozilla.org/)
これらのツールでサイトのURLを入力すると、セキュリティヘッダの設定状況を確認できます。
X-Frame-Optionsの制限事項
X-Frame-Optionsは効果的ですが、いくつかの制限があります。
- ALLOW-FROM は非推奨 - 多くのモダンブラウザでサポート終了
- 複数のドメインを指定できない - 1つのヘッダで複数の許可サイトを指定不可
- 古いブラウザでは無効 - IE8以前などでは機能しない
これらの制限を克服するため、次世代の対策としてCSP(Content Security Policy)が推奨されています。
CSP(Content Security Policy)による高度な防御
リンドくんX-Frame-Options以外にも方法があるんですか?
たなべそう!CSP(Content Security Policy)という、より柔軟で強力な仕組みがあるんだ。
X-Frame-Optionsの後継として、こちらが推奨されているよ。
CSPのframe-ancestorsディレクティブ
CSPは様々なセキュリティ設定ができる包括的な仕組みですが、クリックジャッキング対策にはframe-ancestorsディレクティブを使用します。
基本的な書き方
Content-Security-Policy: frame-ancestors 'none'
frame-ancestorsの設定値
'none'(完全拒否)Content-Security-Policy: frame-ancestors 'none'
どのサイトからもiframe埋め込みを拒否します。X-Frame-Options: DENYと同等です。
'self'(同一オリジンのみ)Content-Security-Policy: frame-ancestors 'self'
自分のサイトからのみiframe埋め込みを許可します。X-Frame-Options: SAMEORIGINと同等です。
特定のドメインを指定Content-Security-Policy: frame-ancestors 'self' https://____.com https://another-trusted.com
複数の信頼できるドメインを指定できます。これがX-Frame-Optionsに対する大きなアドバンテージです。
各サーバでのCSP実装
Apache
Header always set Content-Security-Policy "frame-ancestors 'none'"
# または、特定のサイトを許可
Header always set Content-Security-Policy "frame-ancestors 'self' https://____.com"Nginx
add_header Content-Security-Policy "frame-ancestors 'none'" always;
# または、複数のサイトを許可
add_header Content-Security-Policy "frame-ancestors 'self' https://trusted1.com https://trusted2.com" always;PHP
<?php
// iframe埋め込みを完全拒否
header("Content-Security-Policy: frame-ancestors 'none'");
// または、特定のサイトを許可
header("Content-Security-Policy: frame-ancestors 'self' https://____.com");
?>const helmet = require('helmet');
app.use(
helmet.contentSecurityPolicy({
directives: {
frameAncestors: ["'none'"]
// または: frameAncestors: ["'self'", "https://____.com"]
}
})
);X-Frame-OptionsとCSPの併用
最大限の互換性を確保するため、両方を併用することが推奨されます。
<?php
// 古いブラウザ向けにX-Frame-Options
header('X-Frame-Options: DENY');
// モダンブラウザ向けにCSP
header("Content-Security-Policy: frame-ancestors 'none'");
?># Nginx設定例
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Content-Security-Policy "frame-ancestors 'self'" always;CSPの詳細な設定例
より包括的なセキュリティを実現するCSPの例です。
<?php
header("Content-Security-Policy: " .
"default-src 'self'; " . // デフォルトは同一オリジンのみ
"script-src 'self' 'unsafe-inline'; " . // スクリプトの読み込み元
"style-src 'self' 'unsafe-inline'; " . // CSSの読み込み元
"img-src 'self' data: https:; " . // 画像の読み込み元
"frame-ancestors 'none'; " . // iframe埋め込み拒否
"form-action 'self'; " . // フォーム送信先
"base-uri 'self'" // base要素の制限
);
?>CSPのテストとデバッグ
CSPは強力ですが、設定ミスでサイトが壊れる可能性もあります。安全にテストする方法があります。
Report-Onlyモードでテスト
<?php
// 実際にブロックせず、違反をレポートのみ
header("Content-Security-Policy-Report-Only: frame-ancestors 'none'; report-uri /csp-report");
?>このモードでは、CSPに違反しても実際にはブロックされず、違反内容が指定したURLに送信されます。本番適用前のテストに最適です。
フレームバスターによるJavaScriptでの防御
リンドくんサーバ側の設定ができない場合はどうすればいいんですか?
たなべそういう場合はフレームバスターというJavaScriptを使った方法があるよ。
ただし、これは補助的な対策として考えるべきで、HTTPヘッダによる防御の方が確実なんだ。
フレームバスターとは
フレームバスターは、JavaScriptを使ってiframeに埋め込まれていないかをチェックし、埋め込まれている場合は対処するコードのことです。
基本的なフレームバスターコード
最もシンプルなフレームバスターは以下のようなものです。
// 自分のページがiframe内で表示されているかチェック
if (window.top !== window.self) {
// iframe内で表示されている場合、トップに移動
window.top.location = window.self.location;
}このコードは、自分のページがiframe内で表示されている場合、親ページを自分のページで置き換えます。
より堅牢なフレームバスター
上記のシンプルなコードは、攻撃者によって回避される可能性があります。より堅牢な実装を見てみましょう。
(function() {
// iframe内で表示されているかチェック
if (window.top !== window.self) {
try {
// まずトップページを置き換えようと試みる
if (window.top.location.hostname !== window.self.location.hostname) {
window.top.location = window.self.location.href;
}
} catch (e) {
// Same-Origin Policyで失敗する場合(期待通り)
// bodyを非表示にして警告を表示
var body = document.getElementsByTagName('body')[0];
body.style.display = 'none';
// 警告メッセージを表示
var warning = document.createElement('div');
warning.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;' +
'background:white;z-index:10000;padding:50px;text-align:center;';
warning.innerHTML = '<h1>セキュリティ警告</h1>' +
'<p>このページは不正な方法で表示されています。</p>' +
'<p><a href="' + window.self.location.href + '">正しいページで開く</a></p>';
document.body.appendChild(warning);
}
}
})();スタイルシート破壊の防止
攻撃者は以下のような方法でフレームバスターを無効化しようとします。
<!-- 攻撃者の罠サイト -->
<style>
/* フレームバスターが実行される前にiframeを非表示に */
iframe { display: none !important; }
</style>
<iframe security="restricted" sandbox></iframe>これを防ぐため、JavaScriptが実行されるまでページを非表示にする方法があります。
<!DOCTYPE html>
<html>
<head>
<style id="antiClickjack">
/* JavaScriptが実行されるまでページを非表示 */
body { display: none !important; }
</style>
<script>
// フレームバスターチェック
if (window.top === window.self) {
// iframe内でなければ、非表示スタイルを削除
var antiClickjack = document.getElementById("antiClickjack");
antiClickjack.parentNode.removeChild(antiClickjack);
} else {
// iframe内の場合は警告を表示
window.top.location = window.self.location;
}
</script>
</head>
<body>
<!-- ページコンテンツ -->
</body>
</html>フレームバスターの限界と問題点
フレームバスターにはいくつかの問題があります。
JavaScriptが無効な環境では動作しない
// JavaScriptが無効化されていると、この防御は機能しない
if (window.top !== window.self) {
window.top.location = window.self.location;
}sandbox属性による無効化
<!-- sandbox属性でJavaScriptを無効化 -->
<iframe sandbox="allow-forms" src="https://____.com"></iframe>204 No Contentレスポンスによる無効化 攻撃者が204レスポンスを返すことで、location変更を無効化できます。
onBeforeUnloadイベントの悪用
<script>
window.onbeforeunload = function() {
return false; // ページ遷移をキャンセル
};
</script>フレームバスターの正しい使い方
フレームバスターは以下のように使用すべきです。
- HTTPヘッダ(X-Frame-Options/CSP)が設定できない場合の補助として使用
- 多層防御の一部として実装
- 主要な防御手段としては使用しない
理想的なセキュリティ実装は以下のような多層防御です。
<?php
// 第1の防御:HTTPヘッダ
header('X-Frame-Options: DENY');
header("Content-Security-Policy: frame-ancestors 'none'");
?>
<!DOCTYPE html>
<html>
<head>
<!-- 第2の防御:フレームバスター -->
<style id="antiClickjack">
body { display: none !important; }
</style>
<script>
if (window.top === window.self) {
var antiClickjack = document.getElementById("antiClickjack");
antiClickjack.parentNode.removeChild(antiClickjack);
} else {
window.top.location = window.self.location;
}
</script>
</head>
<body>
<!-- コンテンツ -->
</body>
</html>この多層防御により、一つの対策が破られても他の対策でカバーできる体制を構築できます。
よくある設定ミスと対処法
ミス① 一部のページのみ設定
<?php
// ❌ 悪い例:ログインページだけ設定
if ($page === 'login') {
header('X-Frame-Options: DENY');
}
// ✅ 良い例:すべてのページで設定
header('X-Frame-Options: SAMEORIGIN');
?>ミス② 設定の優先順位ミス
# ❌ 悪い例:後の設定で上書きされる
Header set X-Frame-Options "DENY"
Header set X-Frame-Options "SAMEORIGIN"
# ✅ 良い例:alwaysを使用して確実に設定
Header always set X-Frame-Options "SAMEORIGIN"ミス③ HTTPSとHTTPで設定が異なる
# ❌ 悪い例:HTTPSのみで設定
server {
listen 443 ssl;
add_header X-Frame-Options "DENY";
}
# ✅ 良い例:HTTPでもリダイレクト前に設定
server {
listen 80;
add_header X-Frame-Options "DENY" always;
return 301 https://$server_name$request_uri;
}セキュリティ監査ツールの活用
定期的にセキュリティ設定をチェックしましょう。
推奨ツール
- Mozilla Observatory - 包括的なセキュリティチェック
- Security Headers - ヘッダ設定の確認
- OWASP ZAP - 脆弱性スキャナー
これらのツールを使って定期的にサイトをスキャンすることで、設定漏れや新たな脆弱性を発見できます。
まとめ
リンドくんクリックジャッキングの仕組みと防御方法、よくわかりました!
たなべ素晴らしいね!最後に重要なポイントをまとめておこう。
セキュリティ対策は一つの方法だけに頼らず、複数の対策を組み合わせることが大切なんだ。これを多層防御(Defense in Depth)と呼ぶんだよ。
この記事では、クリックジャッキング攻撃の仕組みと、様々な防御方法について詳しく解説してきました。
重要なポイントを改めて整理しましょう。
- 透明なiframeを悪用した視覚的な騙しである
- ユーザーの意図しないクリックを引き起こす
- 送金、設定変更、権限付与など深刻な被害につながる
- UI Redressing攻撃とも呼ばれる
- X-Frame-Optionsの設定(互換性重視)
- CSPのframe-ancestorsディレクティブ(柔軟性重視)
- 両方を併用することが推奨される
- JavaScriptによる補助的な防御
- HTTPヘッダが設定できない場合の代替手段
- 主要な防御手段としては不十分
- Same-Site Cookie属性の設定
- CSRFトークンとの組み合わせ
- 包括的なCSP設定
- すべてのページで設定されているか確認する
- HTTPSとHTTPの両方で設定する
- テスト環境で動作確認してから本番適用する
- 定期的にセキュリティ監査を実施する
- フレームワークの標準機能を活用する
クリックジャッキング対策は、プロジェクトの初期段階から組み込むべきセキュリティ対策の一つです。
後から追加するよりも、最初から適切な設定をしておく方が、はるかに効率的で安全です。
今回学んだ知識を、ぜひあなたのプロジェクトに活かしてください。HTTPヘッダの設定は非常にシンプルですが、その効果は絶大です。たった1行のコード追加で、深刻な攻撃からユーザーを守ることができるのです。
セキュリティは、ユーザーの信頼を獲得し、長期的にサービスを運営していく上で欠かせない要素です。
クリックジャッキング対策だけでなく、CSRF対策、XSS対策、SQL インジェクション対策など、包括的なセキュリティ知識を身につけていきましょう。





