0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

問い合わせフォームのスパム対策5選(honeypot/Turnstile/レート制限・WordPress・PHP対応)

0
Last updated at Posted at 2026-07-12

問い合わせフォームに毎日届く英語のスパム、外部サービスへの自動リダイレクト、同じ内容の大量送信——。この記事は WordPress や PHP でフォームを運用しているWeb担当者・制作会社 向けに、スパムと不正送信を 多層(複数の対策の重ね掛け)で落とす定石5つ を、そのままコピペできる設定つきでまとめたものです。1つ入れるだけでは抜けられますが、組み合わせれば大半の自動送信は止まります。

そもそも「スパム」と「不正送信」は別物

まず整理します。フォームに来る迷惑送信は、対策の当てどころが2種類あります。

  • スパム(自動送信ボット): プログラムがフォームを機械的に送信してくる。宣伝・SEOリンク・詐欺誘導が中心。数で来るのが特徴。
  • 不正送信(CSRFなど): 利用者本人が意図しないリクエストを、外部サイトから送らされる。あるいは正規の送信フローを無視して直接エンドポイントを叩かれる。

前者は「人間かどうか」「送信ペースが異常でないか」で弾き、後者は「正規の画面から来た送信か」を検証して弾きます。1つの対策で全部を賄おうとせず、役割の違う対策を積むのが基本方針です。

対策は「軽くて誤検知の少ないもの」から順に重ねます。おすすめの導入順は次のとおりです。

  1. honeypot(ボットの大半をコストゼロで落とす)
  2. レート制限(連投・総当たりを止める)
  3. CSRFトークン(正規画面以外からの送信を弾く)
  4. Turnstile / reCAPTCHA(残りの高度なボットを判定)
  5. WordPress ならプラグイン設定で上記を底上げ

対策1: honeypot(見えない罠フィールド)

最もコストが低く、体感で最も効くのが honeypot です。人間には見えないがボットには見えるダミー入力欄を置き、そこに値が入っていたら送信を破棄します。CAPTCHA と違い、正規の利用者には一切手間が増えません。

まずフォームにダミー欄を追加します。CSS で画面外に飛ばし、autocomplete="off"tabindex="-1" でキーボード操作からも外します(display:none はボットに見抜かれやすいので、位置で隠す方が堅牢です)。

<!-- 正規の入力欄 -->
<label>お名前 <input type="text" name="your_name" required></label>
<label>本文 <textarea name="message" required></textarea></label>

<!-- honeypot: 人間には見えない。ボットだけが入力する -->
<div style="position:absolute; left:-9999px;" aria-hidden="true">
  <label>この欄は入力しないでください
    <input type="text" name="website_url" tabindex="-1" autocomplete="off">
  </label>
</div>

<!-- 送信までの経過時間を測る隠しフィールド(サーバー側で現在時刻を埋め込む) -->
<input type="hidden" name="form_rendered_at" value="<?php echo time(); ?>">

サーバー側では「罠欄に値が入っている」または「表示から送信までが速すぎる(人間なら数秒はかかる)」場合に破棄します。

<?php
// honeypot と送信速度でボットを判定する
$trap = $_POST['website_url'] ?? '';
if ($trap !== '') {
    // ボット確定。成功したように見せて静かに破棄する(エラーを返すと学習される)
    http_response_code(200);
    exit;
}

$rendered_at = (int) ($_POST['form_rendered_at'] ?? 0);
$elapsed = time() - $rendered_at;
if ($rendered_at === 0 || $elapsed < 3) {
    // 3秒未満での送信は自動化とみなす
    http_response_code(200);
    exit;
}

ポイントは、ボットを弾くときに あえてエラーを返さないことです。「弾かれた」と分かると回避を試みられるため、正常応答を返して黙って捨てます。

対策2: レート制限(連投・総当たりを止める)

同一送信元からの短時間の大量送信は、フォーム側の手前(Webサーバー)で落とすのが確実です。nginx なら limit_req で、フォームの送信先パスにレート制限をかけられます。

# http ブロックで送信元IP単位のゾーンを定義(10MBで約16万IP分)
limit_req_zone $binary_remote_addr zone=formlimit:10m rate=5r/m;

server {
    # 問い合わせフォームの送信先だけに適用する
    location = /contact/submit {
        limit_req zone=formlimit burst=3 nodelay;
        limit_req_status 429;   # 上限超過は 429 を返す
        # ... アプリへの proxy_pass など
    }
}

rate=5r/m は1分あたり5回まで、burst=3 で瞬間的な3回分までは許容します。正規の利用者が1分に何度も送ることはまずないため、しきい値は厳しめでも実害が出にくい箇所です。

Webサーバーに手を入れられない共有ホスティングなどでは、PHP 側でセッション単位の簡易制限を入れます。

<?php
session_start();
$now = time();
$_SESSION['submit_log'] = array_filter(
    $_SESSION['submit_log'] ?? [],
    fn($t) => $t > $now - 60   // 直近60秒の記録だけ残す
);
if (count($_SESSION['submit_log']) >= 5) {
    http_response_code(429);
    exit('しばらく時間をおいて再送信してください。');
}
$_SESSION['submit_log'][] = $now;

