2022年4月25日

クリックジャッキング攻撃

「クリックジャッキング」攻撃は、悪意のあるページが訪問者に代わって「犠牲となるサイト」をクリックすることを可能にします。

Twitter、Facebook、Paypalなどの多くのサイトがこの方法でハッキングされました。もちろん、それらはすべて修正されています。

アイデア

アイデアは非常にシンプルです。

Facebookでのクリックジャッキングの実行方法

  1. 訪問者は悪意のあるページに誘い込まれます。どのように誘い込まれたかは問題ではありません。
  2. ページには、(「今すぐ金持ちになる」や「ここをクリック、とても面白い」のような)無害に見えるリンクがあります。
  3. 悪意のあるページは、そのリンクの上に、facebook.comからの`src`を持つ透明な`<iframe>`を、その「いいね」ボタンがリンクの真上になるように配置します。通常、これは`z-index`で行われます。
  4. リンクをクリックしようとすると、訪問者は実際にはボタンをクリックしています。

デモ

悪意のあるページがどのように見えるかは次のとおりです。分かりやすくするために、`<iframe>`は半透明です(実際の悪意のあるページでは完全に透明です)

<style>
iframe { /* iframe from the victim site */
  width: 400px;
  height: 100px;
  position: absolute;
  top:0; left:-20px;
  opacity: 0.5; /* in real opacity:0 */
  z-index: 1;
}
</style>

<div>Click to get rich now:</div>

<!-- The url from the victim site -->
<iframe src="/clickjacking/facebook.html"></iframe>

<button>Click here!</button>

<div>...And you're cool (I'm a cool hacker actually)!</div>

攻撃の完全なデモ

結果
facebook.html
index.html
<!DOCTYPE HTML>
<html>

<body style="margin:10px;padding:10px">

  <input type="button" onclick="alert('Like pressed on facebook.html!')" value="I LIKE IT !">

</body>

</html>
<!doctype html>
<html>

<head>
  <meta charset="UTF-8">
</head>

<body>

  <style>
    iframe {
      width: 400px;
      height: 100px;
      position: absolute;
      top: 5px;
      left: -14px;
      opacity: 0.5;
      z-index: 1;
    }
  </style>

  <div>Click to get rich now:</div>

  <!-- The url from the victim site -->
  <iframe src="facebook.html"></iframe>

  <button>Click here!</button>

  <div>...And you're cool (I'm a cool hacker actually)!</div>

</body>
</html>

ここでは、半透明の `<iframe src="facebook.html">` があり、例ではそれがボタンの上に重なっているのが確認できます。ボタンをクリックすると、実際にはiframeがクリックされますが、iframeが透明であるため、ユーザーには表示されません。

結果として、訪問者がFacebookで認証されている場合(「次回からログインを省略」が通常オンになっています)、それは「いいね」を追加します。Twitterでは、それは「フォロー」ボタンになるでしょう。

同じ例ですが、より現実に近い、`<iframe>`の`opacity:0`を使用した場合

結果
facebook.html
index.html
<!DOCTYPE HTML>
<html>

<body style="margin:10px;padding:10px">

  <input type="button" onclick="alert('Like pressed on facebook.html!')" value="I LIKE IT !">

</body>

</html>
<!doctype html>
<html>

<head>
  <meta charset="UTF-8">
</head>

<body>

  <style>
    iframe {
      width: 400px;
      height: 100px;
      position: absolute;
      top: 5px;
      left: -14px;
      opacity: 0;
      z-index: 1;
    }
  </style>

  <div>Click to get rich now:</div>

  <!-- The url from the victim site -->
  <iframe src="facebook.html"></iframe>

  <button>Click here!</button>

  <div>...And you're cool (I'm a cool hacker actually)!</div>

</body>
</html>

攻撃に必要なのは、ボタンがリンクの真上になるように、悪意のあるページに`<iframe>`を配置することだけです。そのため、ユーザーがリンクをクリックすると、実際にはボタンをクリックします。これは通常、CSSで実行可能です。

クリックジャッキングはクリック用であり、キーボード用ではない

攻撃は、マウス操作(またはモバイルでのタップのような類似の操作)にのみ影響します。

