.htaccessでPHPのファイルアップロードサイズの上限値を変更
2022.02.5
PHPを使用して画像(動画・PDF等)をアップロードするフォームを作成した際に、ファイル…

最近、お客様から「お問い合わせフォームに営業メールやスパム投稿が増えて困っている」というご相談を受けました。
ホームページのお問い合わせフォームは、お客様との大切な窓口ですが、同時に営業メールや自動送信プログラム(Bot)の対象にもなりやすい機能です。
営業メールには大きく分けて「Bot(自動送信プログラム)による大量送信」「人が実際にフォームへ入力して送信する営業メール」の二種類がありますが、今回ご紹介する方法は、前者のBotによる自動送信を抑制するための対策です。
人が実際に入力して送信する営業メールまで完全に防ぐことはできませんが、機械的な大量送信には十分な効果が期待できます。
今回は、PHPだけで実装できる5つのスパム対策をご紹介します。
今回紹介するのは、次の5つです。
どれか一つだけでは十分とは言えませんが、組み合わせることでスパム投稿を大幅に減らすことができます。
より強力なBot判定が必要な場合は、Google reCAPTCHAなどの外部サービスを利用する方法もあります。ただし、入力者側の操作負担や外部サービス依存が発生するため、今回紹介したPHP単体対策と用途に応じて使い分けることが重要です。
まず実装したいのがCSRF(クロスサイト・リクエスト・フォージェリ)対策です。
CSRFとは、第三者のWebサイトから意図しないPOST送信を実行させる攻撃です。
確認画面付きメールフォームでは、送信時にランダムなトークン(受付番号のようなもの)を発行し、その値が一致するかを確認します。
//トークンの発行
$_SESSION['token'] = bin2hex(random_bytes(32));
<input type="hidden" name="token" value="<?php echo htmlspecialchars($_SESSION['token'], ENT_QUOTES, 'UTF-8'); ?>">
//トークンの照合
if(empty($_POST['token']) || empty($_SESSION['token']) || !hash_equals($_POST['token'], $_SESSION['token'])){
exit('不正なアクセスです。');
}
hash_equals() を利用することで、安全にトークンを比較できます。
ハニーポットは、人には見えない入力欄を用意する方法です。
Botはフォーム内の入力項目を機械的に入力することが多いため、この項目に値が入っていた場合はBotと判断できます。
<input type="text" name="website" tabindex="-1" autocomplete="off" style="position: absolute; left: -9999px;">
if(!empty($_SESSION['website'])){
exit('不正なアクセスです。');
}
利用者が追加の操作を行う必要がないため、手軽に導入できる対策です。
Botはページを開いた直後に送信するケースが少なくありません。
そこで、フォーム表示から一定時間以内の送信を拒否します。
$_SESSION['time'] = time();
if(time() - $_SESSION['time'] < 5){
exit('不正なアクセスです。');
}
サンプルは5秒を基準としていますが、上記数字を変えることで自由に変更できます。
Botは短時間で何度も送信を試みます。
そこで、同じIPアドレスから一定時間以内の送信を拒否します。
今回は専用メソッドとしてcheckLimit()を作成しました。
$ip = new ValidationClass($_SERVER['REMOTE_ADDR']);
if($ip->checkLimit(__DIR__.'/log/ip.log', 30) == 1){
exit('短時間での連続送信はできません。');
}
サンプルは30秒を基準としていますが、上記数字を変えることで自由に変更できます。
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秒程度保持できれば十分なため、テキストファイルで管理しています。
保存形式は非常にシンプルです。
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」等で保護することを推奨します。
Deny from all
IPアドレスだけでは不十分な場合があります。
同じメールアドレスを利用して連続送信するケースも考えられるため、メールアドレスでも判定します。
$mail = new ValidationClass($_SESSION['email']);
if($mail->checkLimit(__DIR__.'/log/mail.log', 30) == 1){
exit('短時間での連続送信はできません。');
}
IPアドレスとメールアドレスの両方で判定することで、より効果的なスパム対策になります。
今回ご紹介した方法は、
という5つの対策です。
これらを組み合わせることで、Botによる大量送信や単純なスパム投稿を大きく減らすことが期待できます。
一方で、人が実際に入力して送信する営業メールまで完全に防ぐことはできません。それでも、お問い合わせフォームを安全に運用するためには、複数の対策を組み合わせることが重要です。
今回ご紹介した内容は、特別なライブラリやデータベースを使用せず、PHPだけで実装できます。既存のメールフォームにも比較的導入しやすいため、スパム投稿でお困りの方はぜひ参考にしてみてください。

2022.02.5
PHPを使用して画像(動画・PDF等)をアップロードするフォームを作成した際に、ファイル…

2024.03.30
外部ファイル(CSVファイル等)を読み込んでサイト内に表示する際に、保存時の文字コードが…

2020.10.3
テキストエリア等で入力した文字の中にURLを含んでいた場合に、自動で認識してリンクタグを…

2020.12.5
メールフォームからメールを送信する際に、画像(ファイル)を添付できるようにしてほしいとの…

2020.08.1
動画ファイルをCMS化して保存するときに、多くの場合はファイルパスをデータベース(MyS…

2023.12.2
ワードプレスの投稿のカテゴリー機能を、目的ごとに使い分けたいというご要望があった際に、カ…

2022.09.24
パソコンやスマートフォンの戻るボタンを使って画面を戻す(ブラウザバックする)時があります…

2022.02.19
画像データ等をサーバーにアップロードする際、一点づつアップロードしても良いけれど、ファイ…

2019.12.29
メールフォーム等での入力値チェック(バリデーション)。PHPの「preg_match」(…

2020.08.15
フォームの入力値やURLのパラメータ等、文字列や数値を受け取る際に正規表現を利用してバリ…