メニュー

東京・小金井市のWEB制作会社の豆知識

MEMORANDUM

お問い合わせフォームのスパム対策|PHPだけで実装できる5つのBot対策

最近、お客様から「お問い合わせフォームに営業メールやスパム投稿が増えて困っている」というご相談を受けました。
ホームページのお問い合わせフォームは、お客様との大切な窓口ですが、同時に営業メールや自動送信プログラム(Bot)の対象にもなりやすい機能です。
営業メールには大きく分けて「Bot(自動送信プログラム)による大量送信」「人が実際にフォームへ入力して送信する営業メール」の二種類がありますが、今回ご紹介する方法は、前者のBotによる自動送信を抑制するための対策です。
人が実際に入力して送信する営業メールまで完全に防ぐことはできませんが、機械的な大量送信には十分な効果が期待できます。
今回は、PHPだけで実装できる5つのスパム対策をご紹介します。

今回実装するスパム対策

今回紹介するのは、次の5つです。

  • CSRF対策
  • ハニーポット
  • フォーム表示から一定時間以内の送信制限
  • 同一IPアドレスからの連続送信制限
  • 同一メールアドレスからの連続送信制限

どれか一つだけでは十分とは言えませんが、組み合わせることでスパム投稿を大幅に減らすことができます。

より強力なBot判定が必要な場合は、Google reCAPTCHAなどの外部サービスを利用する方法もあります。ただし、入力者側の操作負担や外部サービス依存が発生するため、今回紹介したPHP単体対策と用途に応じて使い分けることが重要です。

CSRF対策

まず実装したいのがCSRF(クロスサイト・リクエスト・フォージェリ)対策です。
CSRFとは、第三者のWebサイトから意図しないPOST送信を実行させる攻撃です。
確認画面付きメールフォームでは、送信時にランダムなトークン(受付番号のようなもの)を発行し、その値が一致するかを確認します。

確認画面の処理(PHP)

//トークンの発行
$_SESSION['token'] = bin2hex(random_bytes(32));

確認画面(HTML)

<input type="hidden" name="token" value="<?php echo htmlspecialchars($_SESSION['token'], ENT_QUOTES, 'UTF-8'); ?>">

送信処理(PHP)

//トークンの照合
if(empty($_POST['token']) || empty($_SESSION['token']) || !hash_equals($_POST['token'], $_SESSION['token'])){
	exit('不正なアクセスです。');
}

hash_equals() を利用することで、安全にトークンを比較できます。

ハニーポット

ハニーポットは、人には見えない入力欄を用意する方法です。
Botはフォーム内の入力項目を機械的に入力することが多いため、この項目に値が入っていた場合はBotと判断できます。

入力画面(HTML)

<input type="text" name="website" tabindex="-1" autocomplete="off" style="position: absolute; left: -9999px;">

送信処理(PHP)

if(!empty($_SESSION['website'])){
exit('不正なアクセスです。');
}

利用者が追加の操作を行う必要がないため、手軽に導入できる対策です。

フォーム表示から一定時間以内の送信を制限

Botはページを開いた直後に送信するケースが少なくありません。
そこで、フォーム表示から一定時間以内の送信を拒否します。

入力画面(PHP)

$_SESSION['time'] = time();

送信処理(PHP)

if(time() - $_SESSION['time'] < 5){
	exit('不正なアクセスです。');
}

サンプルは5秒を基準としていますが、上記数字を変えることで自由に変更できます。

同一IPアドレスからの連続送信を制限

Botは短時間で何度も送信を試みます。
そこで、同じIPアドレスから一定時間以内の送信を拒否します。
今回は専用メソッドとしてcheckLimit()を作成しました。

送信処理(PHP)

$ip = new ValidationClass($_SERVER['REMOTE_ADDR']);

if($ip->checkLimit(__DIR__.'/log/ip.log', 30) == 1){
	exit('短時間での連続送信はできません。');
}

サンプルは30秒を基準としていますが、上記数字を変えることで自由に変更できます。

共通メソッド(PHP)

