お問い合わせフォームが荒らされた——CAPTCHAを入れずに止めるまでの実地記録

2026年7月29日の夜から30日の早朝にかけて、弊社サイトのお問い合わせフォームに迷惑送信が立て続けに届きました。届いたメールを見るかぎりでは「よくある迷惑メールが数通」という程度の出来事です。

けれどサーバーの記録を開いてみると、早朝の58分間で38件の送信が通り抜けていました。そして、いちばん重い問題は受信箱ではなく別のところにありました。

弊社では「起きたことは記録まで下りて確かめ、対策は実測で検証する」という進め方をしています(前回は話題のツールを実測で検証しました)。今回も同じやり方で、原因の特定から対策の実装、効果の確認までを記録します。

結論を先に書きます。

原因はフォームに残っていた「裏口」と、防御が実質ゼロだったことの重なりでした。いちばん怖かったのは迷惑メールを受け取ることではなく、弊社ドメイン名義で第三者に宣伝メールを送ってしまいかねない構造です。対策はCAPTCHAを使わず、サーバー側の関門を7枚重ねる形で実装しました。適用直後から、送信はすべて遮断されています。

夜明け前のオフィスの受付。手前から赤紫の不審な封筒が鎖と黒い尾を引いて大量に押し寄せ、中央に何枚も重なって立つ透明なガラスの盾に触れた瞬間、光の粒になって砕けている。一通だけ白い封筒がやわらかい光をまとってガラスの層を静かに通り抜け、奥の木のデスクへ向かっている
対策の考え方を一枚にすると、こうなります。厚い壁を1枚立てるのではなく、薄い関門を何枚も重ねました。

まず事実

項目 内容
発生 2026年7月29日 22時ごろに1通目が着信、30日早朝も継続
対策前の送信 58分間で38件(すべて成功していた)
送信元 28個の異なるIPアドレス(世界各地に分散)
ブラウザの名乗り 38件すべて同一の文字列(Chrome/125.0.0.0)
送信経路 フォーム本来の送信窓口ではなく、ページURLへの直接送信
当時の防御 迷惑送信の判定サービスは停止中、CAPTCHA未設定、禁止ワード空、連投制限なし
対策後の送信 54分間で31件、すべて遮断(24個のIPアドレス)

届いたメールと、記録が示した実態

届いていたメールは、お名前の欄に「保留中のギフト:登録ボーナスが削除されます。コード BLACK3000 で無料スピンを確保してください」といった宣伝文とURL、件名は 0td8b4 のような無意味な文字列、本文は fvjfff の一言だけ、というものでした。カジノ系の宣伝を、他人のフォーム経由でばらまく典型的な手口です。

メールを削除して終わりにもできますが、それでは原因が残ります。サーバーのアクセス記録から、お問い合わせページへの送信だけを抜き出しました。

  • 早朝の 58分間で送信38件(1〜2分おき)
  • 送信元は 28個の異なるIPアドレス
  • それなのに ブラウザの名乗りは38件すべてまったく同じ

人間が世界中から一斉に、まったく同じブラウザ環境で、1〜2分おきに送信してくることはありません。自動化されたプログラムからの連続送信でした。しかも「昨日たまたま来た」ではなく、調べている最中も進行中でした。

もうひとつ、サーバーが返した応答の大きさに手がかりがありました。38件すべてがまったく同じサイズだったのです。入力の誤りで弾かれていれば、画面に出るメッセージや入力内容の残り方でサイズは少しずつ変わります。全件が同じサイズなのは送信成功の画面に共通する特徴で、38件すべてが成功していたと判断できる状態でした。対策後の応答サイズが件ごとにばらつくようになったことで、この読みは裏づけられています。

なぜ通ってしまったのか——フォームに残っていた「裏口」

いちばんの発見はここでした。

弊社のフォームは、ふつうに使うとJavaScriptが働いて、サイト内部の専用窓口へ内容を届けます。ところが今回のプログラムは、そこを通っていませんでした。お問い合わせページのURLに、フォームの内容を直接送りつけていたのです。

