iframeで接続が拒否されました:X-Frame-Optionsの対処法

ポイント

iframe内の「接続が拒否されました」はネットワークエラーではありません。埋め込み先のサイトが、他のページ内での表示を禁止する X-Frame-Options または CSP の frame-ancestors ヘッダーを返しているのが原因です。直すのは iframe タグではなく、埋め込まれる側のサーバーです。サイト全体は SAMEORIGIN のままにして、埋め込み用のパス1つだけを frame-ancestors * で開放してください。

今すぐ確認

X-Frame-Options の解説図:iframe の「接続が拒否されました」と対処法(サイト全体は SAMEORIGIN、埋め込みパスだけ CSP frame-ancestors *)
X-Frame-Options の解説図:iframe の「接続が拒否されました」と対処法(サイト全体は SAMEORIGIN、埋め込みパスだけ CSP frame-ancestors *)
読み始める
  1. iframeの「接続が拒否されました」の本当の意味
  2. X-Frame-Options の仕組み
  3. DENY と SAMEORIGIN: どちらを送るべきか
  4. ALLOW-FROM は廃止済み: 使わないでください
  5. frame-ancestors: X-Frame-Options に代わる CSP ディレクティブ
  6. 両方のヘッダーを設定した場合の動作
  7. 手順1: ブロックしているヘッダーを特定する
  8. 手順2: 誰にフレーム表示を許可するかを決める
  9. 正しいパターン: サイトはロックし、埋め込みパスを1つだけ開ける
  10. Nginx(と add_header の落とし穴)
  11. Apache
  12. Next.js と Express
  13. サードパーティのウィジェットを安全に埋め込む
  14. sandbox: 電卓ウィジェットで許可すべきもの
  15. 埋め込む側で直せない理由
  16. 変更のたびにヘッダーを確認する
  17. 症状 → 原因 → 対処
  18. よくある質問

iframeの「接続が拒否されました」の本当の意味

別サイトを指す <iframe> を追加してリロードすると、ページの代わりに悲しい顔のアイコンが付いた灰色のボックスが表示されます。ホバーすると example.com で接続が拒否されました。 と出ます。ネットワークの問題を疑いたくなりますが、Chromeはサーバーが落ちているときも同じ表示になるためです。ところが DevTools のコンソールを開くと、実際の原因が分かります。

text
Refused to display 'https://emicalcs.com/' in a frame because it set 'X-Frame-Options' to 'sameorigin'.

ネットワークでは何も失敗していません。DNS は解決され、TCP 接続は確立し、TLS も完了し、サーバーは問題なく 200 を返しています。ブラウザはページを取得してレスポンスヘッダーを1つ読み、あなたのページ内に描画することを意図的に拒否しました。そのヘッダーが X-Frame-Options で、クリックジャッキング(悪意あるページがログインや決済のフォームを透明な iframe に読み込み、見えないボタンをクリックさせる攻撃)を防ぐためのものです。

この違いはトラブルシューティングに影響します。URL をタブで直接開いたときに ERR_CONNECTION_REFUSED と出る本物の接続失敗は、サーバーやファイアウォールの問題で、ERR_CONNECTION_REFUSED で解説しています。iframe 版はページがフレーム内に置かれたときだけ発生し、同じ URL は単独のタブでは問題なく開けます。直接は開けるのにフレームでは開けない場合、原因は障害ではなくフレーム表示のポリシーです。

ページの X-Frame-Options と CSP ヘッダーを確認

X-Frame-Options の仕組み

X-Frame-Options は HTTP レスポンスヘッダーで、最初に RFC 7034 で定義されました。この RFC は参考情報で、現在は HTML 標準 がその役割を引き継いでいます。Internet Explorer 8 以降のすべてのブラウザが対応しています。サーバーがページにこのヘッダーを付け、ブラウザは描画前にそれを読んで、そのページを別ページの <frame>、<iframe>、<embed>、<object> の中に表示してよいかを判断します。

現在も意味があるのは2つの値で、もう1つは無効です。

