グーグルのスパム対策reCAPTCHAの導入と罠[PHP]

PHP,セキュリティ

投稿フォームを用意してるwebサイトにとって、スパム対策としてはグーグルのreCAPTCHAが有効です

その導入方法と待ち受ける多数の罠の回避方法なんかを紹介します

なお、この記事はウェブサイトでの導入を対象にしています

ワードプレスに設置する場合はこちらの記事をご覧ください

ちなみにreCAPTCHAってのはこんなやつです

誰でも一回は見たことがあると思います

v2はチェックもしくはパズル

ちなみにこれはv2の古いバージョンで、パズルを選ぶやつなんかもあります

最新のv3はユーザの必要なアクションがなくなってるので、ユーザにかける負担が少なくなってます

v3は右下にreCAPTCHAマークが表示されるのみ

導入方法

html

<form name="form">
  <input type="hidden" name="g-recaptcha-response" id="g-recaptcha-response">
</form>

まずはレスポンスデータを入れるhiddenをhtmlフォーム内に入れておきます

javascript

<script src="https://www.google.com/recaptcha/api.js?render=reCAPTCHA_site_key"></script>
 
<script>
  grecaptcha.ready(function() {
    grecaptcha.execute('reCAPTCHA_site_key', {action: 'homepage'}).then(function(token) {
      var recaptchaResponse = document.getElementById('g-recaptcha-response');
      recaptchaResponse.value = token;
    });
  });
</script>

あらかじめ管理画面で取得したサイトキーを「reCAPTCHA_site_key」にセットします(2箇所)

php

$secretKey       = 'reCAPTCHA_secret_key';
$captchaResponse = filter_input(INPUT_POST, 'g-recaptcha-response');
$verifyResponse  = file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret={$secretKey}&response={$captchaResponse}");
 
$responseData    = json_decode($verifyResponse);
 
if ($responseData->success) {
    //人間だ!→処理を実行
}
else{
    // ボットだ!、スパムボットが来たぞーー
}

レスポンスデータとシークレットキー「reCAPTCHA_secret_key」を付けてグーグルAPIを叩きます

そうするとAPIからレスポンスが返ってきます

人間の場合はsuccessがtrue、

ボットと判定された場合はfalseとなります

タイムアウト

実はこれだけだと、人間なのにも関わらずボット判定される人が出てきてしまいます

原因はreCAPTCHAがタイムアウトするからです

入力フォームなんかに採用してると、ユーザによっては長い長文を書いたりしますよね?

その入力中にreCAPTCHA側がタイムアウトされてしまい失敗します

ちなみにデフォルトのタイムアウト時間は5、6分です

しかしそれなりの文章を書こうと思ったら5、6分なんて普通に経ってしまいます

このとき返ってくるエラーステータスは以下のものです

timeout-or-duplicate

対策としては色々あるんですが、とりあえず僕のサイトではこの「timeout-or-duplicate」を無視する処理を加えました

その対策を加えたPHPの全文コードは以下の通りです

$secretKey       = 'reCAPTCHA_secret_key';
$captchaResponse = filter_input(INPUT_POST, 'g-recaptcha-response');
$verifyResponse  = file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret={$secretKey}&response={$captchaResponse}");
 
$responseData    = json_decode($verifyResponse);
 
$timeout = false;
if(array_key_exists("error-codes",$responseData)){
    $timeout = array_search('timeout-or-duplicate', $responseData->{'error-codes'});
}
 
if (!$responseData->success && $timeout === false) {
    // ボットだ!、スパムボットが来たぞーー
}
else{
    //人間だ!→処理を実行
}

APIからのレスポンスからerror-codesをチェックして、timeout-or-duplicateだった場合は人間として判定するというものです

対策としては他にも、一定時間ごとにreCAPTCHAをリセットするとか、送信する直前で発行するとかがありますが、一長一短ですね

最初は送信する直前でreCAPTCHAを発行するのが良いと思ってましたが、サブミットの直前に生成→送信をしてしまうとグーグル側が正しくスコアを算出できない可能性があるのでやめました

正しくスコアを算出できないと、そもそもスパム対策の意味がありませんから

タイムアウト時間は変更できない

この厄介なタイムアウト時間は変更できません

入力フォームに採用される機能なんだから、こんな短い時間でタイムアウトするとか普通に考えておかしいと思うんですが。。。

ちなみに僕以外にもそう考える人はいるみたいで、issuesにも登録されてますが今だに修正されて無いようです

error-codesの取得方法

凄いつまんないことなんですが、引っかかったので。

jsonで取得したreCAPTCHAのレスポンスにエラーコードを格納している「error-codes」という項目があります

この取得方法について、ちょっと注意が必要です

連想配列はPHPでは普通、以下の形で取得できます

$responseData->success

しかし「error-codes」はハイフンを含んでるので、その形式ではエラーになります

こういう時は、以下のように{}で括る必要があります

// エラー!
$responseData->error-codes
 
// OK
$responseData->{'error-codes'}