問い合わせフォームにスパムが届き始めると、まず reCAPTCHA を入れる案が出る。手軽で効果もある。ただし代償がある。
- 「信号機を選んでください」で本物の見込み客が離脱する
- 判定が厳しいと、視覚に困難のある利用者が送信できなくなる
- 外部サービスへ、送信者の情報が渡る
問い合わせフォームは1日に数件しか来ない。そこに、全員へ負担をかける仕組みを入れるのは費用対効果が悪い。利用者に何も要求せず、サーバー側だけで完結する方法から順に試すほうがいい。
1. ハニーポット(人には見えない入力欄)
もっとも効果が高く、実装が短い。
人間には見えないが、HTMLを機械的に読むボットには見える入力欄を置く。そこに文字が入っていれば自動送信だと判断する。
<div class="trap" aria-hidden="true">
<label for="website">この欄は入力しないでください</label>
<input type="text" id="website" name="website" tabindex="-1" autocomplete="off">
</div>
/* display:none ではなく画面外へ飛ばす。
display:none はボット側も無視することがある */
.trap {
position: absolute;
left: -9999px;
width: 1px;
height: 1px;
overflow: hidden;
}
サーバー側では、埋まっていたらエラーを返さず、成功したように見せて捨てる。
if (trim((string)($_POST['website'] ?? '')) !== '') {
header('Location: /thanks/', true, 303);
exit; // 何も送らない
}
エラーを返すと、ボットの作者に「弾かれた」と伝わり、対策を変えられる。黙って捨てるほうが長持ちする。
欄の名前は website や url のように、ボットが埋めたくなる一般的な名前にする。honeypot や spam-trap という名前にすると、ボット側に避けられる。また tabindex="-1" と aria-hidden="true" を付けて、キーボード操作や読み上げソフトの邪魔をしないようにする。
2. 送信元の確認
自サイトのページ以外から投げられた POST を弾く。他所のページにフォームを設置して送りつけてくる手口に効く。
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
$referer = $_SERVER['HTTP_REFERER'] ?? '';
$ok = ($origin !== '' && strpos($origin, 'https://example.com') === 0)
|| ($origin === '' && $referer !== '' && strpos($referer, 'https://example.com') === 0);
if (!$ok) {
// 拒否
}
Origin は最近のブラウザなら POST 時に必ず送られる。付いていない場合だけ Referer を見る、という二段構えにしておくと、古い環境の利用者を弾かずに済む。
これは万能ではない。ヘッダは偽装できるので、手間を1段階増やすだけの対策と考えたほうがいい。
3. 同一IPからの連続送信を制限する
同じ相手から短時間に何十件も来る場合に効く。データベースを使わず、ファイル1つで足りる。
function withinRateLimit(string $ip, int $max = 5, int $window = 600): bool
{
$path = sys_get_temp_dir() . '/form-' . hash('sha256', $ip) . '.txt';
$now = time();
$stamps = [];
if (is_readable($path)) {
foreach (explode(',', (string)file_get_contents($path)) as $t) {
$t = (int)$t;
if ($t > 0 && ($now - $t) < $window) {
$stamps[] = $t; // 期限内のものだけ残す
}
}
}
if (count($stamps) >= $max) {
return false;
}
$stamps[] = $now;
@file_put_contents($path, implode(',', $stamps), LOCK_EX);
return true;
}
IPはそのまま保存せず、ハッシュ化してファイル名にする。個人情報を余分に持たないためだ。
上限は緩めでいい。10分に5件なら、通常の利用者が引っかかることはまずない。
4. 内容による判定は最後の手段
「特定の単語が入っていたら弾く」という方法もあるが、これは誤判定が怖い。本物の問い合わせに、たまたま該当語が入っていたら失われる。
どうしても入れるなら、弾くのではなく件名に印を付けて受信するほうがいい。判断を人間に残せる。
忘れてはいけないこと
スパム対策と同時に、必ず入れておくべきものが2つある。
メールヘッダ挿入の防止。 名前やメールアドレスの欄に改行を仕込まれると、メールの宛先を書き換えられ、迷惑メールの踏み台にされる。1行入力の項目からは改行を必ず除去する。
$name = str_replace(["\r", "\n", "\0"], '', $_POST['name'] ?? '');
送信失敗時の退避。 スパムを弾くことより、本物を落とさないことのほうが重要だ。メール送信が失敗したときに内容をログへ残しておけば、サーバー側の障害で問い合わせが消えることはなくなる。
それでも足りなくなったら
ここまでやって、なお処理しきれない量が来るようになったら、そのとき初めて画像認証を検討すればいい。
順番が大事で、先に画像認証を入れてしまうと、離脱している見込み客の数は誰にも見えない。まず利用者に負担をかけない方法を尽くしてから、必要なぶんだけ足す。この順番を守るほうが、結果的に問い合わせ件数は多くなる。