値フレーム表示できるのは主な用途
DENY誰も不可(同じサイトも不可)ログインページ、管理画面、セッションを持つページ全般
SAMEORIGINまったく同じオリジン(スキーム + ホスト + ポート)のページだけ多くのサイトに適した標準設定。自サイトのページはフレームに入れられる
ALLOW-FROM https://a.com廃止。現行のブラウザはすべて無視する使わない。詳細は後述

このヘッダーはサイト単位ではなくレスポンス単位で適用されます。多くの解説が触れていない点ですが、本記事の解決策はここに支えられています。サーバーはページの 99% に SAMEORIGIN を返しつつ、埋め込み用の1つのパスだけに別のポリシーを返せます。

DENY と SAMEORIGIN: どちらを送るべきか

特別な理由がなければ SAMEORIGIN を送ります。第三者のフレームはすべてブロックしつつ、ヘルプウィジェット、プレビュー、他の社内ページを読み込むダッシュボードなど、自サイト内でのフレーム利用は維持できます。

DENY は、フレーム表示に正当な用途がなく、悪用されたときの被害が大きいページに送ります。サインインフォーム、決済ページ、「アカウント削除」のようなワンクリック操作が該当します。DENY は、攻撃者が自サイトのページにフレームを挿入できてしまうケースも防ぎます。SAMEORIGIN だと、その挿入されたフレームは許可されたままです。

どちらを選ぶ場合も、Web サーバーか CDN で1回だけ設定して、エラーページや静的ファイルを含むすべてのレスポンスに付けましょう。アプリケーションコード内で設定したヘッダーは、404 ページやメンテナンスページで漏れがちです。すべてのレスポンスが通る場所で設定してください。

ALLOW-FROM は廃止済み: 使わないでください

古い記事では、1つの埋め込み元だけを許可する方法として X-Frame-Options: ALLOW-FROM https://partner.com が今も紹介されています。Chrome と Safari では一度も動作せず、Firefox もバージョン 70(2019年10月)で削除しました。さらに、モダンブラウザは ALLOW-FROM を見るとヘッダー全体を無効とみなして完全に無視するため、ページは誰でもフレームに入れられる状態になります。意図とは正反対です。

特定のサイトだけを許可したい場合の答えは CSP の frame-ancestors で、次の章で説明します。X-Frame-Options が表現できるのは「誰も不可」か「自分だけ」のどちらかです。

frame-ancestors: X-Frame-Options に代わる CSP ディレクティブ

Content Security Policy Level 2 で導入された frame-ancestors は、X-Frame-Options の機能をすべて備えたうえで、本物の許可リストという独自の機能を追加します。標準の Content-Security-Policy ヘッダーの中に記述し、1つ以上のソースを指定できます。

http
# Nobody may frame this (same as X-Frame-Options: DENY)
Content-Security-Policy: frame-ancestors 'none'

# Only my own origin (same as SAMEORIGIN)
Content-Security-Policy: frame-ancestors 'self'

# My origin plus two named partners, one with a subdomain wildcard
Content-Security-Policy: frame-ancestors 'self' https://partner.example https://*.trusted.example

# Anyone may frame this — for a public embed endpoint only
Content-Security-Policy: frame-ancestors *

切り替える前に押さえておきたい点が3つあります。

  • 親だけでなく、すべての祖先を確認します。 ページ A がページ B をフレームに入れ、B があなたのページをフレームに入れている場合、A と B の両方が許可されている必要があります(CSP)。HTML 標準では X-Frame-Options もすべての祖先に適用されるようになりました(WHATWG)が、古いブラウザは親か最上位ページだけを確認していました。
  • HTTP ヘッダーとしてのみ有効です。 CSP を <meta http-equiv> タグで配信した場合、frame-ancestors は明示的に無視されます。ブラウザは、ドキュメントを描画するかどうかを決める前にポリシーを知っている必要があるためです。X-Frame-Options も同じで、どちらも HTML からは設定できません。
  • ワイルドカードはホスト単位で、全体には効きません。 https://*.example.com は1つのサイトの任意のサブドメインに一致します。単独の * はインターネット上のすべてのオリジンに一致します。電卓ウィジェットには問題ありませんが、セッションを持つページには不適切です。