対策3: CSRFトークン(正規画面以外からの送信を弾く)

外部サイトに置かれた罠フォームから送信させられる、あるいはエンドポイントを直接叩かれる不正送信は、正規のフォーム画面を経由したかどうかで判定します。表示時にワンタイムのトークンを発行してセッションに保存し、送信時に照合します。

<?php
session_start();

// フォーム表示時: トークンを生成してセッションと hidden の両方に持たせる
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
?>
<input type="hidden" name="csrf_token"
       value="<?php echo htmlspecialchars($_SESSION['csrf_token'] ?? '', ENT_QUOTES); ?>">
<?php
// 送信時: hash_equals で定数時間比較(タイミング差での推測を防ぐ)
$sent  = $_POST['csrf_token'] ?? '';
$saved = $_SESSION['csrf_token'] ?? '';
if ($saved === '' || !hash_equals($saved, $sent)) {
    http_response_code(400);
    exit('不正なリクエストです。フォームを開き直してください。');
}
unset($_SESSION['csrf_token']);   // 使い回しを防ぐため使用後に破棄

あわせて、送信を受け付けるエンドポイントは POST 限定にし、GET での送信を受け付けないようにします。Cookie の SameSite 属性も併用すると、クロスサイトからの自動送信をさらに抑えられます(下の関連記事参照)。

対策4: Turnstile / reCAPTCHA(残りの高度なボットを判定)

honeypot をすり抜ける高度なボットには CAPTCHA を足します。利用者の操作を増やしにくい Cloudflare Turnstile(無料・多くの場合クリック不要)か、reCAPTCHA v3(スコア判定)が扱いやすい選択肢です。ここでは Turnstile の例を示します。

フォームにウィジェットを置きます。

<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<form method="post" action="/contact/submit">
  <!-- ... 入力欄 ... -->
  <div class="cf-turnstile" data-sitekey="ここにサイトキー"></div>
  <button type="submit">送信</button>
</form>

送信を受けたサーバー側で、Cloudflare に検証リクエストを送って結果を確認します。この検証を必ずサーバー側で行うのが要点です。フロントだけの表示では簡単に迂回されます。

<?php
// Turnstile のサーバー側検証
$secret = getenv('TURNSTILE_SECRET');   // シークレットはコードに直書きしない
$token  = $_POST['cf-turnstile-response'] ?? '';

$ch = curl_init('https://challenges.cloudflare.com/turnstile/v0/siteverify');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => http_build_query([
        'secret'   => $secret,
        'response' => $token,
        'remoteip' => $_SERVER['REMOTE_ADDR'] ?? '',
    ]),
    CURLOPT_TIMEOUT        => 5,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);

if (empty($result['success'])) {
    http_response_code(400);
    exit('人間による操作を確認できませんでした。');
}

reCAPTCHA v3 を使う場合は、検証先を https://www.google.com/recaptcha/api/siteverify にし、返ってきた score(0.0〜1.0)が一定値(例: 0.5)未満なら弾きます。

対策5: WordPress ならプラグイン設定で底上げする

WordPress で Contact Form 7 や同種のフォームを使っているなら、上記を最短で実現できます。

  • Turnstile / reCAPTCHA: Contact Form 7 のインテグレーション設定でキーを登録するだけで、送信時のサーバー側検証まで有効になります。
  • honeypot: 「Honeypot for Contact Form 7」等の追加プラグインで、専門知識なしに罠欄を追加できます。
  • CSRF・nonce: WordPress は投稿系の処理に nonce の仕組みを持っています。テーマやカスタムフォームで独自にPOSTを受ける場合は、対策3の代わりに nonce を使います。
<?php
// テーマ側でカスタムフォームを扱う場合の nonce
// 表示時:
wp_nonce_field('contact_submit', 'contact_nonce');

// 送信時:
if (!isset($_POST['contact_nonce'])
    || !wp_verify_nonce($_POST['contact_nonce'], 'contact_submit')) {
    wp_die('不正なリクエストです。', '', ['response' => 400]);
}

プラグインは入れっぱなしにせず、自動更新の対象に含めて最新に保つことも忘れないでください。フォーム系プラグインは狙われやすく、古いバージョンの放置がそのまま入口になります。

まとめ

  • スパム(自動送信)と不正送信(CSRF等)は当てどころが違う。役割の違う対策を重ねるのが基本。
  • コストゼロで最も効くのは honeypot。まずこれを入れる。
  • 連投は レート制限(nginx limit_req かPHPの簡易制限)で手前で落とす。
  • 外部からの不正送信は CSRFトークン / nonce と POST 限定で弾く。
  • すり抜ける高度なボットに Turnstile / reCAPTCHA を足す。検証は必ずサーバー側で。
  • WordPress ならプラグイン設定で上記を最短実現。プラグインは常に最新に。

すべてを一度に入れる必要はありません。honeypot → レート制限 → CSRF の順に足すだけでも、届くスパムは大きく減ります。

関連記事

本記事のフォーム周りの設定は、Webサイトを9つの守りでまるごと守る「サイトドック」の月次の定期健診でまとめて確認できます → https://sitedock.jp

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?