これはフォームの仕組みが古くから持っている「JavaScriptが使えない環境向けの経路」で、互換性のために残されているものです。フォーム側もこの経路を正式に受け付けます。つまり、

フォーム画面を一度も表示せずに、送信だけを成立させられる

という裏口が開いていました。ブラウザを立ち上げる必要も、JavaScriptを動かす必要もない。だからこそ1〜2分おきの連投が成り立っていたわけです。そしてこの経路には、画面の側で工夫する対策(入力欄を隠す、送信までの時間を測るなど)は届きません。

標準の迷惑送信対策は、実質働いていなかった

そのうえでサイト側の備えを点検しました。結果は、言い訳のできない状態でした。

備え 当時の状態
迷惑送信の判定サービス(Akismet) インストール済みだが停止中(=何もしていない)
CAPTCHA(人間かどうかの確認) 未設定
禁止ワードのリスト 空
見えない入力欄(ハニーポット) なし
同一相手からの連投制限 なし

フォームが標準で持っている迷惑送信の判定も確認しましたが、内容は「ブラウザの名乗りが2文字未満なら怪しい」「送信票の照合に失敗したら怪しい」の2つだけで、後者は管理画面にログインしている人にしか適用されない仕様でした。ログインしていない訪問者——つまり今回のプログラムには、最初から何も効いていなかったことになります。

一番こたえたのは、判定サービスが入っているのに停止していたことです。プラグイン一覧に名前があると、つい「入れてある」で安心してしまいます。けれど有効にして鍵を登録し、実際に働いていることを確かめるまで、それは飾りでしかありません。

いちばん怖かったのは「受信」ではなく「送信」

ここまでは「迷惑メールが届いて困る」という話です。実は、もっと重い問題が隠れていました。

このフォームは送信があると2通のメールを出します。1通は担当者への通知。もう1通は、送信者への自動返信(「お問い合わせありがとうございます」)です。

自動返信の宛先は、当然ながらフォームに入力されたメールアドレスです。ということは——

  • 誰かが他人のメールアドレスを入力して、
  • お名前や本文の欄に宣伝文とURLを書き込んで送信すると、
  • 弊社のサーバーが、弊社のドメイン名義で、その他人に宣伝メールを届けてしまう

しかもこのメールには、なりすましでないことを証明する電子署名(DKIM)が正しく付きます。受け取った側から見れば、フィーノから届いた正規のメールです。

つまりフォームは、迷惑メールの踏み台になり得る状態でした。これを続けられると、弊社のドメインが「迷惑メールを送るドメイン」として各社の判定に学習されていきます。そうなると、お客様への見積書や連絡メールが迷惑メールフォルダへ落ちるようになる。受信箱が荒れるより、こちらのほうがずっと深刻です。

あわせて、通知メールがHTML形式で送られる設定になっていました。迷惑メール内のURLはクリックできる状態で、外部の画像も読み込まれます。うっかり触れば相手に「このアドレスは生きている」と伝わってしまいます。

入口(受信)だけを考えて、出口(送信)を見ていなかった。 ここが今回いちばんの反省点でした。

対策の候補を4つ比べた

案 内容 費用 判断
① Google reCAPTCHA Googleの判定サービスを組み込む 月1万件まで無料/超過は要課金 見送り
② Cloudflare Turnstile Cloudflareの判定サービス。フォーム側に標準搭載 無料 保留(次の一手)
③ Akismet 迷惑送信の判定サービス。停止中のものを有効化 商用サイトは有料 見送り
④ サーバー側の多層チェック 自前の判定を積み重ねる 無料 採用

「Googleのキャプチャを使う手もあるけれど、最近はあまり勧められないらしい」という話が社内でも出ていました。調べてみると、その感覚はおおむね当たっていました。

表示速度への影響

フォーム側の実装を実際に読んでみたところ、reCAPTCHAを設定すると、Googleのスクリプトがお問い合わせページだけでなくサイトの全ページで読み込まれる作りになっていました。読み込む条件はページ種別ではなく「reCAPTCHAが有効かどうか」だけです。会社概要を見るだけの方にも、外部への通信が発生します。