両方のヘッダーを設定した場合の動作

移行期間中は多くのサイトが両方を送っており、優先順位は仕様で決まっています。frame-ancestors があれば、ブラウザはそちらを適用し、X-Frame-Options を無視します。 CSP Level 2 の仕様に明記されており、Chrome、Firefox、Safari、Edge はすべてこれに従います。

そのため安全な移行手順は簡単です。CSP 2 以前の古いクライアント向けに X-Frame-Options: SAMEORIGIN を残し、あわせて Content-Security-Policy: frame-ancestors 'self' を追加すれば、モダンなブラウザはすべて CSP のルールを使います。問題になるのは、2つのヘッダーが食い違う場合だけです。たとえば X-Frame-Options: DENY と frame-ancestors * を並べると、モダンブラウザはフレーム表示を許可し、ごく古いブラウザだけがブロックします。内容を揃えておけば、デバッグすることは何もありません。

手順1: ブロックしているヘッダーを特定する

何かを変更する前に、どのポリシーが効いているかを確認します。ヘッダー名はブラウザのコンソールがすでに示しており、curl を使えばサーバーが送っている正確な値が分かります。この例では、後述の実例のサイトを使います。メインのページは意図的にロックされています。

bash
curl -sI https://emicalcs.com/ | grep -i -E 'x-frame-options|content-security-policy'

# Output:
# x-frame-options: SAMEORIGIN

結果は次の3つのいずれかです。

  • x-frame-options: SAMEORIGIN または DENY: 従来のフレーム表示ポリシーです。解除できるのは、そのサーバー側の変更だけです。
  • content-security-policy: … frame-ancestors …: CSP のポリシーです。ソースの一覧を読み、自分のオリジンが含まれていなければブロックされます。このディレクティブは script-src などと並ぶ長い CSP ヘッダーに埋もれていることが多いので、値全体を読んでください。
  • どちらのヘッダーもない: 埋め込み先はフレーム表示を許可しており、「接続が拒否されました」の原因は別にあります。ロックされたページ(多くはサインインページ。curl -sIL で追跡)へのリダイレクトか、自分のページに付けた Cross-Origin-Embedder-Policy: require-corp ヘッダーです。後者は、オプトインしていないフレームをすべてブロックし、同じ灰色のボックスを表示します。

手順2: 誰にフレーム表示を許可するかを決める

ここから先は、1つの問いで決まります。埋め込まれる側のサイトを管理しているか?

管理していない場合は、ここで終わりです。クライアント側の回避策はありません。sandbox 属性も、別の src も、訪問者が持っているブラウザのフラグも役に立ちません。サイトの運営者がそのページを埋め込ませないと決め、ブラウザがその決定を代わりに実行しているだけです。選択肢は、そのページにリンクする、埋め込み用エンドポイントがあるか運営者に尋ねる、公式ウィジェットがあればそれを使う、のいずれかです。メインサイトをロックしている多くのサービスは、公式ウィジェットを提供しています。次の章の例がその姿です。

管理している場合も、ヘッダーをサイト全体から外したくなる誘惑は抑えてください。1つの埋め込みを直すために、セッションやフォームのあるページを含むすべてのページでクリックジャッキングを再び許すことになります。代わりに、どのパスをフレーム表示してよいか、誰に許可するかを決めます。

状況そのパスに送るヘッダー
自社のダッシュボードにフレームで入れる社内ツールframe-ancestors 'self'(X-Frame-Options: SAMEORIGIN も併用)
特定のパートナーが埋め込むウィジェットframe-ancestors 'self' https://partner-a.com https://partner-b.com
公開用の埋め込みウィジェット(電卓、地図、プレーヤー)埋め込みパスだけに frame-ancestors *
それ以外すべてframe-ancestors 'self' と X-Frame-Options: SAMEORIGIN