キーボード入力をリダイレクトするのははるかに困難です。技術的には、ハッキングするテキストフィールドがある場合、テキストフィールドが互いに重なり合うようにiframeを配置できます。そのため、訪問者がページに表示されている入力にフォーカスしようとすると、実際にはiframe内の入力にフォーカスします。

しかし、問題があります。訪問者が入力するものはすべて、iframeが表示されないため非表示になります。

人々は通常、画面に新しい文字が表示されない場合は入力を停止します。

旧式の防御策(弱い)

最も古い防御策は、ページをフレームで開くことを禁止する少しのJavaScriptです(いわゆる「フレームバスティング」)。

それはこのようになります

if (top != window) {
  top.location = window.location;
}

つまり、ウィンドウが最上位にないことを検出した場合、自動的に自身を最上位にします。

これは信頼できる防御策ではありません。なぜなら、それを回避する方法はたくさんあるからです。いくつか見ていきましょう。

トップナビゲーションのブロック

beforeunloadイベントハンドラーで `top.location` を変更することによって引き起こされる遷移をブロックできます。

トップページ(ハッカーに属する囲いページ)は、それを防止するハンドラーを次のように設定します

window.onbeforeunload = function() {
  return false;
};

`iframe` が `top.location` を変更しようとすると、訪問者は離れるかどうかを尋ねるメッセージを受け取ります。

ほとんどの場合、訪問者はiframeについて知らないため、否定的に答えるでしょう。彼らが見ることができるのはトップページのみであり、離れる理由はありません。したがって、`top.location` は変更されません!

実行中

結果
iframe.html
index.html
<!doctype html>
<html>

<head>
  <meta charset="UTF-8">
</head>

<body>

  <div>Changes top.location to javascript.info</div>

  <script>
    top.location = 'https://javascriptinfo.dokyumento.jp';
  </script>

</body>

</html>
<!doctype html>
<html>

<head>
  <meta charset="UTF-8">

  <style>
    iframe {
      width: 400px;
      height: 100px;
      position: absolute;
      top: 0;
      left: -20px;
      opacity: 0;
      z-index: 1;
    }
  </style>

  <script>
    function attack() {

      window.onbeforeunload = function() {
        window.onbeforeunload = null;
        return "Want to leave without learning all the secrets (he-he)?";
      };

      document.body.insertAdjacentHTML('beforeend', '<iframe src="iframe.html">');
    }
  </script>
</head>

<body>

  <p>After a click on the button the visitor gets a "strange" question about whether they want to leave.</p>

  <p>Probably they would respond "No", and the iframe protection is hacked.</p>

  <button onclick="attack()">Add a "protected" iframe</button>

</body>
</html>

サンドボックス属性

`sandbox` 属性によって制限されることの1つはナビゲーションです。サンドボックス化されたiframeは `top.location` を変更できません。

したがって、`sandbox="allow-scripts allow-forms"` を持つ iframe を追加できます。これにより、スクリプトとフォームが許可され、制限が緩和されます。ただし、`allow-top-navigation` は省略して、`top.location` の変更が禁止されるようにします。

これがコードです

<iframe sandbox="allow-scripts allow-forms" src="facebook.html"></iframe>

その単純な保護を回避する他の方法もあります。

X-Frame-Options

サーバー側のヘッダー `X-Frame-Options` は、ページをフレーム内に表示することを許可または禁止できます。

HTTPヘッダーとして正確に送信する必要があります。HTML `<meta>` タグで検出された場合、ブラウザはそれを無視します。したがって、`<meta http-equiv="X-Frame-Options"...>` は何も行いません。

ヘッダーは3つの値を持つことができます

DENY
決してページをフレーム内に表示しないでください。
SAMEORIGIN
親ドキュメントが同じオリジンから来た場合、フレーム内での表示を許可します。
ALLOW-FROM ドメイン
親ドキュメントが指定されたドメインからのものである場合、フレーム内での表示を許可します。

たとえば、Twitterは `X-Frame-Options: SAMEORIGIN` を使用しています。

これが結果です

<iframe src="https://twitter.com"></iframe>

ブラウザによっては、上記の `iframe` は空であるか、ブラウザがこの方法でページをナビゲートすることを許可しないことを警告しています。

機能を無効にして表示する

`X-Frame-Options` ヘッダーには副作用があります。他のサイトは、そうする正当な理由がある場合でも、フレーム内で私たちのページを表示することができなくなります。

