1. Qitailang's HP
  2. webtech

単純でも効果抜群のスパム撃退法

2014/07/09

2015/04/30追記 この方法は必ずしも安全ではないように思われてきました。しかし座してスパムの餌食になるよりは次善の策を講じておくほうがましだと思います。ちなみに私はこの方法を講じてから今のところ実質的にまったく被害を受けていません。

下のページはメールアドレスに関するものですが、JavaScriptでも破られるという例です。
http://mailrobo.7jp.net/mrobo3.html

簡単にできるロボットスパム対策

掲示板、ブログ、メールフォームなどを設置したものの怒濤のスパム投稿に閉口して、閉鎖したとかコメントを受け付けないようにしている人は結構いるのではないでしょうか。私は古い良き時代の掲示板がスパムによって閉鎖あるいはスラム化したまま放置されているのを見ると寂しく残念に思います。

スクリプトやテンプレートを2〜3行書き換えるだけの簡単な改造でほとんどのスパムを弾けてしまう方法があります。それはロボットはJavaScriptの中身まで解釈できない(今のところ解釈していない)という弱点を突く方法です。

HTMLのフォームに関する知識があり、簡単なJavaScriptを書けさえすればだれにでもできる対策なのでお勧めです。ただしこの方法は、ロボットによる自動投稿に対する対策であって、人為的ないやがらせ投稿へは対処できません。

当サイトではブログや掲示板でこの対策を採っており、例えばブログではこれだけのスパムが来ていますが被害はありません。

要するにJavaScriptを使って、なんらかの方法で、掲示板スクリプトなどの投稿処理を実行させないように誘導するということなので、やり方はいくらでもあると思いますが、とりあえず一番簡単な方法を示します。

いちばん簡単な方法とその理屈

通常、投稿ページのフォームはこんな感じになっています。

おなまえ
コメント

この部分のソースは下のようになっています。(余分なHTMLは省略しています)

<form action="index.cgi" method="post">
 <input type="hidden" name="mode" value="regist" />
 おなまえ<input type="text" name="name" size="30" value="" />
 コメント<textarea name="text" cols="50" rows="10"></textarea>
 <input type="submit" name="send" value="投稿する" />
</form>

ここで「投稿する」ボタンをクリックするとブラウザは index.cgi にアクセスします。index.cgi には投稿処理のコードが書かれています。

<form name="reg" action="regist.cgi" method="post"> 
 <input type="hidden" name="mode" value="regist" />
 おなまえ<input type="text" name="name" size="30" value="" />
 コメント<textarea name="text" cols="50" rows="10"></textarea>
 <input type="submit" name="send" value="投稿する" />
</form>

そこで最初のフォームを上のように書き換えておきます。

form に name 属性を付け加えておきます。そしてaction属性の値をダミーのurl書き換えておきます。

regist.cgiは引っ掛けのためのダミーですから、「JavaScriptを有効にしてください」とかあるいは皮肉をこめて「投稿ありがとうございました」とだけ表示するような cgiなどを用意しておきます。普通のhtmlでも構わないとおもいます。(例:regist.cgi, regist.php, regist.html)。

たぶんロボットは、フォームを機械的に解析して投稿しようとし、実際には、regist.cgi、つまりダミーの url にアクセスするので、投稿には失敗してしまいます。まさか「投稿ありがとうございました」と表示されたのを見てにんまりしたりはしないでしょうが(笑)

しかしこれだけでは、本当に投稿したい人もできなくなります。そこでHTMLの最後の方に下のようなJavaScript文を書いておきます。

<script type = "text/javascript">
 document.reg.action="index.cgi";//regというnameで指定されるフォームのaction属性の値を書き換える
</script>

これでフォームの action 属性を最初に示した本来の url 書き換えます。ですから JavaScript を有効にしたブラウザからなら正しい url にアクセスし無事投稿できるというわけです。JavaScriptを使ったこのトリックを解決しないかぎりロボットは投稿に失敗すると思われます。

類似の方法

上はフォームのaction 属性をダミーのアドレスにしておく方法ですが、似たような方法はいくらでもあると思います。

簡易IPアドレス収集スクリプトを使った例

フォームのaction属性を任意のダミーurlにするだけでもよいのですが、IPアドレス収集スクリプトを使うと、どれぐらいスパムロボットを引っ掛けて撃退できているのか分かり、少し溜飲を下げることができるかもしれません。先に紹介したブログではこの対策を取っています。

<form name="reg" action="regist.cgi" method="post"> 
 <input type="hidden" name="mode" value="comment" />
 <input type="hidden" name="work" value="regist" />
 <input type="text" name="name" size="30" value="" />
 <textarea name="text" cols="50" rows="10"></textarea>
 <input type="submit" name="send" value="投稿する" />
</form>

regist.cgiはいかにもなcgi名ですが、実は投稿時の処理が書いてあるのではなく単にIPアドレスを収集するためだけのcgiです。

簡易IPアドレス収集スクリプト

  1. regist.cgiを実行権限、acclog.dat(空ファイル)を読み書き権限でサーバーにアップロードします。
  2. 掲示板、ブログ、メールフォームなどのフォームを上のように書き換えておきます。
  3. 設置できたら、試しにブラウザの JavaScript を無効にして投稿してみます。「JavaScriptを有効にしてください。」と表示されれば正しく稼働しているとみなせます。
  4. accview.cgiを使うと改ページ処理付きでログを一覧できます。

