asyncfix

お問い合わせフォームのreCAPTCHA等によるbot対策テクニック

Webサイトでお問い合わせフォームを公開するとbot等のツールを使われて、スパムや大量自動送信の嫌がらせをされるケースがあります。そういった際に導入を考えるのが、reCAPTCHA等のbot対策ツールになります。

Googleが提供しているreCAPTCHA

Googleが提供しているreCAPTCHAは、多くのサイトで目にすると思います。
現状、reCAPTCHAにはv2とv3があり、v2はその中でもいくつか種類が分かれています。

reCAPTCHA v2

Invisible reCAPTCHA以外のv2は下記のような「私はロボットではありません」にチェックを入れると、

reCAPTCHA v2 - "I'm not a robot"

下記のような画像選択ウィンドウが出てきたりします。

reCAPTCHA v2 image tile

これは、信号機や自転車、消火栓など、写っている画像タイルを選択して送信させるもので、それによって人間だと判断するものになっています。

ここで人間が選択した画像はGoogle側で画像検索の精度向上や、AI学習用の教師データ(データラベリング)としても使われていると推測されます。

尚、Invisible reCAPTCHAの場合は、「私はロボットではありません」のチェックボックスが無い代わりに(ページ右下にロゴは出る。)、botと疑われる怪しい挙動を検知すると初めて画像選択ウィンドウが表示される仕様となっています。

reCAPTCHA v3

v3の場合は、そもそも画像選択ウィンドウの仕様はなくなっており(v3もページ右下にロゴが出ますが。)、おそらくページを表示してからフォームへの入力~送信までのキーボードやマウス操作等の挙動データをGoogle側で分析して人間 or botを1.0~0の範囲でスコアリングして返却する仕様となっています。

つまり、v2ではGoogle側でbot判別まで行ってくれていましたが、v3ではGoogleはスコアリングだけ行うのでそれ以降は自分でどうするかを判断・実装しなければなりません(自由度が高くなったとも言える。)。
必ずしもv3がv2よりも優れているということではなく、そもそもの設計思想が異なるということですね。

必要最小限の簡易な実装サンプル(by ChatGPT)としては、下記のような感じになります。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <script src="https://www.google.com/recaptcha/api.js?render=●●●"></script>
  <script>
    function onSubmit(e) {
      e.preventDefault()
      grecaptcha.ready(function () {
        grecaptcha.execute('●●●', {
            action: 'submit',
          })
          .then(function (token) {
            var recaptchaResponse = document.getElementById('recaptchaResponse');
            recaptchaResponse.value = token;
            // トークンを設定した後にフォームを送信
            document.getElementById('myForm').submit();
          })
      })
    }
  </script>
</head>

<body>
  <form id="myForm" action="form.php" method="post" onsubmit="onSubmit(event)">
    名前:
    <input type="text" name="name">
    <br><br>
    住所:
    <input type="text" name="add">
    <!-- トークン送信用のinputタグ  -->
    <input type="hidden" name="recaptchaResponse" id="recaptchaResponse">
    <input type="submit" value="送信">
  </form>
</body>
</html>

「●●●」の部分は、GoogleのreCAPTCHAの管理画面で取得できる「サイトキー」を設定します。

ページ内の送信ボタンを押下するとこのサイトキーをGoogleのAPIに送信してトークンを取得し(このトークンの有効期限は2分しかありません。)、「トークン送信用のinputタグ」にその値を設定してサーバサイドにPOSTします。

上記の例ではsubmitしてページ遷移する実装になっていますが、UI/UXを考慮すればAjaxでサーバサイドとやり取りし、入力画面で人間 or bot判別を行ってエラー表示等を行う形になると思います。

reCAPTCHAは、JavaScriptを実行できるブラウザが前提になっているため、JavaScriptが実行できないbotはこの時点で排除されることになります。

送信されたPOSTデータをサーバ側で処理します。
下記はPHPの場合です。

<?php

// reCAPTCHAのシークレットキー
$secretKey = '▲▲▲';

// POSTリクエストからreCAPTCHAトークンを取得
$recaptchaResponse = $_POST['recaptchaResponse'];

// reCAPTCHAの検証リクエストURL
$verifyURL = 'https://www.google.com/recaptcha/api/siteverify';

// リクエストパラメータを準備
$data = array(
    'secret' => $secretKey,
    'response' => $recaptchaResponse
);

// cURLセッションを初期化
$ch = curl_init($verifyURL);

// POSTリクエストオプションを設定
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

// リクエストを実行し、レスポンスを取得
$response = curl_exec($ch);

// cURLセッションを終了
curl_close($ch);

// レスポンスをデコード
$responseData = json_decode($response);

// reCAPTCHAの検証結果をチェック
if ($responseData->success && $responseData->score >= '0.6') {
    // ★★★人間からの送信の場合は、お問い合わせ内容をメールで送信★★★

} else {
    // ★★★botと疑われる場合の処理★★★

}

?>