class ValidationClass {
	protected $value;

	function __construct($v) {
		$this->value = $v;
	}

	//一定時間内の連続送信チェック
	function checkLimit($logfile, $limit = 30){
	
		$value = $this->value;
		$now = time();
		$records = array();
		$blocked = false;
	
		$fp = fopen($logfile, 'c+');
	
		if($fp === false){
			return(2);
		}
	
		if(flock($fp, LOCK_EX)){
	
			while(($line = fgets($fp)) !== false){
	
				$data = explode("\t", trim($line));
	
				if(count($data) != 2){
					continue;
				}
	
				list($time, $dataValue) = $data;
	
				// 制限時間以内のデータだけ保持
				if(($now - $time) < $limit){
	
					// 同一データなら時刻を更新
					if($dataValue == $value){
	
						$time = $now;
						$blocked = true;
	
					}
	
					$records[] = array(
						'time' => $time,
						'value' => $dataValue
					);
	
				}
	
			}
	
			// ファイルを書き直す
			ftruncate($fp, 0);
			rewind($fp);
	
			foreach($records as $row){
	
				fwrite($fp, $row['time']."\t".$row['value'].PHP_EOL);
	
			}
	
			// 新規データなら追加
			if(!$blocked){
	
				fwrite($fp, $now."\t".$value.PHP_EOL);
	
			}
	
			fflush($fp);
			flock($fp, LOCK_UN);
	
		}
	
		fclose($fp);
	
		if($blocked){
			return(1);
		}
	
		return(0);
	
	}	
}

今回の実装では、データベースは使用していません。
送信履歴は30秒程度保持できれば十分なため、テキストファイルで管理しています。
保存形式は非常にシンプルです。

IPアドレスの場合

1751878200	xxx.xxx.x.xx
1751878215	yyy.yyy.y.yy

メールアドレスの場合

1751878200	user@example.com
1751878215 info@example.com

左側はUnixタイムスタンプ、右側は判定対象となるデータです。
送信時には古いデータを自動的に削除し、最新の送信時刻を書き込みます。

また、同じIPアドレスやメールアドレスから制限時間内に再度送信が行われた場合は、送信を拒否するだけでなく最終アクセス時刻へ更新しています。これにより、「最後の送信試行から一定時間経過するまで送信できない」仕組みとなり、繰り返し送信を試みるBotにも効果があります。

尚、今回の連続送信制限では、送信履歴を保存するために「log」ディレクトリを用意しています。
送信履歴としてIPアドレスやメールアドレスが保存されますので、ブラウザから直接アクセスできないようにlogディレクトリに「.htaccess」等で保護することを推奨します。

.htaccess

Deny from all

同一メールアドレスからの連続送信を制限

IPアドレスだけでは不十分な場合があります。
同じメールアドレスを利用して連続送信するケースも考えられるため、メールアドレスでも判定します。

送信処理(PHP)

$mail = new ValidationClass($_SESSION['email']);

if($mail->checkLimit(__DIR__.'/log/mail.log', 30) == 1){
	exit('短時間での連続送信はできません。');
}

IPアドレスとメールアドレスの両方で判定することで、より効果的なスパム対策になります。

まとめ

今回ご紹介した方法は、

  • CSRF対策
  • ハニーポット
  • フォーム表示から一定時間以内の送信制限
  • 同一IPアドレスからの連続送信制限
  • 同一メールアドレスからの連続送信制限

という5つの対策です。

これらを組み合わせることで、Botによる大量送信や単純なスパム投稿を大きく減らすことが期待できます。
一方で、人が実際に入力して送信する営業メールまで完全に防ぐことはできません。それでも、お問い合わせフォームを安全に運用するためには、複数の対策を組み合わせることが重要です。

今回ご紹介した内容は、特別なライブラリやデータベースを使用せず、PHPだけで実装できます。既存のメールフォームにも比較的導入しやすいため、スパム投稿でお困りの方はぜひ参考にしてみてください。

RANKING

人気記事

同一カテゴリーの記事