regist.cgi と accview.cgi のソース

簡易IPアドレス収集スクリプト regist.cgi

#!/usr/bin/perl

#============#
#  基本設定  #
#============#

# ログファイル
$log = "acclog.dat";

# 最大記録数
$max = 1000;

# IPアドレスを記録後のメッセージ
# $message="投稿ありがとうございました。";
$message="JavaScriptを有効にしてください。";

#============#
#  設定完了  #
#============#

# ホスト名、IPアドレスを取得
$host = $ENV{'REMOTE_HOST'};
$addr = $ENV{'REMOTE_ADDR'};
if ($host eq "" || $host eq $addr) {
	$host = gethostbyaddr(pack("C4", split(/\./, $addr)), 2) || $addr;
}

# 日時の取得
	$ENV{'TZ'} = "JST-9";
	$time = time;
	($sec,$min,$hour,$mday,$mon,$year,$wday,$dmy) = localtime($time);

	# 日時のフォーマット
	@week = ('Sun','Mon','Tue','Wed','Thu','Fri','Sat');
	$date = sprintf("%04d/%02d/%02d(%s) %02d:%02d",$year+1900,$mon+1,$mday,$week[$wday],$hour,$min);

# ログの読み込み
open(IN,"$log");
@lines = <IN>;
close(IN);

# 最大記事数処理
while ($max <= @lines) { pop(@lines); }

# 書き込みデータ
unshift(@lines,"$date<>$ENV{'REQUEST_URI'}<>$host\n");

# ログの書き込み
open(OUT,">$log");
print OUT @lines;
close(OUT);

# メッセージ出力
print "Content-type:text/html\n\n";
print "<html>\n";
print "<head>\n";
print "<meta http-equiv\=\"Content-Type\" content\=\"text/html; charset\=Shift_JIS\">\n";
print "<title></title>\n";
print "</head>\n";
print "<body>\n";
print "<center><hr width=400><P><h3>$message</h3>\n";
print "<P><hr width=400></center>\n";
print "</body></html>\n";
exit;

簡易IPアドレス収集スクリプト用ログ一覧CGI accview.cgi

#!/usr/bin/perl

#============#
#  基本設定   #
#============#

# スクリプト名
$script = 'accview.cgi';

# ログファイル名
$log = 'acclog.dat';

# 1ページあたりの記事表示件数
$p_log = 50;

#============#
#  設定完了  #
#============#

&decode;

# ヘッダ出力
print "Content-type:text/html\n\n";
print "<html>\n";
print "<head>\n";
print "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=Shift_JIS\">\n";
print "<title>LogView</title>\n";
print "</head>\n";
print "<body>\n";

# ページ区切り処理
$start = $page + 1;
$end   = $page + $p_log;

# ログ表示
open(IN,"$log") || &error("Open Error : $log");

$i=0;
while (<IN>) {
	$i++;
	if ($i < $start) { next; }
	if ($i > $end) { next; }

	($date,$request,$host) = split(/<>/);
	
	printf("[ %.4d\ ] \n", $i);
	print "$date $request $host<br>\n";
}
close(IN);

# 改ページリンク作成
$next_page = $page + $p_log;
$back_page = $page - $p_log;

print "<br><div style='text-align:center'>\n";
if ($back_page >= 0) {
	print "<div style='display:inline-block'><form action=\"$script\" method=\"get\">\n";
	print "<input type=hidden name=page value=\"$back_page\">\n";
	print "<input type=submit value=\"前の$p_log件\">\n";
	print "</div></form>\n";
}
if ($next_page < $i) {
	print " <div style='display:inline-block'><form action=\"$script\" method=\"get\">\n";
	print "<input type=hidden name=page value=\"$next_page\">\n";
	print "<input type=submit value=\"次の$p_log件\">\n";
	print "</div></form>\n";
}
print "</div>\n";
print "</body></html>\n";
exit;

#-------------#
# エラー処理  #
#-------------#
sub error {
	print "<center><hr width=400><P><h3>ERROR !</h3>\n";
	print "<P><font color=red>$_[0]</font>\n";
	print "<P><hr width=400></center>\n";
	print "</body></html>\n";
	exit;
}

#----------------#
#  デコード処理  #
#----------------#
sub decode {
	local($name, $value, @pairs);

	if ($ENV{'REQUEST_METHOD'} eq "POST") {
		$post_flag=1;
		if ($ENV{'CONTENT_LENGTH'} > 51200) { &error("投稿量が大きすぎます"); }
		read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
	} else {
		$post_flag=0;
		$buffer = $ENV{'QUERY_STRING'};
	}
	@pairs = split(/&/, $buffer);
	foreach (@pairs) {
		($name, $value) = split(/=/);

		$value =~ tr/+/ /;
		$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;

		$in{$name} = $value;
	}
	$page  = $in{'page'};
}

お願い

私はPHPスクリプトは書けません。どなたかPHPによる簡易IPアドレス収集スクリプトおよびログ一覧スクリプトを作成していただける方がいらっしゃいましたら、お願いできませんでしょうか。以下のリンクからご連絡いただければ幸いです。