📌 結論:3ステップでクリックジャッキング対策完了!
STEP 1
Lambda関数を作成
us-east-1リージョン必須
ヘッダー追加コードを書く
us-east-1リージョン必須
ヘッダー追加コードを書く
STEP 2
CloudFrontに紐付け
Origin Responseに設定
バージョン発行して関連付け
Origin Responseに設定
バージョン発行して関連付け
STEP 3
動作確認
レスポンスヘッダーを確認
iframe埋め込みをテスト
レスポンスヘッダーを確認
iframe埋め込みをテスト
✈️ 空港の税関検査で理解しよう!
🛃 Lambda@Edge = 空港の税関検査官
あなたのWebサイトへのアクセスは、国際空港を通過する旅客のようなもの。
Lambda@Edge(税関検査官)がOrigin Response(入国審査後)のタイミングで
X-Frame-Options(セキュリティスタンプ)を押してくれます!
ユーザー
旅行者
➡️
CloudFront
国際空港
➡️
オリジン(S3等)
目的地の国
➡️
Lambda@Edge
税関検査官
(ここでスタンプ!)
(ここでスタンプ!)
➡️
安全なレスポンス
スタンプ付きで帰国
🔖 X-Frame-Optionsスタンプの意味:
「このWebページは他人の額縁(iframe)に入れられることを拒否します」
悪意あるサイトがあなたのページを勝手に埋め込むのを防ぎます!
⚡ Lambda@Edgeの4つのトリガーポイント
Origin ResponseがX-Frame-Optionsを追加するベストタイミングです!
ユーザー
➡️
① Viewer Request
➡️
CloudFront
キャッシュ確認
➡️
② Origin Request
➡️
オリジン
S3 / ALB など
ユーザー
⬅️
④ Viewer Response
⬅️
CloudFront
キャッシュ保存
⬅️
③ Origin Response ⭐
⬅️
オリジン
✅ Origin Response を選ぶ理由
- キャッシュにヘッダー付きで保存される
- オリジンからの全レスポンスに適用
- 効率的(キャッシュヒット時も適用済み)
⚠️ Viewer Response との違い
- Viewer Responseは毎リクエスト実行
- コストが高くなる可能性
- キャッシュ済みでも毎回Lambda実行
🔧 ヘッダー追加の仕組み
オリジンからのレスポンスにX-Frame-Optionsヘッダーを追加してCloudFrontに返します
❌
Before(オリジンからの返答)
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1234
Cache-Control: max-age=3600
(セキュリティヘッダーなし...)
Content-Type: text/html
Content-Length: 1234
Cache-Control: max-age=3600
(セキュリティヘッダーなし...)
➡️
✅
After(Lambda@Edge処理後)
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1234
Cache-Control: max-age=3600
X-Frame-Options: SAMEORIGIN
(セキュリティヘッダー追加!)
Content-Type: text/html
Content-Length: 1234
Cache-Control: max-age=3600
X-Frame-Options: SAMEORIGIN
(セキュリティヘッダー追加!)
🎯 X-Frame-Options の3つのオプション
😈 クリックジャッキング攻撃とは?
🎭 透明なiframeで騙す攻撃手法
❌
X-Frame-Optionsなし
😈 悪意あるサイト
🎁 無料プレゼント!
ここをクリック!
😱 クリックすると送金実行!
✅
X-Frame-Optionsあり
😈 悪意あるサイト
🎁 無料プレゼント!
ここをクリック!
🎉 銀行サイトは表示されない!
📋 クリックジャッキングの手口
- 攻撃者が悪意あるWebページを作成
- そこに透明なiframeであなたの銀行サイト等を埋め込む
- 「プレゼント獲得!」などの偽のボタンを表示
- ユーザーがクリックすると、実際は銀行の送金ボタンをクリック
- ログイン済みならそのまま送金が実行されてしまう!
💻 Lambda関数コード例
📝
index.js(Node.js 18.x / 20.x)
'use strict';
// Lambda@Edge Origin Response ハンドラー
exports.handler = async (event) => {
// CloudFrontからのレスポンスを取得
const response = event.Records[0].cf.response;
const headers = response.headers;
// X-Frame-Options ヘッダーを追加
// DENY: 全てのiframe埋め込みを禁止
// SAMEORIGIN: 同一オリジンからの埋め込みのみ許可(推奨)
headers['x-frame-options'] = [{
key: 'X-Frame-Options',
value: 'SAMEORIGIN'
}];
// その他のセキュリティヘッダーも追加(推奨)
headers['x-content-type-options'] = [{
key: 'X-Content-Type-Options',
value: 'nosniff'
}];
headers['x-xss-protection'] = [{
key: 'X-XSS-Protection',
value: '1; mode=block'
}];
headers['referrer-policy'] = [{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin'
}];
// 修正したレスポンスを返す
return response;
};
📋 コードのポイント
event.Records[0].cf.responseでレスポンスオブジェクトを取得- ヘッダー名は小文字で指定(
x-frame-options) keyプロパティは表示用の正式名(大文字小文字混在OK)- 必ずレスポンスオブジェクトを return する
📝 設定手順フローチャート
🚀 5ステップで完了!
1
🌎 us-east-1 リージョンでLambda関数を作成
Lambda@Edgeは必ずus-east-1(バージニア北部)で作成する必要があります。
ランタイム: Node.js 18.x または 20.x を選択
ランタイム: Node.js 18.x または 20.x を選択
⚠️ 他のリージョンで作成するとCloudFrontに関連付けできません!
2
📝 上記のコードをデプロイ
Lambda関数にコードを貼り付けて保存。
X-Frame-Optionsの値を
X-Frame-Optionsの値を
DENYかSAMEORIGINに設定
3
🔐 IAMロールにEdge権限を追加
Lambda関数のIAMロールの「信頼関係」を編集し、
edgelambda.amazonaws.com を追加
信頼ポリシーの Service に "edgelambda.amazonaws.com" を追加
4
📦 バージョンを発行
Lambda関数の「アクション」→「新しいバージョンを発行」
Lambda@Edgeは$LATESTではなく特定のバージョンを指定する必要があります
Lambda@Edgeは$LATESTではなく特定のバージョンを指定する必要があります
5
🔗 CloudFrontに関連付け
CloudFrontディストリビューション → ビヘイビア → 編集
「関数の関連付け」でOrigin ResponseにLambda関数ARN(バージョン付き)を設定
「関数の関連付け」でOrigin ResponseにLambda関数ARN(バージョン付き)を設定
ARN例: arn:aws:lambda:us-east-1:123456789012:function:add-security-headers:1
📊 トリガーポイント比較表
| トリガー | タイミング | セキュリティヘッダー追加 | キャッシュ効率 |
|---|---|---|---|
| ① Viewer Request | ユーザー → CloudFront | 不可 | - |
| ② Origin Request | CloudFront → オリジン | 不可 | - |
| ③ Origin Response ⭐ | オリジン → CloudFront | 最適! | 高効率 |
| ④ Viewer Response | CloudFront → ユーザー | 可能だが非推奨 | 毎回実行 |
❓ よくある質問
🤔
なぜ us-east-1 リージョンでないとダメ?
Lambda@EdgeはCloudFrontと密接に連携するため、us-east-1でのみ作成可能です。
CloudFrontはグローバルサービスで、そのコントロールプレーンがus-east-1にあるためです。
ただし、実行時はユーザーに近いエッジロケーションで実行されるため、
レイテンシーの心配はありません。
CloudFrontはグローバルサービスで、そのコントロールプレーンがus-east-1にあるためです。
ただし、実行時はユーザーに近いエッジロケーションで実行されるため、
レイテンシーの心配はありません。
🤔
CloudFront Functions ではダメ?
CloudFront FunctionsでもOrigin Responseで同じことが可能です!
CloudFront Functions を選ぶケース:
・シンプルなヘッダー追加のみ
・コストを最小限にしたい
・より高速な実行が必要
Lambda@Edge を選ぶケース:
・複雑なロジックが必要
・外部APIへのアクセスが必要
・より多くのメモリ・実行時間が必要
CloudFront Functions を選ぶケース:
・シンプルなヘッダー追加のみ
・コストを最小限にしたい
・より高速な実行が必要
Lambda@Edge を選ぶケース:
・複雑なロジックが必要
・外部APIへのアクセスが必要
・より多くのメモリ・実行時間が必要
🤔
Content-Security-Policy の frame-ancestors との違いは?
CSPのframe-ancestorsはX-Frame-Optionsの後継で、より柔軟な設定が可能です。
X-Frame-Options:
・古いブラウザもサポート
・シンプルな設定(DENY/SAMEORIGIN)
CSP frame-ancestors:
・複数のドメインを指定可能
・より細かい制御が可能
・モダンブラウザのみ対応
推奨:両方を設定して互換性を確保
X-Frame-Options:
・古いブラウザもサポート
・シンプルな設定(DENY/SAMEORIGIN)
CSP frame-ancestors:
・複数のドメインを指定可能
・より細かい制御が可能
・モダンブラウザのみ対応
推奨:両方を設定して互換性を確保
🤔
既にオリジンでX-Frame-Optionsを設定している場合は?
Lambda@Edgeで上書きされます。
オリジンのレスポンスにすでにX-Frame-Optionsヘッダーがある場合、
Lambda@Edgeのコードで同じヘッダーを設定すると上書きされます。
既存のヘッダーを保持したい場合は、コード内で存在チェックを追加してください:
オリジンのレスポンスにすでにX-Frame-Optionsヘッダーがある場合、
Lambda@Edgeのコードで同じヘッダーを設定すると上書きされます。
既存のヘッダーを保持したい場合は、コード内で存在チェックを追加してください:
if (!headers['x-frame-options']) {
headers['x-frame-options'] = [{ key: 'X-Frame-Options', value: 'SAMEORIGIN' }];
}
💡
ベストプラクティス
1. 複数のセキュリティヘッダーをまとめて追加:
X-Frame-Options だけでなく、X-Content-Type-Options、X-XSS-Protection、
Referrer-Policy、Content-Security-Policy もまとめて設定しましょう。
2. テスト環境で十分に検証:
本番環境に適用する前に、ステージング環境でiframe埋め込みの動作を確認。
自サイト内でiframeを使っている場合はSAMEORIGINを選択。
3. CloudWatch Logsでモニタリング:
Lambda@Edgeのログはエッジロケーションごとにリージョンに出力されます。
エラーが発生していないか定期的に確認しましょう。
X-Frame-Options だけでなく、X-Content-Type-Options、X-XSS-Protection、
Referrer-Policy、Content-Security-Policy もまとめて設定しましょう。
2. テスト環境で十分に検証:
本番環境に適用する前に、ステージング環境でiframe埋め込みの動作を確認。
自サイト内でiframeを使っている場合はSAMEORIGINを選択。
3. CloudWatch Logsでモニタリング:
Lambda@Edgeのログはエッジロケーションごとにリージョンに出力されます。
エラーが発生していないか定期的に確認しましょう。
🎓 まとめ
Lambda@Edge
空港の税関検査官
レスポンスにスタンプを押す
レスポンスにスタンプを押す
Origin Response
ヘッダー追加の
ベストタイミング
ベストタイミング
X-Frame-Options
クリックジャッキング
攻撃を防止
攻撃を防止
SAMEORIGIN推奨
同一ドメインからは
埋め込みを許可
埋め込みを許可