正しいパターン: サイトはロックし、埋め込みパスを1つだけ開ける

このパターンの実例として最もきれいなものが、他のサイトに埋め込みを推奨している、インドの金融電卓45種(ローン EMI、SIP、所得税、GST など)の無料コレクション EMICalcs です。メインサイトはロックされ、専用の /embed/ パスだけが開いています。両方のヘッダーを比べてみましょう。

bash
# The calculator page a visitor normally uses — locked
curl -sI https://emicalcs.com/income-tax-calculator/ | grep -i -E 'x-frame|frame-ancestors'
# x-frame-options: SAMEORIGIN

# The same calculator on its embed path — open to any site
curl -sI https://emicalcs.com/embed/income-tax-calculator/ | grep -i -E 'x-frame|frame-ancestors'
# content-security-policy: frame-ancestors *

埋め込みのレスポンスに含まれていないものに注目してください。/embed/ には X-Frame-Options ヘッダーがまったくありません。これは意図的です。CSP のディレクティブが優先されるため、SAMEORIGIN を残してもモダンブラウザでは害はありませんが、次にヘッダーを読むエンジニアを混乱させますし、X-Frame-Options しか理解しない古いクライアントのブロックも続いてしまいます。埋め込みパスは1つのポリシーだけを送り、その内容も1つだけです。

サイトのそれ以外の部分は SAMEORIGIN のままなので、トップページ、記事ページ、フル機能の電卓ページはクリックジャッキングから保護されます。埋め込み版はナビゲーションがなく、乗っ取る価値のあるものもない簡素なドキュメントです。だからこそ * に開放するのは妥当な割り切りになります。

自分でウィジェットを公開するなら、同サイトの埋め込みガイドはテンプレートとして参考になります。電卓ごとに安定した URL を1つ、ほとんどに収まる固定の高さ、スニペットの loading="lazy"、そして廃止した埋め込み URL は 404 ではなくリダイレクトするという約束です。埋め込んでくれるサイトはすべて、あなたの URL が動き続けることを信頼しているからです。

Nginx(と add_header の落とし穴)

同じパターンを、ヘッダーを設定する代表的な3か所で示します。いずれも、サイト全体のルールですべてをロックし、より狭いルールで埋め込みパスだけ上書きします。まず落とし穴のある Nginx からです。

nginx
# Site-wide default (server block)
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Content-Security-Policy "frame-ancestors 'self'" always;
add_header X-Content-Type-Options "nosniff" always;

location /embed/ {
    # WARNING: any add_header here stops ALL add_header inheritance from the
    # server block — re-declare every header you still want on this path.
    add_header Content-Security-Policy "frame-ancestors *" always;
    add_header X-Content-Type-Options "nosniff" always;
}

コメントは必ず読んでください。add_header はレベルをまたいでマージされません。location ブロックが add_header を1つでも宣言した時点で、server ブロックの add_header はすべて継承されなくなります。このせいで埋め込みパスの HSTS や nosniff が外れ、数か月後のセキュリティスキャンで初めて気づくことがよくあります。必要なものは再宣言し、埋め込みブロックには X-Frame-Options を入れないで、そのパスが明確な1つのポリシーを送るようにしましょう。Nginx 1.29.3 以降は add_header_inherit merge で継承を戻せます(nginx)が、埋め込みブロックでは使わないでください。サイト全体の X-Frame-Options と frame-ancestors 'self' がそのパスに戻り、CSP ヘッダーが2つあるとブラウザは両方を適用するため、フレームはブロックされたままになります。

Apache

Apache の Header ディレクティブはスコープをまたいでマージされるので、既定値を1回設定し、埋め込みパスで変わる2つのヘッダーだけを変更すれば済みます。

apache
# Site-wide default (needs mod_headers)
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Content-Security-Policy "frame-ancestors 'self'"

<LocationMatch "^/embed/">
    Header always unset X-Frame-Options
    Header always set Content-Security-Policy "frame-ancestors *"