したがって、他の解決策があります...たとえば、`height: 100%; width: 100%;` スタイルを持つ `<div>` でページを「カバー」して、すべてのクリックをインターセプトできます。`<div>` は `window == top` の場合、または保護が必要ないことが判明した場合に削除されます。

このようなものです

<style>
  #protector {
    height: 100%;
    width: 100%;
    position: absolute;
    left: 0;
    top: 0;
    z-index: 99999999;
  }
</style>

<div id="protector">
  <a href="/" target="_blank">Go to the site</a>
</div>

<script>
  // there will be an error if top window is from the different origin
  // but that's ok here
  if (top.document.domain == document.domain) {
    protector.remove();
  }
</script>

デモ

結果
iframe.html
index.html
<!doctype html>
<html>

<head>
  <meta charset="UTF-8">

  <style>
    #protector {
      height: 100%;
      width: 100%;
      position: absolute;
      left: 0;
      top: 0;
      z-index: 99999999;
    }
  </style>

</head>

<body>

<div id="protector">
  <a href="/" target="_blank">Go to the site</a>
</div>

<script>

  if (top.document.domain == document.domain) {
    protector.remove();
  }

</script>

  This text is always visible.

  But if the page was open inside a document from another domain, the div over it would prevent any actions.

  <button onclick="alert(1)">Click wouldn't work in that case</button>

</body>
</html>
<!doctype html>
<html>

<head>
  <meta charset="UTF-8">
</head>
<body>

  <iframe src="iframe.html"></iframe>

</body>
</html>

Samesite cookie 属性

`samesite` cookie 属性は、クリックジャッキング攻撃を防ぐこともできます。

そのような属性を持つCookieは、フレームなどを介してではなく、Webサイトが直接開かれた場合にのみ、Webサイトに送信されます。詳細については、Cookie, document.cookie の章を参照してください。

Facebookのようなサイトが、認証cookieに次のような `samesite` 属性を持っている場合

Set-Cookie: authorization=secret; samesite

…その場合、Facebookが別のサイトからのiframeで開かれたときに、そのようなcookieは送信されません。したがって、攻撃は失敗します。

`samesite` cookie 属性は、cookieが使用されていない場合、効果がありません。これにより、他のWebサイトは、iframeで公開されている認証されていないページを簡単に表示できるようになる場合があります。

ただし、これにより、いくつかの限られたケースでクリックジャッキング攻撃が機能する可能性もあります。たとえば、IPアドレスをチェックして重複投票を防ぐ匿名投票Webサイトは、cookieを使用してユーザーを認証しないため、クリックジャッキングに対して脆弱なままです。

概要

クリックジャッキングは、何が起こっているのかも知らずに、ユーザーを「だまして」犠牲となるサイトをクリックさせる方法です。クリックで起動する重要なアクションがある場合は危険です。

ハッカーは、メッセージで悪意のあるページへのリンクを投稿したり、他の手段で訪問者をページに誘い込んだりできます。多くのバリエーションがあります。

1つの視点からすると、攻撃は「深く」ありません。ハッカーが行っていることは、単一のクリックをインターセプトすることだけです。しかし別の視点から見ると、ハッカーがクリック後に別のコントロールが表示されることを知っている場合、巧妙なメッセージを使用してユーザーにそれらもクリックするように強制することができます。

UIを設計するときは、ハッカーが訪問者に代わってクリックする可能性を通常想定していないため、攻撃は非常に危険です。したがって、脆弱性は完全に予期しない場所で見つかる可能性があります。

  • フレーム内で表示することを意図していないページ(またはWebサイト全体)で、`X-Frame-Options: SAMEORIGIN` を使用することをお勧めします。
  • ページをiframeで表示することを許可したいが、安全な状態を保ちたい場合は、カバーする`<div>`を使用します。
チュートリアルマップ

コメント

コメントする前にこれを読んでください...
  • 改善すべき点があれば、コメントする代わりにGitHub issueを提出するか、プルリクエストを提出してください。
  • 記事の内容が理解できない場合は、詳しく説明してください。
  • 数単語のコードを挿入するには、`<code>` タグを使用し、複数行の場合は `<pre>` タグで囲み、10行以上の場合はサンドボックス(plnkrjsbincodepen…)を使用してください。