「▲▲▲」の部分は、GoogleのreCAPTCHAの管理画面で取得できる「シークレットキー」を設定します。

このシークレットキーとPOSTデータに含まれるトークン情報を一緒にGoogleのAPIに送信すると、下記のようなJSONが返ってきます。

{
"success": true,
"challenge_ts": "2024-01-XXT00:00:00Z",
"hostname": "XXX.XXX.XXX.XXX",
"score": 0.9,
"action": "submit"
}

「success」が「true」であれば、正常に人間 or botの判別が行えています。
そして、「score」の値が人間 or botのスコアとなります。上記では0.9になっていますが、1に近ければ人間の可能性が高く、0に近いとbotの可能性が高いという判別になります。
なので、ここで閾値を設けて自分で処理を分岐させる必要があり、それがソースの「// reCAPTCHAの検証結果をチェック」以降の部分となります。

閾値は仮に「0.6」としていますが、これはサイトによって値を変える必要があります。
つまり、v3を導入した初期段階ではbot判別は行わず、エージング期間が必要になるということです。

reCAPTCHAの管理画面ではスコア分布のグラフが見れるため、実際にお問い合わせフォームを利用しているユーザーのスコア分布を見てから閾値を決める必要があります。

また、前述のようにbotと疑われる結果になった場合、どのような処理をするのかも検討・実装する必要があります。

選択肢としては、下記のようなものが考えられますでしょうか。

  • もう一度最初からユーザーに入力し直して貰う
    (UXは悪い。またbotと疑われるスコアになったら無限ループしてしまう…。)。
  • reCAPTCHA v2を表示してユーザーに画像選択をして貰い、再度、人間 or bot 判別を実施
    (UXは悪い。v2でもbotと判別されたら? v2はアクセシビリティにも問題あるよね…。)。
  • 自前で「日本の首都は?」のようなカスタムCAPTCHAを実装し、ユーザーに回答して貰う
    (UX的には妥協できそう? ラジオボタンの選択式にすれば、何とかなる…?)。

別途、お問い合わせフォーム以外のお問い合わせ手段をユーザーに提供しておくのが良さそうな気もしますね。

ここからは、reCAPTCHA以外のbot対策テクニックを記載します。

JavaScriptチェック

これはフォームの送信ボタンを押下した際のPOST処理をJavaScriptで実行するようにし、JavaScriptが実行できない環境ではお問い合わせ自体をできなくする手法となります。
JavaScriptが実行できない単純なbotを排除するのには良い方法です。

タイムベースチェック

ユーザーが入力画面を表示したタイミングからJavaScriptでストップウォッチを裏で動作させ、送信ボタンを押下するまでを計測します。
もしくは、入力画面を表示した時刻を取得しておき、送信ボタンを押下した時の時刻と比較しても良いです。

そうすることで、ユーザーが入力に掛かった時間を計測することができるため、入力欄が複数あるにも関わらず数秒で入力完了しているような場合はbotと判断することが可能となります。

Honeypot

ハニーポット(蜂蜜の入った瓶)とは、botを嵌めるために仕掛ける罠、おとり(囮)を意味します。

botはお問い合わせフォームの入力画面にアクセスし、必須入力等のバリデーションチェックを突破するためにフォーム内に存在する全ての入力欄に何かしらの文字列を機械的に入力しようとします。
これを逆手に取り、ダミーの入力欄をフォーム内に追加しておいて、それをCSSで非表示にしておきます。

通常の人間が操作していれば(CSSで非表示になっているので)この入力欄のPOSTデータは空になるはずですが、CSSを解釈できないbotの場合は何かしらの文字列が送信されてくる可能性が高いため、ここで人間 or botの判別ができるケースがあります。

まとめ

現実には、お問い合わせフォームを自前で用意するというよりは、SaaSを使うケースが多いでしょうか。
ただ、自前で用意するとなった場合、いろいろなセキュリティ対策と同じで、ユーザーのUI/UXを悪化させずにbot対策をするのは中々難しい現状があります。

Webサービスのユーザー登録のように最初にメアドを登録させて、その返信メールにお問い合わせフォームのワンタイムURLを記載し、そこからしかお問い合わせを受け付けない、といった方法も考えられますが、UI/UXを無視した設計はユーザー離れに繋がりかねません。

reCAPTCHA v3の導入にはスコア収集のためのエージングやbot誤判定後のフォールバック対応等が必要となり、v2はUI/UXやアクセシビリティの悪化の懸念があるため、まずは比較的簡易なJavaScriptチェック、タイムベースチェック、Honeypotのいずれかか、それら複数の組み合わせで様子を見てみるというのが良いのかもしれませんね。

あとは、お問い合わせフォームのページに<meta name="robots" content="noindex">を追加し、URLをGoogle等の検索結果に表示しないようにすることで、お問い合わせページURLを検索結果から収集しているbotからのアクセスを防ぐという対応も考えられます。
ただ、これは効果が限定的で、人間がお問い合わせフォームを検索で探すことができなくなってしまう諸刃の剣でもありますね。