</LocationMatch>

Next.js と Express

Next.js ではヘッダーを next.config.js で宣言します。2つのルールが同じパスに一致して同じヘッダーを設定した場合は後のルールが優先されますが、先のルールだけが設定したヘッダーはそのまま適用されます。埋め込みパスから X-Frame-Options を完全に外すには、サイト全体のルールから否定先読みで除外します。

javascript
// next.config.js
module.exports = {
  async headers() {
    return [
      {
        // Every path EXCEPT /embed/... gets the locked defaults
        source: '/((?!embed/).*)',
        headers: [
          { key: 'X-Frame-Options', value: 'SAMEORIGIN' },
          { key: 'Content-Security-Policy', value: "frame-ancestors 'self'" },
        ],
      },
      {
        source: '/embed/:path*',
        headers: [
          { key: 'Content-Security-Policy', value: 'frame-ancestors *' },
        ],
      },
    ]
  },
}

Express では、定番の helmet ミドルウェアが既定で X-Frame-Options: SAMEORIGIN と、frame-ancestors 'self' を含む CSP を送ります。全体にマウントしたうえで、埋め込み用ルーターでは res.removeHeader('X-Frame-Options') を呼び、描画前に Content-Security-Policy を frame-ancestors * に設定します。順序が重要です。上書きは helmet より後で実行しないと、helmet に上書きされてしまいます。

サードパーティのウィジェットを安全に埋め込む

ここからは立場が逆になります。あなたは埋め込む側のサイトで、提供元が開放済みの埋め込みパスを用意していて、フレームをきちんと動かしたい場合です。うまく作ったウィジェットのスニペットは次のようになります。EMICalcs が各電卓ページで公開しているものです。

html
<iframe src="https://emicalcs.com/embed/income-tax-calculator/"
        title="Income Tax Calculator FY 2026-27 — EMICalcs"
        width="100%" height="760"
        style="max-width:680px;border:0;border-radius:16px;overflow:hidden"
        loading="lazy"></iframe>
<p>Free <a href="https://emicalcs.com/income-tax-calculator/" target="_blank" rel="noopener">Income Tax Calculator FY 2026-27</a> by EMICalcs</p>

各属性にはそれぞれ役割があります。

  • title: スクリーンリーダー向けにフレームの名前を付けます。ないと支援技術は「フレーム」とだけ読み上げ、アクセシビリティ監査でページが不合格になります。
  • width="100%" と style の max-width: モバイルの狭い列でもいっぱいに広がり、デスクトップの広い列ではあふれません。
  • 固定の height: 埋め込む側のページはクロスオリジンのフレームのコンテンツの高さを読めないため、読み込み中のレイアウトシフトを避けるにはハードコードした値しかありません。ウィジェットごとに1回設定します。
  • loading="lazy": フレームがビューポート付近にスクロールされるまでリクエストを遅らせるため、長い記事の最下部にあるウィジェットは初回読み込みの負荷になりません。
  • border:0; border-radius; overflow:hidden: 既定のインセット枠線を消し、角を切り取って、ウィジェットを自分のページになじませます。

クレジット表記は意図的にiframeの外に置いています。フレーム内のものはすべて、埋め込まれたドキュメント側のものです。リンクも提供元の URL 上の提供元のリンクであり、読者や検索エンジンのクローラーから見れば、フレーム内のリンクはあなたのページの一部ではありません。出典を示したい場合や、提供元から求められた場合は、上のようにフレームの横に、自分の HTML で普通の段落を置いてください。

sandbox: 電卓ウィジェットで許可すべきもの

sandbox 属性は、埋め込む側が自分で行う防御です。値なしで追加すると、埋め込まれたページは何もできなくなります。スクリプトもフォームもポップアップも、あなたのウィンドウの遷移も不可で、独自の Cookie やストレージも無効になる不透明なオリジンになります。そのうえで、ウィジェットに必要なものだけを戻します。

