スパム症候群 〜その発病及び傾向と対策に関する一考察 (2:またも未完)

「明日(以降)に続く」と書いてからはや1週間。まあ「以降」なので嘘は言っていないわけだが。さて続きだが、ちょっと仕事モードで書くのが辛くなってきたので、普通に書くことにする。仕事モードの文書だと、なぜその方法で開発を行うのかを根拠付きで示すのが常道だが、何せ今回の対処法については「たぶんこんなもんだろ」程度の認識でエイヤッとやっちまったので、そんなん仕事モードで書けるか、という結論となった。続きがなかなか書けなかったのは主にこのせい、ということにしておく。

設計

スパム書き込みの対処というと、このはてなダイアリーのコメント書き込みでも使っているようなCAPTCHA(文字がうねうね曲がった画像を読み取って入力するやつ)を使うのがよくあるパターンらしい。しかし、このCAPTCHA画像生成の仕組みを準備するのが非常に大変そうだった(探せばPerlのライブラリくらいありそうな気もするが)ので、それは却下。代わりに、これまたはてなの公開設定で使える「なぞなぞ認証」を応用することにした。要するに書き込みメンバーしかわからないような問題の答を入力させることで部外者の書き込みを防ぐ、というわけだ。

とは言え、掲示板の現ユーザの全員が知っているようななぞなぞ、というのがイマイチ思いつかなかったというのと、やはり掲示板の設立名目に沿ったなぞなぞの方が面白いんではなかろうか、ということであのような「さだ」ネタを採用することに。以前、誰かがそういう案を書いていた事もあったし。ぶっちゃけて言うと、今回の作業の中で一番熱中したのはあのクイズ作成部分だ(笑)。さだファンならそう苦もなく思いついて、答の入力文字数が短めで、かつ人によって入力にブレがない(ローマ字で何通りか書き方がある音が現れるのは外す、とか)、というクイズを考えるのはなかなか楽しかった。

で、問題はこのなぞなぞによる認証処理部分をどう作るか、というところ。上で書いたように、CAPTCHA機械的なスパム書き込みに対して有効なのは「画像の内容を機械的に判定することが難しい」というところにあるわけだが、何せ面倒くさがりなもんだから、問題部分はそのままプレーンテキストで書き込みページ内に埋め込んでしまったため、これだと機械的な判定が容易になってしまう。

そこで、CSRFの対処法に出てくる「ワンタイムトークン」の仕組みを応用してみた。要するにこういう事。

  1. 1回目のアクセス(掲示板ページで書き込みボタンを押す)で、フォームを含む書き込み用ページを返す。ここにはあらかじめ以下の情報を埋め込んでおく。
    • 認証用の問題文
    • その問題文の答を「ある一定の法則」に基づいて暗号化した文字列(これがトークンに相当)。これはinputタグのhidden属性として埋めてあるので、ブラウザ上では見えない。
  2. 書き込み文章と問題の答を入力し、投稿ボタンを押す。これにより以下の情報がサーバに送られる。
    • 書き込み内容
    • 問題の答
    • (あらかじめinputタグに埋め込んであった)トークン文字列
  3. 上記情報を受け取ったサーバ側では、以下の処理を行う。
    • 送られてきた問題の答を「ある一定の法則」に基づいて暗号化した文字列を生成する。
    • この暗号化文字列が、一緒に送られてきたトークン文字列と一致すれば認証OK。

ちなみに、上で言っている「ある一定の法則」というのは、

  • 暗号化前後の文字列内容は必ず1対1で対応する(暗号化の度に結果が異なったりはしない)
  • 異なる文字列の暗号化文字列が同一になることはない
  • 暗号後の文字列から、暗号前の文字列に逆変換することは不可能

という条件を満たしている。業界用語で言うとハッシュ関数というやつ。

さて、上記のような方法を用いたシステムにCSRF攻撃を仕掛ける者は、認証問題の答と、対応するトークン文字列の両方を送信するスクリプトを準備する必要がある。ここで問題になるのがこのトークン文字列の作り方で、もしも、ある問題文に対する答のトークン文字列が「いつ誰がアクセスしても同じ」だとしたら、いずれその法則性を見抜かれて攻撃されてしまうということも考えられる。そこで、「問題文とその解答は同じでも、トークン文字列はアクセス毎に異なるようにする(どう異なるかは開発者だけの秘密)」というように対処した。これで、もしも認証問題の答がばれたとしても、対応するトークン文字列を生成できない攻撃者にはどうしようもない、ということになる。


ーーーーーーーーーーーーーーーーーーー
…と思われたのだが、ここで思わぬ落とし穴が。さらに続けようかと思ったのだが、ここからまだ結構長くなりそうなので、次回へ続く。…なるべく連休中に終わらせたい。