弾いたことを、相手に教えない
問い合わせフォームにボット対策を入れました。
いちばん効いたのは、判定そのものではなく弾いたときに返す言葉でした。
機械による送信をどう見分け、見分けたあとどう返すかの話です。判定を並べた順番とその理由、ブラウザ側の確認では足りない理由、そして実際に4通り送って効き目を確かめた結果を書きます。
弾いたことを、弾いた相手に教えない
入れた対策のひとつは、人には見えない入力欄です。画面の外へ飛ばしてあるので、使っている人は存在に気づきません。フォームの中身を機械的に読んで全部埋めるプログラムだけが、そこに書き込みます。
埋まっていたら機械、という判定です。ここまでは定石どおりでした。迷ったのは、そのあとです。
理由を返すと、送ってきた側はそれを手がかりにできます。次はその欄を空のままにして、もう一度送ればいいと分かってしまう。
1回弾くたびに、相手に1つ情報を渡すことになります。弾き続けるほど、相手の精度が上がります。
そこで、引っかかったときは「送信しました」と返して、実際には送らないことにしました。送った側から見れば成功です。受け取る側には何も届きません。
正しく使っている人には、この分岐は一度も起きません。見えない欄を埋めようがないからです。
判定を並べる順番
確認はいくつもあります。全部を並列にやるのではなく、順番を決めました。
| 順 | 見るもの | 落ちたときの返事 |
|---|---|---|
| 1 | 送信先が設定されているか | 受け付けていないと伝える |
| 2 | 同じサイトから送られてきたか | 拒否 |
| 3 | 見えない欄が埋まっていないか | 成功に見せて、捨てる |
| 4 | 開いてから送るまでの時間 | 入力内容を確かめるよう伝える |
| 5 | 必須項目と文字数 | 同上 |
| 6 | メールアドレスの形 | 同上 |
| 7 | 本文に入っているリンクの数 | 同上 |
| 8 | 短い時間に何回送ってきたか | 時間をおくよう伝える |
並べ方には規則があります。安い判定を先に、高い判定を後ろに置くことです。
1から7は、届いた1件だけを見れば答えが出ます。受け取った文字列の長さを測る、形を見る、それだけです。
8だけが違います。「前にいつ送ってきたか」を覚えておく必要があるので、記録を持ち、古いものを捨て、数える作業が要ります。明らかにおかしい送信をここまで通してから数えるのは、無駄に記録を増やすだけです。
3を上のほうに置いたのも同じ理由です。見えない欄が埋まっている時点で、それ以上調べることは何もありません。
速すぎる送信と、古すぎる送信
4は、フォームを開いた時刻を持たせておき、送信までに何秒かかったかを見る判定です。
人間が名前と本文を打つなら、どんなに速くても数秒はかかります。開いた直後に送信が届いたら、打っていません。逆に、何時間も前に開いたものが送られてくる場合もあります。開きっぱなしのタブか、一度取得した画面を使い回しているかのどちらかです。
ただし、この時刻はブラウザから送られてきます。送る側で好きな値を入れられるので、これ単独では根拠になりません。他の判定と組み合わせて使う前提の材料です。
ブラウザ側の確認は、確認ではない
必須項目も、メールアドレスの形も、ブラウザ側で確かめています。それでもサーバー側に同じ確認を書きました。
| 役割 | 迂回 | |
|---|---|---|
| ブラウザ側 | 打ち間違いをその場で知らせる親切 | できる |
| サーバー側 | 通してよいかを決める条件 | できない |
機械はフォームの画面を開きません。送信先のURLへ直接投げるだけです。画面の中にある確認は、画面を通らない相手には存在しないのと同じでした。
効いていることを、実際に確かめた
入れたあと、4通りの送信を本番で試しました。
| 送ったもの | 画面の返事 | 受信箱 |
|---|---|---|
| 普通に書いて送信 | 送信しました | 届いた |
| 開いた直後に送信 | 確かめるよう表示 | 届かない |
| 形が違うメールアドレス | 確かめるよう表示 | 届かない |
| 見えない欄を埋めて送信 | 送信しました | 届かない |
4つめが要です。画面には成功と出て、受信箱には入っていない。この2つを両方確かめないと、確かめたことになりません。
「成功に見える」だけを見て終わると、本当に送ってしまっている可能性が残ります。「届いていない」だけを見て終わると、正しい送信まで捨てている可能性が残ります。受信箱の件数がちょうど1件だったことで、両方が同時に確定しました。
これを書くと、少し弱くなる
正直なところ、この記事を公開すると対策の一部は効き目が落ちます。見えない欄があると分かっていれば、避けて送ることはできるからです。
それでも書いているのは、仕組みを知られて無意味になるものと、知られても構わないものが混ざっているからです。
| 書いたもの | 書かなかったもの |
|---|---|
| 判定の順番と、その理由 | 見えない欄の名前 |
| 時間を見ているということ | 何秒未満を弾いているか |
| 回数を数えているということ | 何分に何回まで通すか |
左は、知られても迂回の役に立ちません。考え方だからです。右は、そのまま迂回の手順になります。「なぜそうしたか」は公開できて、「いくつにしたか」は公開できないという線引きでした。
今回の学び
まず、弾き方より返し方のほうが効く場面があるということ。同じ判定でも、結果を正直に伝えるか黙って捨てるかで、次に来るものが変わります。
次に、判定には値段があること。1件だけ見れば済む確認と、記録を持たないとできない確認は、同じ一覧に並んでいても重さが違いました。順番を決めるだけで、重いほうが動く回数が減ります。
そして、「入れたこと」は「効いていること」ではないこと。入れた直後は、動いているかどうか誰も知りません。弾かれる側と通る側の両方を自分で送ってみて、はじめて分かりました。
この記事を共有する
関連記事

Criticalが4件並んだ日にやったこと
朝、通知を見たらCriticalが4件並んでいました。どれも同じ相手、Next.js本体です。 この日にやったことを順に書きます。結論から言うと、上げました。ただ、上げる前にやったことのほうが本題です。 この記事で扱うこ […]

リンク先は存在した。それでも404だった
連載の記事に「次回:〇〇」というリンクを書いています。そこを押すと404でした。 リンク切れは事前に確認していました。結果は0件でした。確認の仕方が間違っていたのです。 この記事で扱うこと 存在するのに辿り着けないリンク […]

動かしていなかったから、漏れていなかっただけ
問い合わせフォームにボット対策を入れようとして、先に別のことが分かりました。 いまの作りでは、ブラウザ側にどんな対策を入れても効きません。 この記事で扱うこと 送信先のURLが配信されるJavaScriptに入る作りにな […]