html
<iframe src="https://emicalcs.com/embed/loan-emi-calculator/"
        sandbox="allow-scripts allow-forms allow-same-origin allow-popups"
        title="Loan EMI Calculator — EMICalcs"
        width="100%" height="760" loading="lazy"></iframe>

allow-scripts と allow-forms は、インタラクティブなものに最低限必要です。allow-same-origin は、埋め込まれたページが自分のオリジンを保持して、ストレージと Cookie を使えるようにします。クロスオリジンの埋め込みでは安全で、「そのオリジン」はあくまで提供元のもので、あなたのものではないからです。allow-popups は、ウィジェットがリンクを新しいタブで開く場合にのみ必要で、EMICalcs の埋め込み内のクレジットリンクがそれに当たります。

やってはいけないのは、自分自身のオリジンを指すフレームで allow-scripts と allow-same-origin を組み合わせることです。フレーム内のスクリプトが自分の sandbox 属性を外せてしまい、何もサンドボックス化していないことになります。

埋め込む側で直せない理由

「iframe 接続が拒否されました」で検索すると、よく見かける回避策が3つ出てきます。どれも良くない方法で、理由を知っておくと役に立ちます。

  • X-Frame-Options を削除するブラウザ拡張機能: 自分のブラウザでしか動きません。サイトを訪れる人にはやはり灰色のボックスが表示されますし、その間、自分が訪れるすべてのサイトでクリックジャッキング対策を切っていることになります。
  • 自分のサーバー経由でページをプロキシする: 第三者の HTML をサーバー側で取得して自分のオリジンから配信するので、ヘッダーはブラウザに届きません。ただし相対リンク、Cookie、ログイン、スクリプトで動く機能が壊れ、相手のコンテンツの責任を負うことになり、たいてい提供元の規約にも違反します。見つかったサイトはあなたのサーバーの IP をブロックします。
  • スクリーンショットや「Web to image」サービス: 得られるのは動くページではなく静止画です。プレビューのサムネイルならよいのですが、電卓には使えません。

このヘッダーは、サイト運営者が自分のページに何が起きてよいかを表明したもので、ブラウザがそれを実行します。正当な方法は、すでに触れた2つだけです。運営者が埋め込みパスを開けるか、あなたがリンクを張るかです。埋め込んでほしい提供元は、それを簡単にしてくれます。良い提供元はスニペットを公開し、URL を安定させ、開けて安全なパスだけを開放しています。

変更のたびにヘッダーを確認する

フレーム関連のヘッダーは設定ファイルに書いたきり忘れられがちで、CDN、リバースプロキシ、善意のセキュリティプラグインが、いつでも追加、削除、重複させる可能性があります。デプロイ後は、自分のネットワークの外から確認しましょう。

bash
# The locked pages should show SAMEORIGIN and/or frame-ancestors 'self'
curl -sI https://example.com/ | grep -i -E 'x-frame-options|content-security-policy'

# The embed path should show ONLY frame-ancestors, with the sources you intended
curl -sI https://example.com/embed/widget/ | grep -i -E 'x-frame-options|content-security-policy'

# Error pages should carry the header too
curl -sI https://example.com/this-does-not-exist | grep -i -E 'x-frame-options|content-security-policy'

次に、ブラウザと同じ方法でテストします。別のオリジンから配信されるページの <iframe> に埋め込み URL を入れ、コンソールを開いてください。ローカルの file:// ページは、ブラウザが特別扱いするので適しません。正常に読み込まれれば、Refused to display や Refused to frame の行は出ません。

サイトが Cloudflare などの CDN の背後にある場合、キャッシュやエッジルールが、オリジンとは異なるヘッダーを返すことがあります。HTTPヘッダーチェッカー は、ブラウザやネットワークの外にある当サイトのサーバーから URL にリクエストし、X-Frame-Options と Content-Security-Policy を、フレーム表示の判定(SAMEORIGIN または DENY、CSP の frame-ancestors 経由、未送信)とともに表示します。CDN のルールが埋め込みパスに SAMEORIGIN をこっそり追加し直していないかを確かめるのに便利です。

