BBSantispam - 掲示板CGIに対してアンチスパム機能を提供するライブラリ
use BBSantispam; $antispam = BBSantispam::new; : if ($antispam->is_black_word($message)) { &error("禁止語句が含まれています"); } : ($time, $key) = $antispam->get_post_wait; print "<input type=hidden name=wait value=$time>\n"; print "<input type=hidden name=wait_key value=$key>\n"; : if ($antispam->is_too_fast_post($arg{'wait'}, $arg{'wait_key'})) { &error("投稿が速すぎるか、引数が改竄されてます"); }
BBSantispam は、掲示板CGIへのスパム投稿を防ぐ以下のような機能を提供します。
以下の判定を行うメソッドを提供します。
IPアドレスやホスト名のブラックリスト、ホワイトリスト
禁止語句のブラックリスト
フォーム表示から投稿までが速すぎる
ホストのIPアドレスが逆引きできる
逆引きして得たホスト名が正引きできる
英文のみの投稿
投稿文中にURLがたくさん含まれている
想定していないHTTPメソッドでのアクセス
HTTPヘッダにおかしな情報が含まれている
CGI引数用のランダムな文字列を提供し、POSTのみを行ったり、フォーム取得と投稿を異なるホストから行うロボットからのスパム投稿を防ぎます。
ログファイルを作成する機能を提供します。 スパムと判断したアクセスや投稿内容を簡単にログに保存できます。
既存のCGIにできるだけ簡単に組み込めるようなコンビニエンスメソッドを用意しています。(version 1.7より)
新しい BBSantispam オブジェクトのコンストラクタを作成します。
コンフィグファイルを読み込みます。
CONFIG_FILE
を指定すればそれをコンフィグファイルとみなします。
指定しない場合はデフォルトのコンフィグファイル
(カレントディレクトリのBBSantispam-config.cgi)を読み込みます。
read_config を複数回呼び出した場合、設定がどんどん追加されていきます。 これを利用して設定ファイルを分割管理することも可能です。
コンフィグ項目 KEY
に対して値 VALUE
を設定します。
コンフィグ項目 KEY
を削除します。
KEY
が省略された場合、すべてのコンフィグを削除します。
コンフィグ項目 KEY
の値を読み出します。
返り値: コンフィグ項目 KEY
に対応する値を返します。
すべてのコンフィグ内容をテキスト形式で取得します。デバッグ用。
返り値: コンフィグ内容を表す文字列。
アクセスしてきたホストが、IPアドレスやホスト名のブラックリストに一致するかどうかを判定します。
ブラックリストはコンフィグの black_host で設定します。
HOST
を省略した場合は環境変数REMOTE_ADDRの値が使用されます。
返り値: ブラックリストに一致しない場合は空文字列 ``'' を返します。
一致した場合は一致したリスト項目を返し、reason() メソッドで black host HOST
(HOST
は一致したブラックリスト項目)という情報を得ることができます。
アクセスしてきたホストが、IPアドレスやホスト名のホワイトリストに一致するかどうかを判定します。
ホワイトリストはコンフィグの white_host で設定します。
HOST
を省略した場合は環境変数REMOTE_ADDRの値が使用されます。
返り値: ホワイトリストに一致しない場合は空文字列 ``'' を返し、一致した場合は一致したリスト項目を返します。
アクセスしてきたホストのIPアドレスが逆引きできるか、また逆引きして得たホスト名が正引きできるかを判定します(正引き・逆引きの一致までは判定しません)。
それぞれの判定を行うかどうかはコンフィグの deny_unresolv_address および deny_unresolv_host で設定します。
HOST
を省略した場合は環境変数REMOTE_ADDRの値が使用されます。
返り値: 逆引きも正引きも問題なければ 0 を、逆引きできなければ 1 を、正引きできなければ 2 を返します。
アクセスしてきたホストのIPアドレスがBBQにより公開プロキシと判断されるかどうかを判定します。
判定を行うかどうかはコンフィグの bbq で設定します。
HOST
を省略した場合は環境変数REMOTE_ADDRの値が使用されます。
返り値: 公開プロキシと判断されれば 1 を、問題なければ 0 を返します。
アクセスしてきたホストが、GeoIPによる国コードのブラックリストに一致するかどうかを判定します。
ブラックリストはコンフィグの black_geoip で設定します。
HOST
を省略した場合は環境変数REMOTE_ADDRの値が使用されます。
返り値: ブラックリストに一致しない場合は空文字列 ``'' を返します。
一致した場合は一致したリスト項目を返し、reason() メソッドで black geoip COUNTRY
(COUNTRY
は一致した国コード)という情報を得ることができます。
GET/HEAD以外のHTTPメソッドでアクセスされているかどうかを判定します。
METHOD
を省略した場合は環境変数REQUEST_METHODの値が使用されます。
本来GETでしかアクセスされないはずのCGIに対してPOST等でアクセスしてくるクライアントを判別するために使用します。
返り値: GET/HEADメソッドの場合は 0 を、それ以外の場合は 1 を返します。
HTTPのHost:ヘッダ(環境変数HTTP_HOST)に特定の文字列が含まれているかどうかを判定します。
マッチする文字列はコンフィグの illegal_http_host で指定します。
STRING
を省略すると環境変数HTTP_HOSTの値を使用します。
返り値: マッチした場合はそのパターンを返します。マッチしない場合は 0 を返します。
HTTPのReferer:ヘッダ(環境変数HTTP_REFERER)に特定の文字列が含まれているかどうかを判定します。
マッチする文字列はコンフィグの illegal_http_referer で指定します。
STRING
を省略すると環境変数HTTP_REFERERの値を使用します。
返り値: マッチした場合はそのパターンを返します。マッチしない場合は 0 を返します。
コンフィグの black_word で設定したブラックリストの語句が MESSAGE
に含まれているかどうかを判断します。
含まれている場合は一致した語句を返します。
含まれていない場合は 0 を返します。
CGI引数用のランダムな文字列を生成します。
NUM
は生成する文字列の個数を設定します。
省略した場合、生成される文字列は1つです。
SEED
はランダムな文字列の種となる文字列を指定します。
省略した場合、コンフィグの random_seed が使用されます。
SPAN
はランダムな文字列の有効期限を指定します。
``hour''を指定すると1時間、``day''なら1日間です。
省略した場合``hour''とみなします。
HOST
はアクセス元を一意に表す文字列を指定します。
通常はアクセス元のIPアドレスです。
省略した場合はアクセス元のIPアドレスを使用しますが、
アクセス元が white_host に一致した場合はその一致パターンが使用されます。
set_random_args で生成した文字列をリストの形式で返します。
SET
に1を指定すると、 set_random_args の SPAN
で指定した期限の1つ前の文字列リストを返します。
(例えば SPAN
で``hour''を指定した場合、 random_args(1) は1時間前に生成されたであろう文字列リストを返します)
CGIの引数のリストを LIST
で与えると、ランダムなCGI引数と思われるものを抽出します。
CGIの引数が random_args で得られるどの文字列にも一致しない場合に使用します。
(これで引数を探す必要があるというのは、おそらく何らかのspam投稿でしょう)
投稿を許可する時間情報を返します。
INTERVAL
は、このメソッドを呼び出してから投稿を許可するまでの時間を秒で指定します。
省略した場合、コンフィグの post_wait が使用されます。
返り値: 投稿を許可する時間(現時点のtime()の返り値に INTERVAL
を加えた数字)と、その値を検証するためのハッシュキーからなるリストを返します。
この2つの値をCGIのフォームにhidden属性で埋め込み、後で is_too_fast_post での検証に利用します。
get_post_wait のコンビニエンス関数で、以下のようなHTMLコードを直接得ます。
<input type=hidden name=ARG1 value="TIME"> <input type=hidden name=ARG2 value="HASHKEY">
TIME
と HASHKEY
は get_post_wait メソッドの返り値です。
投稿可能な時間かどうかを判定します。
TIME
と HASHKEY
は get_post_wait で得た値です。
返り値: 投稿可能な時間になっていれば 0 を、そうでなければ 1 を、 TIME
の値が改竄されている場合は 2 を返します。
MESSAGE
が英文のみかどうかを判別します。
判別するかどうかはコンフィグの deny_ascii_post で設定できます。
返り値: 英文のみならば 1 を、そうでなれば 0 を返します。
MESSAGE
にURLと思われる文字列が NUM
個以上含まれているかどうかを判別します。
NUM
を省略した場合、コンフィグの max_url が使用されます。
NUM
が -1 以下の場合は何もしません。
返り値: URLが NUM
個以上ならば 1 を、そうればければ 0 を返します。
ログを出力します。
出力内容の引数 LIST
はリストの形式で与えます。
ログファイル名はコンフィグの spamlog で設定します。 spamlog が設定されていない場合はログファイルは生成されません。
ログの形式はいわゆるCSVで、区切り記号はコンフィグの spamlog_separator で設定します。 設定がなければタブ文字が使用されます。
またログファイルの第1カラム目は、ログが記録された時間(time関数の返り値)とCGIのPIDをピリオドで連結したものになります。
ログファイルを読み出し、HTML のテーブルに整形した結果を得ます。 引数の与え方によって「一覧表示」と「詳細表示」が可能です。
FORMAT
は、ログファイルの各項目名と、各項目の一覧表示での書式を指定します。
書式は以下の1文字で指定します。
そのまま出力します。
出力しません。
右寄せで出力します。
「詳細表示」へのリンクを「click」という文字列で出力します。
書式に続けて任意の項目名を指定します。 書式と項目名の組は、カンマで区切って必要個数記述できます。
例えばログの項目が『日時』『理由』『IPアドレス』『名前』『メール』『題名』『URL』『コメント』の順で並んでおり、 「『日時』『理由』『IPアドレス』は表示、『名前』『メール』『題名』『URL』は非表示、『コメント』はリンク」としたい場合は、次のように指定します。
".日時,.理由,.IPアドレス,.名前,.メール,.題名,.URL,Lコメント"
OPTION
が指定されていない場合は一覧表示を、「p(数字)」が指定されている場合は指定ページを、ログの記録時間が指定されている場合は指定されたログの詳細表示を行います。
CGI
は詳細表示へのリンクを出力する際のCGI名です。
省略した場合は環境変数 SCRIPT_NAME, REQUEST_URI や $0 から推測したCGI名が使用されます。
投稿やアクセスがspamであると各メソッドが判断した場合、その判断理由をこのメソッドで取り出すことができます。
HOST
の名前解決をします。
返り値: HOST
がIPアドレスの場合は、逆引きしたホスト名を返します。
HOST
がホスト名の場合は、正引きしたIPアドレスを返します。
複数のIPアドレスが設定されている場合はそれらを空白で連結した値を返します。
ランダム引数ARG
から、フォームを取得したホストのIPアドレスと、フォーム取得日時を得ます。
返り値: IPアドレスとUNIX timeからなるリストを返します。
spamチェック用のタグを埋め込むためのHTMLコードを返します。
spamチェックをまとめて行います。 詳細は後述の「掲示板プログラムに組み込むにあたって(簡易バージョン)」を参照してください。
コンフィグファイルは「設定項目=設定値」という書式です。 行頭が「#」または「//」の行はコメントとして扱われます。 同じキーワードが複数回出てきた時は、設定値はスペースで区切られて連結されます。
例: 以下の2つの設定の結果は同じ。
#まとめて書く black_host=192.168.0.1 host.example.com
#分けて書く black_host=192.168.0.1 black_host=host.example.com
注意: コンフィグファイルを設定しても、掲示板プログラム側で BBSantispam の各メソッドを利用した判定ルーチン等を組み込んでいなければ、意味がありません。
関連メソッド: is_black_host
接続元(掲示板閲覧者/投稿者)のIPアドレスやホスト名のブラックリストを設定します。
HOST
はホスト名またはIPアドレスで、アスタリスク(*)を使ったワイルドカードでの記述も可能です。
またホスト名が「regex:」で始まる場合は、perlの完全な正規表現でパターンを記述することができます。
例:
black_host=192.168.0.1 172.16.0.* host.example.com *.example.jp black_host=regex:p[0-9]*-ip.*\.example\.jp
関連メソッド: is_white_host, set_random_args
接続元(掲示板閲覧者/投稿者)のIPアドレスやホスト名のホワイトリストを設定します。
HOST
はホスト名またはIPアドレスで、アスタリスク(*)を使ったワイルドカードでの記述も可能です。
またホスト名が「regex:」で始まる場合は、perlの完全な正規表現でパターンを記述することができます。
例:
white_host=192.168.0.1 172.16.0.* host.example.com *.example.jp white_host=regex:p[0-9]*-ip.*\.example\.jp
関連メソッド: is_unresolv_host
1を設定すると、接続元(掲示板閲覧者/投稿者)のIPアドレスが逆引きできるかどうかを判定します。
関連メソッド: is_unresolv_host
1を設定すると、接続元(掲示板閲覧者/投稿者)のIPアドレスを逆引きして得たホスト名が正引きできるかどうかを判定します。
関連メソッド: is_bbq
1を設定すると、BBQにより公開プロキシのチェックを行います。
関連メソッド: is_black_geoip
GeoIPを利用した国単位のブラックリストを設定します。
COUNTRY
は国コードです。例えば日本は JP
、イギリスは GB
となります。
例:
#ロシア・中国・韓国・台湾・香港からのアクセスを拒否 black_geoip=RU CN KR TW HK
関連メソッド: is_black_word
禁止語句のブラックリストを設定します。
WORD
は禁止したい単語です。
例:
black_word=禁止ワード ブラックリスト black_word=www.adult-site.example.com
関連メソッド: is_ascii_post
1を設定すると、投稿内容が英字のみかどうか(日本語を含まないかどうか)を判定します。
関連メソッド: is_many_url
spamとみなすURLの数を指定します。
関連メソッド: get_post_wait
フォーム表示から投稿可能になるまでの時間を指定します。
関連メソッド: check
フォーム表示から投稿が不可能になるまでの時間を指定します。
注意: これはメソッドcheck用であり、ランダム引数の方とは関係ありません。
関連メソッド: is_illegal_method
1を設定すると、POST/GET以外のHTTPメソッドかどうかを判定します。
関連メソッド: is_illegal_http_host
HTTPのHost:ヘッダ(環境変数HTTP_HOST)に含まれていてはいけない文字列を指定します。
関連メソッド: is_illegal_http_referer
HTTPのReferer:ヘッダ(環境変数HTTP_REFERER)に含まれていてはいけない文字列を指定します。
関連メソッド: set_random_args, get_post_wait, is_too_fast_post
ランダムなCGI引数や、投稿までの時間を検証するハッシュキーを生成するための種(シード)を設定します。
ここで設定する値は BBSantispam を利用するにあたって特に覚えておく必要はありません。 推測や辞書攻撃を防ぐために、十分に長い日本語やランダムな文字列を設定してください。 文字数制限は特にありません。
例えば以下のような方法を推奨します。
注意: 掲示板の設置時には、デフォルトの設定値から必ず変更してください。
関連メソッド: log, view_log
出力するログファイル名を指定します。 指定しない場合はログファイルは生成されません。
関連メソッド: log, view_log
出力するログファイルの、各項目を区切るセパレータの文字を16進数で指定します。
関連メソッド: lock, unlock, log
log メソッドでログを出力する際の、排他処理を行うためのロックを行う方式を指定します。
mkdir()関数でディレクトリを作成してロックを行います。
symlink()関数でシンボリックリンクを作成してロックを行います。
ロックは行いません。(場合によってはログファイルが破壊されますので、お勧めできません)
もっとも多くのOSで動作するのはおそらく mkdir ですが、 組み込み対象の掲示板プログラムに合わせるのが一番確実です。
関連メソッド: lock, unlock, log
ロックを行う際の最大待ち時間を指定します。
関連メソッド: lock, unlock, log
ロックファイル名を指定します。
関連メソッド: set_random_args, search_args
ランダム引数の長さを指定します。
通常は IPアドレス情報で 8文字 + 時間情報で 8文字 + md5_hex()
が生成する 32文字 + 末尾に 1文字追加の計 49文字になるので 49を指定します。
関連メソッド: set_random_args
ランダム引数が変化する時間の間隔(=ランダム引数の寿命)を指定します。
関連メソッド: view_log
ログを表示する場合の、1ページあたりの件数を指定します。
1時間毎に変化します。
1日毎に変化します。
関連メソッド: check
メソッドcheckでspamと判断した際の処理を指定します。 1を指定すると「error.」とだけ表示してCGIを終了します。 2を指定すると判断理由を表示してCGIを終了します。 0を指定するとCGIを終了しませんので、checkの返り値を見て適切な処理を行うコードを追加してください。
BBSantispam を掲示板プログラムに組み込む時のヒント。
コンビニエンスメソッド check()
を使った簡単な方法です。
(BBSantispamの大半の機能が利用できますが、すべての機能は利用できません)
プログラムの先頭に
use BBSantispam;
を、またプログラムの序盤に
$antispam = BBSantispam::new; $antispam->read_config;
を入れ、 $antispam でメソッドを呼び出せるようにしてください。 (perlのオブジェクト指向プログラミングについては、perlの書籍等を参考にしてください。 ここでは特に触れません。)
投稿フォームを表示している部分に以下を追加します。
print $antispam->check_html;
これにより以下のようなチェック用のタグが挿入されます。
<input type=hidden name=BBSantispam1 value="host=10.0.0.1 time=1151289314"> <input type=hidden name=BBSantispam2 value="2a0c38abf14b7099a79a77a23ca53571">
投稿を受け付ける部分に以下のようなコードを追加します。 メソッドcheck()の引数の中身は組み込み対象のCGIに合わせてください。 以下はYY-BOARDでの例です。 (ハッシュ%inにCGIのフォームの値が格納されています)
$antispam->check( [ $in{'BBSantispam1'}, $in{'BBSantispam2'} ], [ $in{'name'}, $in{'email'}, $in{'sub'}, $in{'url'}, $in{'comment'} ], [ $in{'name'}, $in{'email'}, $in{'sub'}, $in{'comment'} ], [ $in{'comment'} ], [ $in{'name'}, $in{'email'}, $in{'sub'}, $in{'url'}, $in{'comment'} ] );
メソッドに渡す引数は5つで、それぞれは角カッコで囲みます(無名配列のリファレンス)。 角カッコの中にはチェックしたい変数を必要なだけ並べます。
1番目はチェック用タグの内容を渡します。 この情報を使用して、速すぎる投稿、遅すぎる投稿、フォームを取得したホストとは違うホストからの投稿をチェックします。
2番目は語句のブラックリスト(禁止ワード)をチェックしたい変数を並べます。
3番目はURLが多数含まれているかどうかをチェックしたい変数を並べます。
4番目は日本語が含まれていない(英字のみの)投稿をチェックしたい変数を並べます。
5番目はspamと判断した際にログに出力したい変数を並べます。 (ログを取らない場合は角カッコの中身を空にしてください)
また上記以外に、IPアドレスの逆引き・正引きのチェックや、ホストのブラックリストのチェックも行います。 チェック内容の取捨選択や具体的な内容はコンフィグで設定してください。
コンフィグでcheck_error=1と指定すると、内容が「error.」だけのエラー画面を表示してCGIを終了します。 check_error=2とするとエラーの理由が簡単に書かれたエラー画面を表示してCGIを終了します。 これらのBBSantispamの簡易エラー画面は、出力後単純にexit関数を呼び出して終了しますので、組み込み対象のCGIでロック処理などをしている場合はご注意ください。
check_error=0とするとエラー画面は表示しません。 この場合は別途エラー画面を表示したりするエラー処理コードを作成してください。 メソッドcheck()が0を返した場合はspamと判断していません。 1以上を返した場合はspamと判断しています (どの数値がどの判断基準に該当するかは、メソッドcheck()のコードをご参照ください)。 判断理由はメソッドreason()で取得できます。
spamと判断した場合ログを出力できます。 ログはタブ区切りで、出力内容は次の通りです。 時間(time関数の値)、判断理由、IPアドレス(引ける場合はホスト名も)、メソッドcheck()の第5引数で並べた変数群、フォームを取得した時のIPアドレス、フォームを取得した日時。
BBSantispam を掲示板プログラムに組み込む時のヒント。 BBSantispamの各メソッドをフルに利用する方法です。
プログラムの先頭に
use BBSantispam;
を、またプログラムの序盤に
$antispam = BBSantispam::new; $antispam->read_config;
を入れ、 $antispam でメソッドを呼び出せるようにしてください。 (perlのオブジェクト指向プログラミングについては、perlの書籍等を参考にしてください。 ここでは特に触れません。)
is_ で始まる判定系のメソッドは、そのまま条件判断に組み込んで使えます。
例: $in{'comment'}に投稿フォームの入力値が入っている場合。
if ($antispam->is_many_url($in{'comment'})) { &error("URLが多すぎます"); }
perlでCGI引数を扱う場合、ハッシュで管理することが多いと思います。 CGI引数のランダム化は基本的にそれを前提としています。
以下、例を挙げて説明します。 (既存の掲示板CGIで)フォーム内容が %in というハッシュに格納されているとします。 そして名前を $in{'name`} 投稿文面を $in{'comment'} として扱っているとします。
まずCGIプログラムを修正し、$in{'name`} は $in{$RNDname}、$in{'comment'} は $in{$RNDcomment} というようにハッシュのキーを変数にし、これで正常に動作することを確認します。 もちろん事前に $RNDname = 'name'; などと設定しておくのを忘れずに。 また既存の変数名とぶつからないようにしてください。
次にランダム化の組み込みですが、この例の場合扱う要素は 2つですので、$RNDname や $RNDcomment の初期化部分を
$antispam->set_random_args(2); ($RNDname, $RNDcomment) = $antispam->random_args;
とします。これでCGIの引数がランダム化されているはずなので、WebブラウザでHTMLソースを表示させるなどして確認してみてください。
しかしこれだけだと、set_random_args の SPAN
引数による有効期限の切れ目で引数名が変化してしまいます。
例えば 10時58分にフォームを表示した時と 11時02分に投稿した時では、プログラムが取り扱う引数名が変化してしまい投稿に失敗する、ということが発生します。
これを回避するために、フォームの値を取得する部分あたりで
if (! defined($in{$RNDcomment})) { my($RNDname2, $RNDcomment2) = $antispam->random_args(1); if (defined($in{$RNDcomment2})) { $RNDname = $RNDname2; $RNDcomment = $RNDcomment2; } }
というルーチンを組み込み、1スパン分古い引数についても面倒を見るようにします。
以下のようなラッパー関数を用意して、spamと判断したら呼び出します。
例: %in に投稿フォームの各種情報が格納されている場合。
sub spamlog { my ($name, $sub, $comment, $email, $url); unless ($in{$comment}) { ($name, $sub, $comment, $email, $url) = $antispam->search_args(keys %in); } $antispam->log($antispam->reason, $ENV{'REMOTE_ADDR'}, $in{$url}, $in{$email}, $in{$name}, $in{$sub}, $in{$comment}); }
ソースを以下のように修正することで、 Digest::MD5 の md5_hex()
の代わりに
perl 標準の crypt()
関数を使用できます。
「use Digest::MD5 qw(md5_hex);」をコメントアウトします。
「sub set_random_args」を「sub set_random_args_md5」に、 「sub set_random_args_crypt」を「sub set_random_args」に書き換えます。
コンフィグの「random_seed=」を 3文字の英数字に、 「arg_length=」を 23 に変更します。
crypt()
使用時の制限ホワイトリスに一致した場合の引数が、 通常はホワイトリストの項目毎に同じ引数が使用されるのに対し、 ホワイトリストのどの項目に一致しても同じ引数が使用されるようになります。
コンフィグの「random_seed=」は必ず 3文字の英数字でなければなりません。
MD5 の代わりに crypt を使用するので、 引数のランダム化のアルゴリズムがとても弱くなります。
DES版の crypt()
であることを前提にしています。
MD5版の crypt()
である場合、どのような挙動になるか未確認です。
またこの改造自体、ほとんどテストをしていません。
HIRAMOTO Kouji (https://flatray.com/)
あったら報告していただけると嬉しいです。
著作権は主張します。
Copyright 2006, HIRAMOTO Kouji. All Rights Reserved.
BBSantispam.pm自体の再配布
CGI等に組み込んでの配布
コードやアルゴリズムなどの流用
などは自由に行っていただいて構いませんし、特に連絡もいりません。 (でも連絡をいただけると多分喜びます :-)
しかし、丸々パクって自分の著作であると主張したりというような 恥ずかしい行為だけはご遠慮ください。