コメントスパム対策・実践編

ちょっと時間が空きましたが、先日のコメントスパム対策・理論編 - @ikikko のはてなダイアリーで取り上げたCAPTCHAの設定です。実はCAPTCHAの設置だけならば大した手間ではなかったのですが、それに伴っていくつか懸念事項が発生したので、その分の付加作業にも対処してました。

CAPTCHAの設置

まずはPerl上でCAPTCHA画像の生成。Authen::Captchaのモジュールを使わせてもらいます。

# CAPTCHAオブジェクト生成
my $captcha = Authen::Captcha->new(
  data_folder   => $captcha_data,
  output_folder => $captcha_output,
);
# 6文字の画像生成
my $md5sum = $captcha->generate_code(6);

続いては、上記で生成した画像と、画像からユーザが推測した文字を入力するフィールドの表示。当たり前ですが、どの画像を表示しているかの情報をhiddenパラメータででも渡してやらないと、ユーザが推定した入力が正しいのかが後続処理で判断できません*1

<img src="$captcha_output/$md5sum.png" />

<form method="POST" action="./captcha.cgi">
<input type=text name="captcha" value="画像内の文字列を入力してください">
<input type=hidden name="md5sum" value="$md5sum">
<input type=submit>
</form>

あとは、CGIの遷移先で画像の表示値とユーザの入力値をつき合わせてやります。

# CAPTCHAオブジェクト生成
my $captcha = Authen::Captcha->new(
  data_folder   => $captcha_data,
  output_folder => $captcha_output,
);
# ユーザ入力値のチェック
my $result = $captcha->check_code($FORM{'captcha'}, $FORM{'md5sum'}) 

# 違っていた場合、エラールーチンに遷移
&error("画像の文字列と入力された文字列が違います") unless $result = 1;

登録画面への入力フォーム値戻し

と、↑までが実際のCAPTCHAの作業ですが、ここで問題点が発生しました。正常にユーザが推測できた場合はいいのですが、間違った場合のルーチンが既存のやつそのままだとちょっと困ったことになったのです。


現状だと登録画面で何かエラーが起こった場合、ただエラーの内容(例:「名前が入力されていません」)を出力するだけになっていました。ここで、ブラウザの「戻る」ボタンで戻ると入力した内容はそのままですが、CAPTCHA画像もそのままになってしまいます。一度推測失敗したCAPTCHA画像は消去される*2ので、このまま何度リトライしても失敗するだけになってしまいます。かといって、更新をかけてやるとせっかくフォームに入力した値が消えてしまいます*3


ちょっと考えた結果、エラー画面に登録画面へ戻る用のボタンを作って、そこから戻ってもらうようにしました。エラー画面には、登録時のフォーム値が入ってくるはずなので、それをそのままhiddenパラメータで戻してやれば大丈夫でしょう。ま、Webアプリケーションによくある処理ですね。


コメントスパム対策にとって本質的ではないので、実際のコードは省略。

User-agentによる振り分け

当該オンライン出席簿には、PC用と携帯用のCGIがあります。今回CAPTCHAを設置したのはPC用だけなので、携帯用スクリプトの方からコメント投稿されると突破されてしまいます。そこで、User-agentを見て、携帯用のUA以外はPC用にリダイレクトさせる手法を取ろうと思いました*4


…ですが、いろいろやる気がなくなってきたので実際はこの作業はやっていませんorz。↑二つの作業だけで問題が起きたときに、またやろうかな。

*1:3分ぐらい、はまりましたが…

*2:Authen::Captchaのオプションで変更できるみたいですが、セキュリティ上好ましくないのでそのままに。

*3:多分IEだけ。Firefoxでは大丈夫そう。他のブラウザは未検証。そもそも、想定しているユーザはIE以外使わないかな…

*4:UAは偽装できるので、やろうと思えばコメントスパムもできますね。けど、そもそもCAPTCHA自体が自動投稿をはじくだけの手法で根本的な解決にはなっていないので、UAに関してもそこまで厳密にやらなくてもいいかなと…。