症状 → 原因 → 対処

ガイド全体を読み直さなくても、ここから対処に直行できます。

見えるもの意味対処
灰色のボックスに「接続が拒否されました」。ただし URL はタブでは開けるX-Frame-Options または frame-ancestors がフレームをブロックした変更できるのはサイト運営者だけ。運営者の埋め込みパスを使うか、リンクにする
コンソール: Refused to display … 'X-Frame-Options' to 'sameorigin'従来のヘッダー。同一オリジンのフレームだけ許可埋め込まれる側のサイトで、埋め込みパスを frame-ancestors * かパートナー一覧で開放する
コンソール: Refused to frame … violates … frame-ancestorsCSP の許可リストに自分のオリジンがない埋め込まれる側のサイトの frame-ancestors のソースに自分のオリジンを追加する
昨日は動いたのに今日は動かない。コードは変えていないCDN やセキュリティプラグインが SAMEORIGIN を挿入し始めたcurl で確認し、CDN やプラグインのルールから埋め込みパスを除外する
自分のダッシュボードが自分のページをフレームに入れられないサブドメインかスキームの不一致(www と apex、http と https)frame-ancestors 'self' https://example.com https://*.example.com を使う(ワイルドカードは裸のドメインを含まない)
埋め込みは動くが、そのパスの HSTS や nosniff が消えたNginx の add_header の継承で外れたlocation ブロック内でヘッダーをすべて再宣言する
URL を直接開くと ERR_CONNECTION_REFUSEDフレーム表示のポリシーではなく、本物のネットワーク障害ERR_CONNECTION_REFUSED とポートチェックを参照

URL が単独のタブでも失敗する場合、フレームは見当違いです。フレーム関連のヘッダーを触る前に、公開エンドポイントのテストガイドで、DNS、ポート、TLS、ヘッダーの順にネットワークの各層を確認してください。

URLが返すセキュリティヘッダーをすべて確認

任意のページの X-Frame-Options、Content-Security-Policy、HSTS などを当サイトのサーバーから取得して確認できます。ブラウザ拡張機能や手元のネットワークのプロキシの影響を受けません。無料、登録不要。

出典

  1. RFC 7034: HTTP Header Field X-Frame-Options, IETF
  2. CSP Level 3: frame-ancestors, W3C
  3. HTML Standard: the X-Frame-Options header, WHATWG
  4. ngx_http_headers_module (add_header, add_header_inherit), nginx

よくある質問

X-Frame-Options は廃止されたのですか?

機能面では CSP の frame-ancestors ディレクティブに置き換えられており、両方のヘッダーがある場合、ブラウザはそちらを優先します。ただし無意味ではありません。現行のすべてのブラウザは、frame-ancestors がなければ X-Frame-Options を今も適用しますし、CSP Level 2 以前の古いクライアントも保護できます。実用上は、両方を送り、内容を揃えておくことをおすすめします。frame-ancestors 'self' とあわせて SAMEORIGIN を送る形です。

X-Frame-Options: DENY とはどういう意味ですか?

DENY は、そのページを、同じサイト内のページを含め、どのページのフレーム、iframe、embed、object 要素の中にも表示してはならない、とブラウザに伝えます。最も厳格な設定で、ログインフォーム、決済ページ、管理画面のように、埋め込まれる正当な理由がなく、クリックジャッキングの被害が大きいページに適しています。

DENY と SAMEORIGIN の違いは何ですか?

DENY はあらゆるフレーム表示をブロックします。SAMEORIGIN は、スキーム、ホスト、ポートがまったく同じオリジンのページによるフレーム表示だけを許可し、それ以外はブロックします。SAMEORIGIN は、第三者によるクリックジャッキングを防ぎつつ自サイトのページを自サイトで表示できるため、サイト全体の標準設定として妥当です。DENY は、フレーム表示を一切許したくない一部のページ向けです。

