登録ページでヘッドレスブラウザと自動化を検出する方法
Puppeteer、Playwright、Seleniumによる登録を見破るブラウザのシグナル、それぞれが単独では偽装できる理由、そして本物の人をブロックせずに組み合わせる方法。
ボットが本物のブラウザを使うようになった理由
数年前まで、登録ボットの大半はフォームを直接送信する単純なHTTPスクリプトでした。これは簡単に止められます。JavaScriptを実行しないので、ページがブラウザ内で計算したものが何も届かないからです。今日、本格的なボットの多くはPuppeteer、Playwright、Seleniumで本物のブラウザを操作しており、ヘッドレスのことも、フィンガープリントを偽装するアンチディテクトブラウザを経由することもあります。彼らはJavaScriptを実行し、ページを描画し、人と同じようにフォームを埋めます。
それを見抜くには、自動化されたブラウザが人の使うブラウザと異なる点を探します。以下のシグナルはどれも単独では証拠になりません——本気の運用者なら一つずつ隠せます——が、収集コストは低く、積み重ねることができます。
シグナル1:WebDriverフラグ
WebDriver型の自動化下にあるブラウザは navigator.webdriver を true にします。これはWebDriver標準の意図的な仕様で、Selenium、Puppeteer、Playwrightのデフォルト設定はこれを公開します。
const automated = navigator.webdriver === true;
「stealth」系プラグインはこれを削除・上書きするので、ないことは何の証明にもなりません。しかしあれば、ほぼ決定的です。本物のユーザーにはまず付いていません。
シグナル2:ヘッドレスのユーザーエージェント
ヘッドレスChromeは、運用者が上書きしない限り、ユーザーエージェントで HeadlessChrome と名乗ります。JavaScriptで報告されるユーザーエージェントと実際に送信されるものは異なる場合があるので、サーバー側でもリクエストヘッダーから確認しましょう。
シグナル3:DevToolsプロトコルのクライアントが接続されている
PuppeteerとPlaywrightはDevToolsプロトコルでChromeを操作します。プロトコルのクライアントがコンソールイベントを監視していると、オブジェクトをログに出すだけでブラウザがそれをシリアライズし、通常のページなら決して触れないプロパティまで読み取ります。読まれたかどうかを記録するgetterは、これに気づくためのよく知られた方法です。
function devtoolsProtocolAttached() { let read = false; const e = new Error(); Object.defineProperty(e, "stack", { get() { read = true; return ""; } }); console.debug(e); return read; }
開発者がDevToolsを開いているときにも反応するため、判定ではなく中程度の重みとして扱いましょう。
シグナル4:ソフトウェアレンダリングと不自然なウィンドウ
サーバー上のヘッドレスブラウザにはGPUがないため、WebGLはSwiftShaderやllvmpipeのようなソフトウェアレンダラーにフォールバックします。ブラウザが公開していれば、WEBGL_debug_renderer_info 拡張でレンダラー名を読めます。幅や高さがゼロのウィンドウを報告するヘッドレス構成もあります。仮想マシンやリモートデスクトップも同じ特徴を持つので、単独では弱いシグナルです。
シグナル5:自己矛盾するフィンガープリント
アンチディテクトブラウザは、多数の異なる本物のブラウザに見せかけるために存在します。そのためにプロパティを上書きしますが、上書きが完全に一貫していることはまれです。比べる価値があるのは次の点です。
- ユーザーエージェントのOSと、
navigator.platformおよびUser-Agent Client Hints。 - ユーザーエージェントのブラウザの系統と、そのエンジンにしかない機能。
- インストール済みフォントと、そのOSに標準で入っているフォント。
- 続けて2回描いたcanvasが異なる2枚の画像になる——ノイズが注入されている兆候。
Notification.permissionが「denied」なのに、Permissions APIは「prompt」と答える。
プライバシー拡張や珍しい構成でも一部は起きるので、矛盾は単独では弱いものです。2つ3つ重なれば強いパターンになります。
シグナル6:戻ってこなかったトークン
シンプルで堅牢な手法です。登録ページのスクリプトに短命で使い捨てのトークンを要求させ、フォームの隠しフィールドに入れ、サーバーで検証します。JavaScriptを飛ばしたボットは有効なトークンなしで、古いトークンを使い回すボットは使用済みのトークンでやって来ます。ボットがどう動くかを知らなくても、ページを本当には実行していないことがわかります。トークンは使い捨てにし、自分のサイトに紐づけ、数分で失効させましょう。
シグナル7:指紋ではなく行動
指紋は偽装できますが、行動はもっと難しい。人はフォームを読み、ポインタを動かし、キーを打ちます。スクリプトはページ読み込みから数百ミリ秒でフィールドを埋めて送信し、信頼できる入力イベントを1つも発生させません。
確実な測定に追加のソフトは要りません。エージェントがページ読み込み時に訪問者トークンを発行し、サーバーがその時刻とチェックの瞬間を比べます。読み込みから登録まで約1秒未満ならスクリプトです。エージェントが信頼できる入力イベントも報告できる場合、ポインタ・キー・タッチが皆無の即時送信は、2つ目の独立した裏付けになります。
シグナル3への注意:現在のChromeでは、DevToolsのトリックは自動化クライアントがコンソールイベントを購読しているときだけ発火します。主要な検出手段ではなく、追加の材料として扱ってください。
組み合わせる。単一のシグナルでブロックしない
各シグナルをスコアの点数として扱い、合計で判定します。強いものから弱いものへ、最初の重み付けの目安です。
| シグナル | 重み | 理由 |
|---|---|---|
navigator.webdriver がtrue | 高 | 本物のユーザーではほぼtrueにならない |
| HeadlessChromeのユーザーエージェント | 高 | ヘッドレス版だけが送信する |
| トークンがない、または再利用 | 高 | ページのスクリプトが動いていない、または1回の実行で多数の登録 |
| DevToolsプロトコルが接続 | 中 | DevToolsを開いた開発者でも反応する |
| OSやブラウザの矛盾 | 中 | アンチディテクトの上書きだが、一部の拡張機能でも起きる |
| ソフトウェアレンダラー、ゼロサイズのウィンドウ | 低 | VMやリモートデスクトップにも見られる |
ブラウザのシグナルをサーバー側のシグナル——クラウドIP範囲からの登録、1つのサブネットからの集中、同じデバイスに結びついたアカウント——と組み合わせれば、すべてで同時にクリーンに見えるボットはごくわずかです。
やってはいけないこと
- プラグインや言語がないことだけでブロックしない。プライバシーを強化したブラウザはそれらを取り除きます。
- 検出ロジックを分かりやすい名前のチェックとして配布しない。運用者に検索されて一気に回避されます。ブラウザでは生の事実を集め、判定はサーバーで行いましょう。
- ブラウザの報告を鵜呑みにしない。クライアントはボットの支配下にあります。検証はあなたのサーバーで、あなたの秘密鍵を使って行います。
Attrelixの対応
Attrelixのエージェントは、登録ページでこれらのブラウザ情報と使い捨ての訪問者トークンを収集し、サーバーがそれをネットワーク、受信箱、リンクされたアカウントのシグナルと合わせて1つのスコアにします。すべての重みはドキュメントで公開しており、弱いシグナル1つだけで登録が確認に回ることはありません。
まとめ
- 現代のボットは本物のブラウザを使います。自動化が人と異なる点を探しましょう。
- WebDriver、ヘッドレスのユーザーエージェント、トークンの欠落は強く、残りは弱くても積み重なります。
- 判定はサーバーでスコアに基づいて行い、ブラウザのフラグ1つで決めないこと。