spamp : 掲示板スパムバリデータ
spamp とは
spampは、ロボットによるスパム投稿を判定するライブラリです。PHPで書かれた掲示板やブログなど、既存のPHPアプリケーションにスパム判定機能を加えます。
掲示板スパムボットには JavaScript による対策が極めて有効ですが、そうもいかない諸事情に対応するため JavaScript・Cookie・Session の状況に依存することなく、掲示板スパムボットを判定できることを最重点にしています。
spampは、マルチバイトを含まない場合はじく、投稿出来るURLを1つに限定する、プロキシっぽいクライアントを拒否する、といったフィルタはまったく使っていません。またCAPTCHA(画像認証)やパスワードによる認証方式でもありません。
掲示板スパムボットのプロファイリングから生まれたロジックでスパム判定をおこなう点がこのスクリプト最大の特徴です。とても単純なのでこのページを読み進めていくとどのようなロジックかおわかり頂けると思います。
補助的に、IPアドレスとキーワードによるフィルタリング機能も備えていますので、ロジックでは判定出来ない手動スパム投稿などはこれで対応可能です。
このページでは導入までを簡単に説明しています。
カスタマイズや機能拡張については以下のドキュメントもあわせてご覧下さい。
適用
サーバー環境
- PHP4.3以降、PHP5.2でも動作テスト済みです。
ココの掲示板とブログ(WordPress)にて検証しました。 - 掲示板やブログのコメントなど、特定のウェブフォームを使って投稿するものなら、どのようなPHPスクリプトにも使えます。
- 特定のフォームが無いトラックバックや、メール・書き込みツールからの投稿を許可しているアプリケーションには対応していません。
クライアント環境
- 一般的なブラウザは大抵パスします。↓これらはテスト済です。
IE7, IE8, Firefox3, GoogleChrome4, Safari4, Opera10 - テキストブラウザには対応していません。必ずSPAMと判定します。また、モバイル端末等IPアドレスがコロコロ変わるクライアントは誤ってSPAMと判定する場合があります。
- このようなクライアントには、許可IPリストを併用することで適用可能です。
- [2010-05-04] FireFoxだけ、1/2の確率で画像がキャッシュされ、セッションが確立できずに投稿に失敗する現象を確認しています。「1度でもセッションが確率できたクライアントはスパムボット判定からを除く」方向で仕様変更を検討します。
ダウンロード
- spamp ver. 1.0.1 (zip) - 2010-05-04
- 個々のファイルは archives で見れます。
掲示板スパムボットの特長
あらかじめ掲示板スパムボットの特長を知っておくとこのスクリプトが最大限に活かせます。
投稿フォームと投稿先以外にリクエストは無い。他のページはもちろんのこと、画像やJavascript、CSSなど投稿フォームに付随するファイルも一切リクエストが無い。
『積極的なスパム投稿対策 》スパム投稿の特徴』より
ならば、フォームに付随するファイルをリクエストしたクライアントだけ投稿を許可すればいいじゃないか。というのがこれを書き始めたきっかけです。
正規のフォームをリクエストしてきた一般的なブラウザは、画像を通じてサーバー側に足跡を残し、投稿された際にその足跡で判定する、ワンタイムチケットによる方法をとっています。このスクリプトではこの判定をセッションと呼んでいます。
もうひとつ、大きな特徴として リクエスト間隔が極めて短い ことが挙げられます。投稿フォームのリクエストから投稿があるまで1~2秒、長くても4秒までです。人の手でこんな短時間で投稿することは難しいので、ほんの数秒「待ち時間」をもうけることでせっかちなスパムボットを判定しています。これはCookieとセッションの両方に実装しています。
この特長さえわかっていれば、アイデア次第でこのスクリプトよりもっとスマートな対策が取れると思います。
Spampでは判定媒体にCookieを使っていますが、スパム(セッションが確立できなかったクライアント)をクッキーだけで継続して判定するためだけに使っており、Cookie未実装クライアントが CookieでSPAMと判定されることはありません。
なお、spamp は「スパンプ」と呼ぶことにしています。
「SPAM には、屁でもかましたれ!」という意味も無くはないですが、投稿を許可するスタンプでSPAMを判別する特徴から「スタンプ(Stamp)」をもじってつけたものです。
spampを構成するファイル
spamp.php- spamp クラス本体spamp.conf.php- 設定ファイルaccess.php- セッション開始コマンドallow.host.php- 許可IPリストdeny.host.php- 拒否IPリストdeny.word.php- 拒否ワードリストreadme.txt- 簡単な説明書(このページとほぼ同等の内容。実行時は不要。)
ファイルの文字コードは、UTF-8 です。
おすすめするディレクトリ構成
/home/userdir/lib/spamp(1)spamp.phpspamp.conf.phpallow.host.phpdeny.host.phpdeny.word.php/home/userdir/tmp/spamp_work(2)/home/userdir/htdocs(3)access.php
(1) spamp ライブラリのディレクトリ
任意の場所ではかまいません。spamp.php は他のファイルから読み込んで使うので、include_path の通ったディレクトを推薦します。
(2) 作業用ディレクトリ (読み書き属性必須)
spamp はセッションやリストのキャッシュなど一時的なファイルを読み書き出来るディレクトリをひとつ必要とします。DocumentRoot以下でなければ任意の場所ではかまいません。専用のディレクトリを作成しウェブサーバーの権限で読み書き出来る属性を与えておきます。
(3) DocumentRoot以下
access.php だけはクライアントから直接アクセス出来る必要があるので必ず DocumentRoot 以下に配置します。DocumentRoot 以下ならばディレクトリは問いません。
最少限必要な設定
使い始める前に少なくとも2つのファイルで計6ヶ所設定します。
access.php の設定箇所
/**
* Load Spamp class
*/
require_once '/home/userdir/spamp/spamp.php';
(1) spamp.php のパスを指定します。必ず実際に配置したパスに書き変えてください。
spamp.conf.php の設定箇所
/**
* Signature
*
* @var string $signature unique strings
*/
$signature = 'u5*a0(9gil#4krxb-s;g%t7~yd2';
(2) ユニークなIDを生成するためのキーとなる文字列を設定します。文字種や文字数は問いませんので、必ず管理者固有の文字列に書き変えてください。
/**
* Work directory
*
* Writable attribute
*
* @var string $workDir directory. default 'configrationfile_dir/work'
*/
$workDir = '/home/userdir/tmp/spamp_work';
(3) spamp はセッションやキャッシュなど一時ファイルを保存する作業用ディレクトリをひとつ必要とします。DocumentRoot下でなければ場所は問いませんのでSpamp専用のディレクトリをつくり、ウェブサーバの権限で読み書きできる属性を与えておきます。
/**
* Internal encoding
*
* @var string $internalEncoding encoding name. default 'UTF-8'
*/
$internalEncoding = 'UTF-8';
//$internalEncoding = 'EUC-JP';
(4) Spampがデータとして取りあつかう文字エンコーディング名を指定します。この文字エンコーディングは拒否ワードリストやログファイルの出力に影響します。
通常は対象の掲示板スクリプトやブログが内部で用いている文字エンコーディングに合わせてください。多くの場合 php の mbstring.internal_encoding になると思います。
/**
* Cookie path
*
* @var string $cookiePath path. default '/'
*/
$cookiePath = null;
//$cookiePath = '/';
(5) Cookieを有効とするパスを指定します。DocumentRootが自分の管理下にある場合はデフォルトで構いませんが、共用サーバーやプロバイダのホームページスペースなどで /~YourHome みたいなURLが自分のホームの場合は必ずそれ以下のパスに書き換えます。
/**
* Cookie Domain
*
* @var string $cookieDomain your domain. default $_SERVER['HTTP_HOST']
*/
$cookieDomain = null;
//$cookieDomain = $_SERVER['HTTP_HOST']; // default
//$cookieDomain = 'www.sound-uz.jp'; // your domain
(6) Cookieを有効とするドメインを指定します。www 有り無しなどサブドメインを複数使って運用しているなど特定のドメインからのみCookieが有効となるよう書き換えます。
spamp をアプリケーションに組み込む
投稿フォームにイメージタグを書き加える
まずは投稿フォームのHTMLに以下のようにイメージタグを書き加えます。
投稿フォームのHTMLの記述例
<img src="http://*****/access.php" width="1" height="1" alt=""/>
src="..." には、実際に配置した access.php のURLを記述します。イメージタグを書き込む位置は、フォームが完全に表示された後が望ましいのでできるだけページの下部がおすすめです。
access.php はレスポンスとして 1px×1px の透明GIFと一緒にクッキーを送信します。さらにサーバー側ではクライアントの情報を元にセッションを作成します。
受信側スクリプトにスパム判定コードを書き加える
続いて投稿を受ける側(投稿処理)のスクリプトに書き加えるのは3点です。
- spamp クラスを読み込み
- spamp オブジェクト生成と初期化
- SPAM判定と後処理
受信側スクリプトへの記述例
<?php /** * 1. spamp クラスを読み込み */ require_once '/home/userdir/lib/spamp/spamp.php'; /** * 2. spamp オブジェクト生成と初期化 */ $Spamp = new spamp(); /** * 3. SPAM判定と後処理 * * スパムでなければ 0 を、スパムと判定すれば 1 以上の整数を返します。 * * @return integer 0: No SPAM, 1: Cookie, 2: Session, 4: DenyHost, 8: DenyWord */ if ($Spamp->isSpam()) { // スパムと判定した時の処理例 header('HTTP/1.0 403 Forbidden'); exit(); // スパムに本文を返す必要なし。エコ・トラフィックでいきましょう } $Spamp->removeSession(); /* * これ以降は、スパムでない場合の投稿処理・・・ */
スパムの判定は、isSpam() をコールするだけです。
isSpam() は、クライアントが送ってきた値を元に次の順で評価し、スパムでなければ 0 を、スパムと判定すれば 1以上の整数を返します。
1. Cookieの待ち時間と整合性(戻り値:1)
2. セッションの待ち時間と有無(戻り値:2)
3. 許可IPリスト (allow.host.php)
4. 拒否IPリスト (deny.host.php) (戻り値:4)
5. 拒否ワードリスト (deny.word.php) (戻り値:8)
1と2でスパムと判定した場合に限って許可IPリストによるチェックを行います。許可IPリストに該当しなかった場合、これ以降の判定は行わずすぐに結果を返します。
1と2でスパムと判定されなかった場合と許可IPリストに該当する場合は、続いて4と5のチェックが行われます。すべてのチェックをパスすれば戻り値は 0 です。
さらに、スパムと判定した場合に限り、isSpam() はあえて整合性のとれてない Cookieをクライアントに送信します。これにより、一度スパムと判定したクライアントを継続してスパムと判定し続けます。
あとは isSpam() の戻り値を元に、適切に処理を振り分けてください。
尚、セッションは、$lifeTime で設定した時間が過ぎるまで生きていますので不要となればすぐに $Spamp->removeSession() を呼んで破棄します。
なお、ここで言う「セッション」は Spamp 独自のモノで PHP の $_SESSION とはまったく関係ありません。また、Cookieのチェックは Cookieがある場合だけ行うので Cookie非対応のクライアントがCookieでスパムと判定されることはありません。
投稿フォーム出力スクリプトにクッキー送信コードを書き加える
ここまでの段階で基本的に使えるようになっています。が、さらに投稿フォームを出力前にクッキーを送信することにより、Spampの判定ロジックが100%活かされ効率良くスパム判定を行うことが出来ます。
投稿フォーム出力スクリプトの記述例
<?php /** * 1. spamp クラスを読み込み */ require_once '/home/userdir/lib/spamp/spamp.php'; /** * 2. spamp オブジェクト生成と初期化 */ $Spamp = new spamp(); /** * 3. SPAMクッキー送信 * * 引数 true で整合性のとれてないクッキー送信を送信。 */ $Spamp->sendCookie(true); /* * これ以降は、通常の投稿フォーム出力処理・・・ */
sendCookie() は 引数 true を指定すると整合性のとれてないクッキーを送信します。あえて整合性のとれてないクッキーを送信することでクッキーの確認だけでスパムを判定ができるので、セッションや拒否リストチェックによるファイルアクセスがなくなり余分な負荷を軽減できます。
(スパム判定のために大切な資源を浪費できませんって。)
尚、クッキーは、何か出力した後からでは送信できませんので、あらゆる出力処理の前に記述する必要があります。
許可リストと拒否リストの編集
spampのクッキやセッションで判定出来ない手動スパムや携帯端末は、拒否IPリストと許可IPリストを使って補うことが出来ます。
各リストファイルは、1件1行の単純なリスト構造のテキストファイルです。エディタで簡単に編集できます。
行頭の # から行末までは読み飛ばされますので保守しやすいよう適当にコメントをつけておきましょう。データ行の後に #コメント をつけることはできません。
- allow.host.php - 許可IPリスト
- deny.host.php - 拒否IPリスト
この2つは、IPv4形式(***.***.***.***)または、CIDR形式(***.***.***.***/***)で記述します。
- deny.word.php - 拒否ワードリスト
拒否ワードリストにマルチバイト文字を加える場合は、ファイルのエンコーディングを設定ファイル spamp.conf.php の $internalEncoding に必ず合わせる必要があります。
尚、リストファイルにPHPスクリプトの拡張子をつけていますがこれは、やむなくこれらをDocumentRoot下に配置しなければならない場合の情報漏えい策です。spamp内部では単純にテキストファイルとして処理していますのでリスト中にコードを書くことはできません。
allow.host.php にはあらかじめ国内の代表的なモバイル端末のIPリストを記載しています。これを本当に必要とするのはおそらくi-mode だけだと思いますが、なにせ古い携帯端末も現役で活躍しており、これらを含めた沢山の端末を試すことも実際不可能なため、不要だとわかるIPアドレスは使用者自身で削除してください。
以上で、スパム投稿をフィルタリングする準備OKです。
Spampのクラスリファレンスと設定ファイルのリファレンスを用意してますので、環境に合わせてカスタマイズしたい場合にご利用ください。
このスクリプトに関する、ご意見・ご感想・ご要望などは、出来る範囲でお応えしたいと思いますので 掲示板 にご投稿ください。
また、このようなフィルタリングだけでなく、スパムのリクエストそのものを減らす対策も必ず実施し、掲示板スパム、コメントスパムを撲滅しましょう。
TODO
- IPがコロコロ変わる携帯端末対応にも知恵を絞ってみる。
2010-05-04 今後モバイル端末がますます増える傾向にあるので、スパム判定要素からIPアドレスを除く方向で仕様変更を検討中です。 - テキストブラウザ対応に知恵を絞ってみる
更新履歴
- 2010-05-04 ver. 1.0.1
- $lifeTime切れのセッションファイルが2日以上残っていることがあるのでセッションファイルの削除頻度を倍にアップ。
- putImage() のレスポンスヘッダに'Pragma: no-cache'を追加。
- 1.0 からの更新は、spamp.php を差し替えるだけです。他のファイルについては 1.0 と変わりありません。
- 2010-04-12 ver. 1.0 release