X-Frame-Options を回避してサイトを埋め込むには?

埋め込む側からは、できません。ヘッダーは埋め込もうとしているサイトが設定し、訪問者のブラウザが適用するため、iframe の属性も sandbox のフラグもクライアント側のテクニックも、これを変えられません。ヘッダーを削除する拡張機能は自分のブラウザにしか効かず、自分のサーバー経由でページをプロキシすると壊れるうえ、たいてい提供元の規約にも違反します。正当な方法は、提供元が公式の埋め込みエンドポイントを用意していればそれを使うか、ページにリンクすることです。

X-Frame-Options を DENY に設定するには?

Web サーバーのレベルで HTTP レスポンスヘッダーとして追加し、エラーページを含むすべてのページに適用します。Nginx では add_header X-Frame-Options "DENY" always; 、Apache では Header always set X-Frame-Options "DENY" です。モダンブラウザ向けに Content-Security-Policy: frame-ancestors 'none' も併用してください。meta タグでは設定できず、HTTP ヘッダーだけが有効です。

サイトは新しいタブでは開けるのに、iframe で「接続が拒否されました」と出るのはなぜですか?

接続自体には問題がないからです。サイトはページを通常どおり返していますが、ほかのページ内に表示することを禁じる X-Frame-Options または frame-ancestors ヘッダーが付いています。Chrome はこの場合も、本物のネットワーク障害のときと同じ灰色の「接続が拒否されました」ボックスを表示するため、紛らわしくなります。コンソールの「Refused to display … in a frame」というメッセージが決め手です。

X-Frame-Options や frame-ancestors を meta タグで設定できますか?

できません。どちらも HTTP レスポンスヘッダーとして配信する必要があります。ブラウザはドキュメントを描画するかどうかを決める前にフレーム表示のポリシーを知っている必要があり、meta タグでは間に合いません。frame-ancestors は meta http-equiv の CSP では明示的に無視され、meta タグの X-Frame-Options はどのブラウザにも無視されます。

frame-ancestors * を指定するとサイトが危険になりますか?

このヘッダーを送るレスポンスに限った話です。ヘッダーはドキュメントごとに評価されるので、専用の /embed/ パスだけに frame-ancestors * を送れば、サイトのそれ以外の部分は SAMEORIGIN か frame-ancestors 'self' で保護されたままです。リスクは、攻撃者が埋め込みページそのものをフレームに入れて得られるものに限られ、セッション Cookie も重要な操作もないステートレスなウィジェットなら、何も得られません。

X-Frame-Options: ALLOW-FROM が動かないのはなぜですか?

ALLOW-FROM を実装していたのは Internet Explorer、旧 Edge(バージョン 12〜18)、Firefox だけで、Firefox もバージョン 70 で削除しました。Chrome と Safari は一度も対応していません。モダンブラウザは ALLOW-FROM を含むヘッダーを無効とみなして完全に無視するため、ページは誰でもフレームに入れられる状態になります。許可するオリジンの一覧を指定できる CSP の frame-ancestors ディレクティブを使ってください。

X-Frame-Options と frame-ancestors の両方がある場合、どちらが優先されますか?

frame-ancestors です。HTML 標準は、frame-ancestors を含む強制適用の CSP がある場合に X-Frame-Options を無視するようブラウザに求めており(CSP Level 2 も推奨していました)、現行のすべてのブラウザがそうしています。Report-Only のポリシーは対象外です。CSP を理解できないほど古いクライアントだけが X-Frame-Options にフォールバックするため、2つのヘッダーが一致している限り、送っておく価値があります。

著者について

Shaik Vahid

DNS Robot 創設者. Vahid は長年 Web ホスティングのサポートで、お客様のドメイン、DNS レコード、SSL 証明書、メール配信の問題を解決してきました。DNS Robot のツールを開発し、実際によく起きるエラーをもとにこれらのガイドを書いています。

HTTPヘッダーチェッカー

HTTPレスポンスヘッダーとセキュリティヘッダーのグレーディング分析