はじめに
応用情報技術者試験のセキュリティ分野を勉強している中で、クリックジャッキングという攻撃手法を学びました。
そこでふと、
あれ? 自分が公開しているWebサイトはクリックジャッキング対策をしていたかな?
と気になり、実際に確認してみることにしました。
確認してみると、公開中のサイトではクリックジャッキング対策として使われる Content-Security-Policy の frame-ancestors や X-Frame-Options を設定していませんでした。
今回は、
- 現在のResponse Headersを確認
- 実際にiframeへ埋め込めるか検証
- どこで対策するか検討
- XServerの
.htaccessに対策を追加 - ローカルApache環境で確認
- XServer本番環境で確認
- 対策後にiframeへの埋め込みが拒否されることを確認
という流れで、実際のサイトを使って検証しました。
単に設定ファイルへ記述するだけではなく、ブラウザへ返されたResponse Headersと実際の表示結果まで確認することを重視しています。
クリックジャッキングとは
クリックジャッキングは、正規のWebページをiframeなどで別のページに重ねて表示し、利用者に意図しない操作をさせる攻撃です。
例えば、利用者には別のボタンが見えているように見せながら、その位置に透明なiframeで正規サイトのボタンを重ねることで、本来とは異なる操作を実行させることが考えられます。
今回の記事では攻撃手法そのものを詳しく扱うのではなく、自分のWebサイトがiframeなどへ埋め込まれることを制限する対策に焦点を当てます。
MDNでは、クリックジャッキング対策として主に次の2つが説明されています。
- CSPの
frame-ancestors X-Frame-Options
frame-ancestors は、どの親ページから埋め込みを許可するかを指定できます。
今回はサイトをiframe等へ埋め込む用途がないため、frame-ancestors 'none' を使用しました。
また、X-Frame-Options: DENY も併記しました。
まず現在のResponse Headersを確認する
最初にChrome DevToolsのNetworkタブから、ローカル環境のResponse Headersを確認しました。
この時点では、
X-Frame-Options: SAMEORIGIN
は返っていましたが、
Content-Security-Policy: frame-ancestors 'none'
は設定されていませんでした。
つまり、ローカル環境には既存の SAMEORIGIN 設定が存在していたものの、今回採用したい「同一オリジンを含めて埋め込みを禁止する」設定にはなっていませんでした。
実際にiframeで埋め込めるか確認する
Response Headersを見るだけでなく、実際にiframeで表示できるかも確認しました。
検証用のHTMLは、考え方としては次のような単純なものです。
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Clickjacking Test</title>
</head>
<body>
<h1>Clickjacking Test</h1>
<iframe
src="https://example.com/"
width="100%"
height="800"
></iframe>
</body>
</html>
本番環境で対策前の状態を確認するため、バックアップを取得したうえで旧 .htaccess に一時的に戻して検証しました。
すると、iframe内に公開中のログイン画面を表示できました。
この確認によって、「対策ヘッダーが見当たらない」というだけではなく、実際にiframeへ埋め込める状態だったことを確認できました。
検証後はすぐに対策済みの .htaccess へ戻しています。
今回のXServer構成
今回の環境では、XServer上で複数のWebアプリケーションをサブドメインごとに分けて運用しています。
構成を簡略化すると、次のようなイメージです。
XServer
└── honda-dev.com
├── .htaccess
│ └── 共通のHTTPセキュリティヘッダーを管理
├── portfolio.honda-dev.com
│ └── スクラッチPHP
├── Laravel用サブドメイン
│ └── Laravel
└── WordPress用サブドメイン
└── WordPress
※ 上記は今回の構成を説明するための簡略図です。サブドメインをそのままファイルシステム上の子ディレクトリとして表したものではありません。
この構成では、クリックジャッキング対策をスクラッチPHP、Laravel、WordPressそれぞれに個別実装することもできます。
しかし、同じ目的のHTTPセキュリティヘッダーを各アプリケーション側へ分散させると、設定漏れや二重管理が起きやすくなります。
そこで今回は、複数のサブドメインへ共通して適用したい設定を、大元ドメイン直下の .htaccess で一元管理する方針にしました。
もちろん、これはXServerやWebアプリケーション全般に共通する唯一の正解という意味ではありません。
.htaccess の適用範囲やDocumentRoot、アプリケーション構成によって適切な設定場所は変わるため、今回は自分のXServer構成に合わせた判断です。
どこで対策するか考えた
クリックジャッキング対策のHTTPレスポンスヘッダーを付与する場所はいくつか考えられます。
例えば、
- PHPの
header() - LaravelのMiddleware
- Apache / NginxなどWebサーバー側
です。
前節の構成を踏まえると、それぞれのアプリケーション側で同じHTTPセキュリティヘッダーを設定する方法もあります。
しかし今回の構成では、同じ目的の設定を各アプリ側へ分散させるより、共通して適用したい設定を大元ドメイン直下の .htaccess で管理する方が一元管理しやすいと判断しました。
今回の判断は「Webサーバー側で設定するのが常に正解」という意味ではありません。
アプリケーション構成やサーバー構成によって、
- アプリケーション側へ持たせる
- Webサーバー側へ持たせる
- リバースプロキシやCDN側へ持たせる
など、適切な場所は変わります。
今回のXServer構成では、サブドメインごとに複数のアプリを運用しているため、共通設定を .htaccess に寄せる方針を採用しました。
XServerでは .htaccess を利用できる
XServer公式マニュアルでは、.htaccess はWebサーバーの挙動を決定する設定ファイルであり、ディレクトリ単位でアクセス制限などを設定できると説明されています。
また、XServerはnginx環境下でも、Apache環境で設定された .htaccess を利用できると案内しています。
ただし今回調査した範囲では、XServer公式マニュアル上で mod_headers や Header ディレクティブが利用可能であることを明示した記述までは確認できませんでした。
そのため、
.htaccessに書けたから動くだろう
とは判断せず、実際のResponse Headersで確認することにしました。
.htaccess にクリックジャッキング対策を追加する
今回追加した設定は次のとおりです。
# ----------------------------------------------------------------------
# クリックジャッキング対策
# ----------------------------------------------------------------------
# iframe / frame 等への埋め込みを禁止する
#
# Content-Security-Policy:
# frame-ancestors 'none' により、すべてのサイトからの埋め込みを禁止
#
# X-Frame-Options:
# DENY により、旧来ブラウザ等も含めフレーム内表示を禁止
#
# ※ Apache mod_headers が有効な環境でのみ適用する
<IfModule mod_headers.c>
Header always set Content-Security-Policy "frame-ancestors 'none'"
Header always set X-Frame-Options "DENY"
</IfModule>
frame-ancestors 'none'
Content-Security-Policy: frame-ancestors 'none'
frame-ancestors は、ページを <frame>、<iframe>、<object>、<embed> などで埋め込める親を指定するCSPディレクティブです。
'none' を指定すると、埋め込みを許可しません。
なお、これは他サイトからの埋め込みだけではなく、同一オリジンからの埋め込みも禁止します。
X-Frame-Options: DENY
X-Frame-Options: DENY
DENY は、オリジンに関係なくフレーム内への表示を禁止します。
MDNでは、より柔軟な制御にはCSPの frame-ancestors を利用することが案内されています。
今回は frame-ancestors 'none' を中心としつつ、X-Frame-Options: DENY も併記しました。
ローカルApache環境で確認する
.htaccess を変更したあと、まずローカルのApache環境でResponse Headersを確認しました。
次の2つが返っていることを確認できました。
Content-Security-Policy: frame-ancestors 'none'
X-Frame-Options: DENY
既存の X-Frame-Options: SAMEORIGIN も DENY へ変わり、CSPの frame-ancestors 'none' も追加されています。
ここでまず、Apache側で今回の .htaccess 設定がResponse Headersへ反映されることを確認しました。
XServer本番環境でもResponse Headersを確認する
次にXServer本番環境へ反映し、Chrome DevToolsでResponse Headersを確認しました。
本番環境でも、
Content-Security-Policy: frame-ancestors 'none'
X-Frame-Options: DENY
が返っています。
本番環境のResponse Headersでは Server: nginx と表示されていますが、XServer公式マニュアルにもあるとおり、XServerではnginx環境下でもApacheの .htaccess を利用できます。
さらにCLIからも確認しました。
curl -I https://example.com/
実際の確認では、レスポンスに次のヘッダーが含まれていました。
HTTP/2 200
content-security-policy: frame-ancestors 'none'
x-frame-options: DENY
.htaccess に設定が存在するだけではなく、本番環境から実際に返されたHTTPレスポンスへ設定が反映されていることを確認できました。
対策後はiframeへの埋め込みが拒否された
最後に、先ほどと同じように検証用HTMLから本番サイトをiframeで読み込みました。
ブラウザには、
portfolio.honda-dev.com で接続が拒否されました。
と表示され、iframe内へサイトを表示できなくなりました。
本番環境での対策前後の検証結果をまとめると、次のようになります。
| 状態 | Response Headers | iframe表示 |
|---|---|---|
| 対策前 |
frame-ancestors 'none' / DENY なし |
表示できた |
| 対策後 |
frame-ancestors 'none' + X-Frame-Options: DENY
|
拒否された |
設定前後で実際のブラウザ動作まで比較できたため、クリックジャッキング対策が期待どおり有効になっていることを確認できました。
.htaccess に書いて終わりにしない
今回の検証で特に意識したのは、
設定ファイルに記述があることと、実際の本番レスポンスで有効になっていることは別
という点です。
.htaccess に設定を書いても、
- Webサーバー側で対象ディレクティブが利用できない
- 別階層の設定で上書きされる
- 想定したDocumentRootへ適用されていない
- CDNやリバースプロキシなど別レイヤーの影響を受ける
といった可能性があります。
そのため今回は、
-
.htaccessを設定 - ローカルのResponse Headersを確認
- XServer本番のResponse Headersを確認
-
curlでも確認 - iframeで実際の表示結果を確認
という順番で検証しました。
この考え方は、クリックジャッキング対策以外のHTTPセキュリティヘッダーでも同じだと思います。
今回の検証で学んだこと
今回のきっかけは応用情報技術者試験の勉強でした。
試験勉強では、
クリックジャッキングとは何か
どのような対策があるか
を覚えることが中心になります。
しかし、自分のサイトを実際に確認してみると、
- 自分のサイトでは本当に対策されているのか
- HTTPレスポンスのどこを見るのか
- PHP・Middleware・Webサーバーのどこへ設定するのか
- サーバーの仕様として本当に反映されるのか
- 対策前後でブラウザの挙動がどう変わるのか
まで考える必要がありました。
特に印象に残ったのは、「どこに設定を書くか」は技術だけではなくシステム構成で決まるという点です。
今回はXServer上で複数のアプリケーションをサブドメインごとに管理しているため、共通するHTTPセキュリティヘッダーを大元側の .htaccess で管理する方針にしました。
別の構成であれば、Laravel MiddlewareやWebサーバー設定、CDN側などへ寄せる判断もあり得ます。
まとめ
応用情報技術者試験の勉強をきっかけに、自分の公開サイトのクリックジャッキング対策を確認しました。
今回実施したのは、
- DevToolsでResponse Headersを確認
- iframeで対策前の状態を実証
- XServerの
.htaccessにCSPframe-ancestors 'none'を追加 -
X-Frame-Options: DENYを追加 - ローカルApache環境でResponse Headersを確認
- XServer本番環境でResponse Headersを確認
-
curlでも確認 - iframeで対策後に埋め込みが拒否されることを確認
です。
試験勉強で知った用語を覚えるだけでなく、
「自分の環境ではどうなっているのか?」
と実際に確認してみることで、理解がかなり深まりました。
また、セキュリティ設定は「設定ファイルへ書いたから対策済み」ではなく、実際のHTTPレスポンスとブラウザの動作まで確認することが重要だと改めて感じました。
参考資料
- MDN Web Docs: Clickjacking
- MDN Web Docs: Content-Security-Policy
frame-ancestors - MDN Web Docs: X-Frame-Options
- XServer マニュアル:
.htaccess
本記事は筆者自身の環境で行った検証結果をまとめたものです。
.htaccessの適用範囲や利用可能なディレクティブは、サーバー構成やサービスによって異なる場合があります。




