最近では、多くのWebセキュリティインシデントに自動化が関係しています。 Web スクレイピング、パスワードの再利用、クリック詐欺などの攻撃は、実際のユーザーを模倣しようとする攻撃者によって実行され、ブラウザーからアクセスしているように見せかけようとします。 ウェブサイトの所有者としては、人間にサービスを提供していることを保証したいと考えます。また、ウェブ サービス プロバイダーとしては、重くて安定性の低いウェブ インターフェースを介してコンテンツを取得するのではなく、API を介してプログラムによるコンテンツ アクセスを実行したいと考えています。
cURL のような訪問者に対する基本的なチェックが済んでいると仮定すると、次の合理的なステップは、訪問者がPhantomJSやSlimerJSなどのヘッドレス ブラウザーではなく、実際の UI 駆動型ブラウザーを使用していることを確認することです。
この記事では、PhantomJS による訪問を識別するためのいくつかの手法を紹介します。 最も人気のあるヘッドレス ブラウザー環境である PhantomJS に焦点を当てることにしましたが、ここで説明する概念の多くは SlimerJS やその他のツールにも適用できます。
注記: この記事で紹介するテクニックは、明示的に記載されていない限り、PhantomJS 1.x と 2.x の両方に適用できます。 まず、PhantomJS に応答せずに検出することは可能ですか?
ご存知のとおり、PhantomJS はQt フレームワーク上に構築されています。 Qt が HTTP スタックを実装する方法は、他の最新ブラウザとは一線を画しています。
まず、次のヘッダーを送信する Chrome を見てみましょう。
ただし、PhantomJS では、同じ HTTP リクエストは次のようになります。
PhantomJS ヘッダーは、Chrome (そして、実は他のすべての最新ブラウザ) とはいくつかの微妙な点で異なることに気づくでしょう。
サーバー上でこれらの HTTP ヘッダーの異常をチェックすると、PhantomJS ブラウザを識別できるはずです。
しかし、これらの値を信じるのは安全なのでしょうか? 攻撃者がプロキシを使用してヘッドレス ブラウザの前でヘッダーを書き換えると、それらのヘッダーを変更して、通常の最新ブラウザのように見えるようにすることができます。
この問題にサーバーだけで対処するのは、特効薬ではないようです。 それでは、PhantomJS の JavaScript 環境を使用してクライアントで何ができるかを見てみましょう。
HTTP 経由で配信される User-Agent 値を信頼できない可能性がありますが、クライアントではどうでしょうか?
残念ながら、PhantomJS で user-agent ヘッダーと navigator.userAgent 値を変更するのも同様に簡単なので、これだけでは不十分かもしれません。
navigator.plugins には、ブラウザ内に存在するプラグインの配列が含まれています。 一般的なプラグインの値には、Flash、ActiveX、Java アプレットのサポート、およびこのブラウザが OS X のデフォルト ブラウザであるかどうかを示すプラグインである「デフォルト ブラウザ ヘルパー」が含まれます。当社の調査によると、一般的なブラウザの新規インストールのほとんどには、モバイルであっても少なくとも 1 つのデフォルト プラグインが含まれています。
これは、プラグインを実装せず、プラグインを追加する方法も提供していない PhantomJS とは異なります ( PhantomJS APIを使用)。
次のチェックが役に立つかもしれません:
一方、ページが読み込まれる前にPhantomJS JavaScript 環境を変更することで、このプラグイン配列を偽装するのは非常に簡単です。
実際に実装されたプラグインを使用して PhantomJS のカスタム ビルドを想像することも難しくありません。 PhantomJS が構築されている Qt フレームワークは、プラグインを実装するためのネイティブ APIを提供するため、これは思ったより簡単です。
もう 1 つの興味深い点は、PhantomJS が JavaScript ダイアログを抑制する方法です。
数回測定した結果、警告ダイアログが 15 ミリ秒以内に抑制された場合、ブラウザは人間によって制御されていない可能性が高いことがわかりました。 しかし、このアプローチを使用すると、手動で閉じなければならないアラートが表示され、実際のユーザーに迷惑をかけることになります。
PhantomJS 1.x は、グローバル オブジェクトに 2 つのプロパティを公開します。
ただし、これらのプロパティは実験的な機能の一部であり、将来変更される可能性があります。
PhantomJS 1.x および 2.x は現在、古い WebKit エンジンを使用しているため、新しいブラウザーには存在するが PhantomJS には存在しないブラウザー機能があります。 これは JavaScript エンジンにも適用され、一部のネイティブ プロパティとメソッドは PhantomJS では異なっていたり、存在しなかったりします。
そのようなメソッドの 1 つが Function.prototype.bind ですが、これは PhantomJS 1.x 以前には存在しません。 次の例では、bind が存在するかどうか、および実行環境で偽装されていないかどうかを確認します。
この例では、カスタム indexOfString() 関数を使用していることに注意してください。これは、ネイティブの String.prototype.indexOf が PhantomJS によって偽装され、常に負の結果を返す可能性があるため、読者の演習として残されています。
さて、PhantomJS スクリプトでこのコードを評価するにはどうすればよいでしょうか? 1 つの手法は、呼び出される可能性が高い、頻繁に使用されるいくつかの DOM API 関数をオーバーライドすることです。 たとえば、以下のコードは document.querySelectorAll をオーバーライドしてブラウザのスタック トレースを検査します。
この記事では、サーバー上と PhantomJS のクライアント JavaScript 環境でコードを実行することによって PhantomJS を識別する 7 つの異なる手法について説明しました。 検出結果を強力なフィードバック メカニズム (たとえば、動的なページを不活性にレンダリングしたり、現在のセッション クッキーを無効にしたり) と組み合わせることで、PhantomJS 訪問者に対して強固なハードルを導入できます。 ただし、これらのテクニックは絶対確実ではなく、巧妙な敵は最終的には突破してしまうということを常に念頭に置いてください。
さらに詳しく知るには、 AppSec USA 2014 でのプレゼンテーションの録画 (スライド) を視聴することをお勧めします。 また、ここで紹介した手法の実装例と回避策をまとめたGitHub リポジトリも作成しました。
読んでいただきありがとうございます。良い狩りを。
セルゲイ・シェキアン– @sshekyan
ベン・ビネガー– @bentlegen
ベイ・チャン– @ikarienator