個人データの扱い

reCAPTCHAは訪問者のふるまいを見て判定する仕組みで、Cookieを発行し、Googleのサーバーと通信します。訪問者の情報を国外の第三者に渡すことにあたるため、プライバシーポリシーへの記載や同意の取り方まで含めた設計が必要です。欧州では、同意なしの利用を問題視した行政処分の事例も報じられています。フォーム1つのために、サイト全体の同意設計を作り直すのは、今回の状況に対して大掛かりでした。

費用と運用

費用も「ずっと無料」ではありません。現在は月1万件までが無料で、超えるとプランや請求設定に応じてエラーになるか課金される料金体系です。弊社の規模なら無料枠に収まりますが、前提としては置きにくくなりました。

運用にも手がかかります。現行版はスコア(人間らしさの点数)で判定するため、どの水準で弾くかを運用しながら調整する必要があり、「効いているのかどうかが分かりにくい」という性質があります。解読を代行するサービスまで存在するので、入れれば安心というものでもありません。

「CAPTCHAなら止まったのか」——自分の理屈が間違っていた

ここで、公開前の原稿を別のAI(Codex)に事実確認させたところ、こちらの理屈に誤りがあると指摘が入りました。

当初は「CAPTCHAは画面に置く部品だから、画面を通らない裏口送信には出番がない」と考えていました。ところが実際にフォーム側の処理を読み直すと、CAPTCHAの合否確認はサーバー側で行われ、合格の証(トークン)が空っぽの送信はその場で迷惑送信と判定される作りになっていました。つまり——

reCAPTCHAを設定していれば、今回の裏口送信も止まっていた。 見立てが間違っていました。

ではCAPTCHAを入れるべきだったのか。正直に整理すると、入口を止めるという一点ではCAPTCHAは有効でした。それでも④を選んだ理由が3つあります。

  1. CAPTCHAでは「出口」が塞がらない。 自動返信が第三者への宣伝メールになる構造は、判定をすり抜けた1件が来ればそれで成立します。ここはCAPTCHAの担当範囲外で、結局サーバー側で手を入れる必要がありました
  2. 代償が規模に見合わない。 全ページでの外部スクリプト読み込み、同意設計の作り直し、しきい値の調整。守りたいのは1ページのフォーム1つです
  3. 1つの関門に頼りたくない。 CAPTCHAは強力ですが、突破されたときに後ろに何も残りません。薄い関門を何枚も重ねるほうが、この規模には合うと判断しました

そのうえで、すり抜けが出たらCAPTCHA(Turnstile)を足すという次の一手を残しました。この順番なら、費用も表示速度も同意設計も、必要になった時点で払えばよくなります。

事実確認を頼んでいなければ、間違った理屈のまま公開していました。AI同士に読み合わせをさせるひと手間は、こういうときに効きます。

実装:7つの関門

サーバー側だけで完結する仕組みを追加しました。訪問者から見て画面上の手間は一切増えていません(入力欄も追加していません)。

  1. 裏口を閉じた — フォーム画面を経由しない直接送信を受け付けないようにした。今回のプログラムはこれで止まる
  2. 見えない入力欄を置いた — 人間には見えず、機械だけが埋めてしまう欄を追加。埋まっていたら迷惑送信と判断する
  3. 連投を制限した — 同じ相手からの送信は「15秒以内の連続はNG」「1時間に3件まで」に
  4. お名前・件名のURLを弾いた — お名前の欄にURLを書く方は実務上いません。今回の迷惑送信はまさにここに宣伝文を入れていた
  5. 本文のリンクを見るようにした — URLが複数並ぶ、リンク記法が使われている、日本語がまったくなくURLだけがある、といった特徴で判断
  6. 自動返信からURLを取り除いた — 万が一すり抜けても、弊社名義のメールで第三者にリンクを届けないための保険。これが「出口」の対策
  7. メールを文字だけにした — 通知・自動返信ともHTML形式をやめた。リンクはクリックできず、外部の画像も読み込まれない

