はじめに:「プラグインが使えないんです」という一言から始まった相談
先日、メンタリングをさせていただいている受講生の方から、こんなご相談をいただきました。
「クライアント様のコンタクトフォームに、空欄や適当な文字列を打ち込んだ迷惑送信が計600件ほど来ているそうです。CAPTCHAを設定して対策したいのですが、対象サイトがWordPressではないため、プラグインが使えません……」
WordPressサイトであれば、迷惑メール対策のプラグインを一つ入れるだけで済むケースも多くあります。しかし今回のようにフルスクラッチのPHPで組まれたサイトでは、そうはいきません。仕組みそのものを理解した上で、手を動かして実装する必要があります。
今回は、この相談を通じてお伝えした「多層防御」の考え方と、実装時に陥りがちな落とし穴について紹介します。
1. reCAPTCHA v3を選ぶ理由——「見えない」ことの価値
CAPTCHA対策と聞くと、多くの方が思い浮かべるのは「私はロボットではありません」というチェックボックスや、信号機の写った画像を選ばせるあのUIだと思います。これはreCAPTCHAのv2という方式です。
一方、今回採用したのはv3という、ユーザーには一切何も表示されない方式です。ページ操作の裏側でGoogleが挙動を解析し、0.0〜1.0の「人間らしさスコア」を算出します。
すでに完成しているクライアント様のサイトデザインに、チェックボックスや画像認証のUIを後から組み込むのは、レイアウトへの影響も工数も無視できません。「見た目を一切変えずに導入できる」という一点で、v3は今回の要件に合っていました。
2. reCAPTCHAだけでは防げない相手がいる
ここが今回、受講生の方に一番お伝えしたかったポイントです。
reCAPTCHA v3は、ブラウザ上でJavaScriptが実行されて初めて機能する仕組みです。つまり、ブラウザを介さずスクリプトから直接フォームにPOSTリクエストを送りつけてくる単純なbotに対しては、そもそもトークン自体が発行されません。
今回の迷惑送信の中身が「空フォーム」「適当なアルファベットの羅列」という、極めてシンプルなものだったことから、こうした単純なbotが主要な発生源である可能性が高いと判断しました。
そこで提案したのが、reCAPTCHAとは全く異なる原理で動くhoneypot(隠しワナ)の併用です。
- フォームの中に、人間には見えない入力欄をCSSでこっそり1つ追加する
- 人間はその存在に気づかず空欄のまま送信するが、機械的にフォームを読み取るbotは律儀に埋めて送信してくる
- サーバー側で「その欄に値が入っていたら弾く」という、たった1行の条件分岐で対策できる
実装コストの低さに対して効果が見込めるため、reCAPTCHAとhoneypotは「片方で十分」ではなく、得意な相手が違う対策として併用するのが正解です。
3. 「セッションで連続送信を防ぐ」設計が抱えていた落とし穴
実装が進む中で、受講生の方から追加の相談がありました。「同じ相手からの短時間の連続送信を防ぎたい。セッションを使ってIPアドレスと送信時刻を記録する方法で対応しようと思います」という内容です。
一見自然な発想に見えますが、ここに見落としがありました。
PHPのセッションは、サーバーが発行した識別子をCookieとしてブラウザに保存させ、次回アクセス時にそれを送り返してもらうことで「同一人物」を判定する仕組みです。しかし、フォームを直接POSTしてくるbotの多くはCookieを保持・送信しません。つまりbot側から見ると、送信のたびに「初めましてのアクセス」として扱われてしまい、セッションに記録した情報を参照できず、連続送信として検知できないのです。
対策として提案したのは、セッションではなくサーバー側でIPアドレスをキーにして送信時刻を記録する方式への変更でした。Cookieの有無に依存しないため、bot・ブラウザを問わず確実に機能します。
保存先についても、今回の規模感であればわざわざDBを新設する必要はなく、ファイルへの書き出しで十分という判断をお伝えしました。ただしファイル方式を採用する場合、同時アクセス時の排他制御(flock()によるファイルロック)と、一定時間より古い記録を随時間引く仕組みは、セットで実装しておく必要があります。ここを怠ると、アクセスのたびに記録が際限なく増え続け、判定処理そのものが徐々に重くなっていきます。
4. テスト環境を作る際、見落としがちな「依存関係」
実装がひと通り完了した後、受講生の方は本番サーバー上にテスト用のフォルダを別途作成し、送信先を自分のアドレスに変えてテストを行う方針を取られました。「本番のフォルダとは完全に切り離されているので影響はない」という認識でしたが、ここにも確認すべき点がありました。
1つ目は、本番サーバー上に置く以上、URLさえ分かれば誰でも(botも含めて)アクセスできる状態になるという点です。検索エンジンにインデックスされたり、既存の迷惑botに新しいフォームとして発見されたりするリスクがあるため、Basic認証などでのアクセス制限は必須です。
2つ目は、共通ファイルへの依存関係です。複数のフォームが同じ設定ファイルを参照する構成になっている場合、テスト用フォルダがその参照をそのまま引き継いでいると、「テスト環境のつもりで変更した設定が、実は本番の共通ファイルそのものだった」という事故が起こり得ます。「フォルダが分かれている」ことと「本当に独立している」ことは、似ているようで別の話です。テスト用フォルダの中だけで全ての依存ファイルが完結しているか、requireやincludeの参照先を一つひとつ確認する作業が欠かせません。
まとめ:迷惑メール対策は「一つの正解」を探すゲームではない
今回の事例を通じてお伝えしたかったのは、迷惑メール対策には銀の弾丸となる単一の解決策は存在しない、ということです。
- reCAPTCHA v3は賢いbotに強いが、単純なbotのトークン未発行という穴がある
- honeypotは実装コストが低く単純なbotに刺さるが、それを見破るbotには効かない
- セッションベースの連続送信対策は、Cookieを送らない相手には無力
- テスト環境は「フォルダを分ける」だけでは、本番からの独立を保証できない
一つひとつの対策の「得意・不得意」を理解し、組み合わせて多層的に防御する。そして、閾値やスコアといった数値は最初から完璧を目指さず、ログを取りながら段階的に調整していく。この考え方は、迷惑メール対策に限らず、セキュリティ全般に通じるものだと感じています。
受講生の方は今回、こうした一つひとつの論点に丁寧に向き合い、クライアント様への説明・提案も含めて主体的に進めてくださいました。技術的な正しさだけでなく、それを分かりやすく相手に伝える力もまた、フリーランスとして信頼を積み重ねる上で欠かせない要素だと、改めて感じた案件でした。
もし、以下のようなお悩みをお持ちの方がいらっしゃいましたら、ぜひお気軽にご相談ください。
- 非WordPress環境でのセキュリティ・迷惑メール対策の進め方に迷っている
- reCAPTCHAやhoneypotなど、複数の対策の使い分けが分からない
- クライアント様への技術的な説明・提案の仕方に自信が持てない
『何から相談していいか分からない』という段階でも問題ありません。現状を整理するお手伝いから始めさせていただきます。
▶︎ 平林 颯太への制作・コンサルティング相談はこちら(Smile Creations お問い合わせフォーム)
▶︎ MENTAでのスポット相談はこちら