あわせて、空だった禁止ワードのリストに、この種の宣伝で使われる語を20語ほど登録しました。

判定はすべてサーバー側で行っています。画面を見て回避することはできません。そして迷惑送信と判断した場合はメールを1通も送りません。通知も自動返信も出ないので、踏み台にされる経路も同時にふさがります。

地味ですが大事な工夫として、遮断した送信を記録に残すようにしました。日時・理由・送信元・内容の一部を最新50件まで保存します。対策は入れて終わりにすると、効いているのか、行きすぎて正規のお問い合わせまで弾いていないのかが分からなくなります。測れる状態にしておくことを、仕組みの一部として組み込みました。

効果:適用直後に、本物が止まった

適用したのは早朝4時58分。効果はすぐに記録に現れました。

  • 4時58分45秒:実際の迷惑送信を1件遮断。記録には送信元と「BLACK3000」のカジノ文面が残っていた
  • 以降の送信はすべて遮断。54分間で31件、24個のIPアドレスからの送信を止めた
  • サーバーの応答も、送信成功の画面からエラー画面へ変わった(応答サイズの変化でも確認できた)

念のため、正規のお問い合わせがきちんと通ることも確認しました。普通の内容で送信し、通知メールと自動返信メールの到達を確認しています。対策を入れるときに、ここを確かめないのは怖いところです。

なお適用直後に、「遮断の記録が実際の遮断件数より少ない」ことに気づきました。禁止ワードで弾かれた分が記録に残らない作りになっていたためです。すぐに直して、どの関門で止まったかをすべて同じ場所に残すようにしました。測るための仕組みが正しく測れていなければ意味がないので、ここは早めに見つけてよかった点です。

持ち帰ったこと

「入れてある」は「守れている」ではない。 今回いちばん刺さったのはこれでした。判定サービスは一覧に名前があり、日本語化まで済んでいたのに、停止したままでした。入れた・有効にした・鍵を登録した・実際に働いていることを確かめた——この4段目まで行かないと、対策は対策になりません。

入口だけでなく、出口も守る。 迷惑メールが届く不便より、自社ドメインが迷惑メールの送信元として扱われることのほうが、事業への影響は大きい。フォームは「受け取る窓口」だと思いがちですが、自動返信を出す=送信する仕組みでもあります。設計の時点で見ておくべきでした。

まず記録を読む。見立ては外れる。 「CAPTCHAでは止まらないはずだ」という当初の見立ては間違いでした。逆に「迷惑メールが数通」という見た目も、記録をたどれば58分で38通でした。見立てを検証する手間を省くと、対策の方向ごと間違えます。 困ったときにまずサーバーの素の記録を見る——これは以前の「どの固定ページを開いてもブログ一覧が出る」不具合のときと、まったく同じ教訓でした。

次の一手を用意しておく。 今回はCAPTCHAを使わずに止まりましたが、相手も手口を変えてきます。今の関門をすり抜ける送信が出てきたら、次はCloudflare Turnstileを追加する予定です。フォーム側に標準搭載されていて無料、通常の構成ではCookieを使わず画像パズルも出さない仕組みなので、いま採った方針と相性がよいと考えています。遮断の記録を残したのは、その判断を勘ではなく数字で下せるようにするためでもあります。

最後に

迷惑メールが数通届いた、という小さな出来事でした。けれど記録をたどれば58分で38通が通り抜け、自社ドメインで第三者に宣伝メールを送りかねない状態だった。気づいた日のうちに原因まで下りて塞ぐ。地味ですが、この積み重ねがサイトの信頼を守るのだと思います。

お問い合わせフォームは、これまでどおりお使いいただけます。入力の手間が増えたところは一つもありません。安心してご相談ください。

同じ症状(フォームからの迷惑送信が続く、自動返信が知らない相手に飛んでいる)でお困りの場合は、お問い合わせフォームよりご相談ください。