Hatena::ブログ(Diary)

Scrapcode@はてなダイアリー このページをアンテナに追加 RSSフィード

2010-02-24

モバイル版はてなのログインページはSSLへのリンクがない

今のモバイルはてな携帯電話iPhoneAndroidDSi)のログインページに変わってから、SSLに切り替えるリンクがなくなりました。

f:id:khashi:20100224202842j:image:h300

はてなアイデアに2月1日にidea:26265として登録したのですが、未だに検討すらされていません。

iPod touchはてなログインするときはURL欄を編集してhttpsに変えてからログインしています。が、携帯電話ではURL書き換えは非常に面倒なので、自分でSSLログインページをブックマークでもしておかないといけません。

…と思って携帯(au W53CA)でhttps://www.hatena.ne.jp/mobile/loginにアクセスしてみたところ、

このサイトは安全でない可能性があるため接続できません(発行者エラー)

という、携帯電話が出すエラーが表示されてしまいました。

怖くて携帯からはてなにID、パスワードを使ってログインできない…!

かんたんログインログインしてますが、「高木浩光@自宅の日記 - はてなのかんたんログインがオッピロゲだった件」を見るとそれも心配だし…。

いやはや、恐ろしい。

2010-02-06

パスワードリマインダーの実装を考える

サイト運営者の視点で、パスワードリマインダーの実装について考えてみます。

(1) 登録メールアドレスに生パスワードを送信する

アチコチでよく見かけますが、パスワードの扱いが軽すぎます。

メールを盗聴されるとパスワードが漏れます。パスワードの使い回しが少なくないと思われる現在、該当サイトだけでなく他のサイトもアカウントを乗っ取られる危険性があります。

また、該当サイトではデータベースに生パスワード、もしくは復号可能なパスワードを保存している事になります。万が一該当サイトのサーバークラックされた場合、データベースのデータと復号方法まで一緒に漏れる可能性があります。

(2) 登録メールアドレスにランダムな仮パスワードを送信する

メールを盗聴されるとその仮パスワードを使ってログインされ、アカウントが乗っ取られます。パスワードを変更されてしまうとどうしようもなくなります。

盗聴者より先にログインしてパスワードを変更してしまえば、被害を回避できます。

パスワードが漏れても、アカウントを乗っ取られるのは該当サイトだけで済む分、(1)よりマシです。

(3) (2)に加え、登録情報との照合を行う

生年月日や秘密の質問など、ユーザー登録情報との照合を行うことにより、第三者による勝手な仮パスワード申請を避ける事ができます。

しかし、メールを盗聴されると危険なのは(2)と変わりません。

パスワード申請時ではなく、仮パスワードでのログイン時に照合を行うのであれば、安全性は高まります。

(4) 登録メールアドレスパスワード再設定用のURLを送信する

メールを盗聴されると危険なのは(1)と同じですが、盗聴者より先にパスワードを再設定してしまえば*1、被害を回避できます。

盗聴者が勝手にパスワード再設定用のURLを申請してしまうことができると、本人より先にパスワードを再設定されてしまう危険があります。

パスワードが見えてしまう事が無いので、もしメールを盗聴されて先にパスワード再設定を行われたとしても、アカウントを乗っ取られるのは該当サイトだけで済む分、(1)よりマシです。

(5) (4)に加え、登録情報との照合を行う

生年月日や秘密の質問など、ユーザー登録情報との照合を行うことにより、もしパスワード再設定用URLが漏れてもアカウントを乗っ取られる可能性がかなり低くなります。

ただし、パスワード再設定用URLをメール送信する前にこの照合を行った場合の危険性は(4)と同じになってしまいます。照合を行う場合は、パスワード再設定の段階で行うべきです。

(6) 登録メールアドレスパスワード再設定用URLを送信し、リマインダーフォームに確認コードを表示する。

  1. リマインダーのフォームで登録情報との照合を行う。
  2. 登録メールアドレスパスワード再設定用のランダムなURLを送信する。
  3. リマインダーの「メールを送信しました」表示と共に、ランダムな確認コードを表示する。
  4. ユーザーはパスワード再設定用URLにアクセスし、確認コードを入力し、パスワード再設定を行う。
  5. 使用済みURLと確認コードは破棄する。

メールを盗聴されても、パスワード再設定用URLだけではパスワードを変更できません。また、確認コードの表示がSSLで保護されていれば、確認コードを傍受されることもありません。

(7) 登録メールアドレスに確認コードを送信する

  1. リマインダーのフォームで登録情報との照合を行う。
  2. 登録メールアドレスにランダムな確認コードを送信する。
  3. セッションデータとしてサーバー側で確認コードを保持し、ユーザーからの確認コード入力を待つ。
  4. ユーザーがメールの確認コードを入力。
  5. パスワード再設定を行う。
  6. 使用済み確認コードは破棄する。

メールを盗聴されても確認コードのみなので、盗聴者がログインパスワード変更を行う事はできません。

セッションハイジャックとメールの盗聴が同時に発生しなければ、アカウントを乗っ取られる事は無いと思います。

この記事を書いたキッカケ

(6)と(7)はどこかで使われているかな?

(1)〜(5)に比べてメール盗聴に強く、実装も難しくないと思いますが、何か見落としている問題点があればコメントいただけるとうれしいです。

この記事を書いたキッカケは、去年見たユーザー登録型のサイトで(1)の事例が多くてウンザリしたからです。パスワードみたいな秘匿性の高い情報を平文でネットワークを流れるメールに書いてはいけないというのは、電子メールを使い始める最初に注意されることだと思うのですが…最近はそうでもないのでしょうか?

最近はPOP over SSL / SMTP over SSLに対応したプロバイダも増えてきてはいますが、これらが保護できるのはメールクライアントと各サーバーとの間だけです。サーバー - サーバー間をSSLで保護されるかどうかは保証されません。また、受信者側がPOP over SSLに対応していないかも知れないし、対応していてもメールクライアントSSLを使う設定にしていないかも知れません。

メール盗聴が実際にどれぐらい発生するかはわかりませんが、サイト運営側は盗聴されてもユーザーに被害が及ばない仕組みを用意するべきです。

(2010-02-09 追記) 「登録情報との照合」について

文中で「生年月日や秘密の質問など」と書きましたが、生年月日はWebで公開しているプロフィールに書いている人も多いため、照合に使うには危険です。

秘密の質問はサイト側が用意している質問選択肢が全然秘密になっていないことが多く、質問内容を自由に決められない場合には、やはり危険です。「ペットの名前」や「出生地」などは、ブログなどで公開していることも多いでしょう。

Webサービスによっては登録情報がメールアドレス、ユーザーID、パスワードぐらいしか無い場合もあるので、第三者にでも簡単に照合できてしまうかもしれません。

第三者でも照合できるような場合は勝手にリマインダーを使ってしまえるため、どの方法でもメール盗聴と照合突破を同一の攻撃者にやられると無力です。

第三者による照合一致の確率を極力抑える事ができた場合は、メール盗聴されただけではパスワードを変更できない方法が安全です。

危険度が変わらないならどの方法でもいいかも知れませんが…、それでも(1)だけは勘弁して欲しいところです(これだけは危険度が別格かと)。

高機能で使いやすいけどパスワードの扱いが杜撰なサービスよりは、多少使いにくくてもパスワードの扱いがしっかりしているサービスを使いたいものです。

*1:ただし、再設定が完了したURLを即時無効にすること

2010-02-03

NanoAでモバイルサイト(4) - app/myapp/config.pmの書き方

また前回から大きく時間が開いてしまいました。

NanoAはもう長い間更新されていないし、今更NanoAの記事を公開してもあまり意味がないかな…とか思いつつ、下書きに書きかけの記事がいくつかあったので、そのうち一つを完成させて公開してみます。

今回は app/myapp/config.pm の書き方です。今回もモバイルサイトとは特に関係ありません。

NanoAプラグインについての説明に、このような記述があります。

また、myapp/config.pm 内で use pluginname; すれば、同ディレクトリ内の全コントローラプラグインが適用されます。例えば、全コントローラモバイル対応機能を有効化したい、という場合は、こちらの手法が便利です。

no title

app/myapp/config.pm について簡単に記述されていますが、具体的にどう書けばいいのかが書かれていません。

正しい書き方を調べるために、まずはどういうタイミングで app/myapp/config.pm が呼び出されるのかを見てみます。

nanoa.cgiでconfig.pmを検索してみると、NanoA::Dispatchのload_config内に見つかりました。

sub load_config {
    my ($klass, $handler_path) = @_;
    my $app_name = $handler_path =~ m|^(.*?)/| ? $1 : 'system';
    my $module_name = "NanoA::Config";
    if (NanoA::load_once(NanoA::app_dir() . "/$app_name/config.pm")) {
        $module_name = "$app_name\::config";
    }
    return $module_name->new({
        app_name => $app_name,
    });
}

if文のところで app/myapp/config.pm の読み込みを試してみて、成功した場合はmyapp::config、それ以外はNanoA::Configのインスタンスを生成しています。このことから、myapp::configはNanoA::Configを継承させておくべき、ということがわかります。

これを踏まえて、app/myapp/config.pm を作ってみます。

package NanoA;

use plugin::myplugin;  # <- プラグイン呼び出し


package myapp::config;

use strict;
use warnings;
use utf8;

use base qw{ NanoA::Config };

1;

これで、myappで共通で使用するプラグインの呼び出しができるようになりました。

ところで、上記コードではプラグイン呼び出しの前にNanoAパッケージを宣言しています。これをしないと、plugin::mypluginのinit_pluginに渡される第2引数が 'myapp::config' になってしまうからです。

NanoA付属のプラグインを見ると、例えば app/plugin/form.pm ではinit_plugin内で第2引数($controller)を使用してコントローラオブジェクトにメソッドを追加しています。もし上記 app/myapp/config.pm でNanoAパッケージに切り替えずにmyapp::configパッケージ内でuse plugin::myplugin;してしまうと、formプラグインが追加するメソッドはmyapp::configのメソッドになってしまいます。

プラグインによるメソッドの追加方法がinit_plugin内ではなく、sub NanoA::method { ... } のように直接NanoAパッケージを指定して追加している場合は、myapp::configパッケージ内で use plugin::myplugin しても大丈夫です。

なお、これらの場合は厳密には実行されるコントローラのパッケージではなくNanoAパッケージにメソッドが追加されることになります。コントローラのパッケージはNanoAパッケージを継承させるので、NanoAパッケージに追加されたメソッドも他で上書きされなけば、コントローラ側で同じように使えます。

初期化処理の書き方

プラグイン呼び出し以外にも、共通で行いたい初期化処理もあります。その場合はNanoA::Configで用意されているinit_appをオーバーライドします。

package myapp::config;

use strict;
use warnings;
use utf8;

use base qw{ NanoA::Config };

sub init_app {
    my ($self, $app) = @_;

    # ここに初期化処理を記入
}

1;

$appには、その実行時のコントローラオブジェクトが入ります。なので、ほぼ共通だけど一部のコントローラでは共通の初期化処理を適用したくない、というような場合には、次のようにすれば良さそうです。

package myapp::config;

use strict;
use warnings;
use utf8;

use base qw{ NanoA::Config };

sub init_app {
    my ($self, $app) = @_;
    my $control = ref $app;

    if( $control ne 'myapp::start' ) {
        # myapp::start 以外の共通処理
    }
}

1;

または、各コントローラ全てで共通処理の要・不要を返すメソッドを用意しておいて、それを呼び出して判断します。

package myapp::config;

use strict;
use warnings;
use utf8;

use base qw{ NanoA::Config };

sub init_app {
    my ($self, $app) = @_;

    if( $app->need_session ) {  # <- セッション処理が必要な場合に真を返すメソッド
        # セッションの前処理
    }
}

1;

コントローラ

package myapp::xxx;

use strict;
use warnings;
use utf8;

# セッション処理が必要なコントローラでは真を返すようにする
sub need_session { 1 }

1;

2010-02-02

はてなダイアリーの記事バックアップ2

2008年1月11日に「 はてなダイアリーの記事バックアップ - Scrapcode@はてなダイアリー」という記事を書きました。

そのすぐ後に、ダウンロードしたXMLファイルをgzip圧縮し、メールで送信するスクリプトを作って動かしていました。

すっかり忘れていましたが、ふと思い出したので公開してみます。

ボクはこのスクリプトを次のように動かしています。

  • cronで1日1回実行
  • メールの送信先はGmail
  • Gmailで次のようなフィルタを設定
    • 受信トレイをスキップ (アーカイブする)(なくても良い?)
    • 既読にする(なくても良い)
    • ラベル「Backup/Hatena Diary」を付ける(なくても良い)
    • 削除する

「削除する」は、ゴミ箱のメールは30日経つと自動的に削除されることを利用して、延々蓄積されるのを防ぐためです。このやり方はどこかのサイトで見たのですが、どこだったかは忘れてしまいました…。検索して見つかったブログの記事は、当時参考にしたものとは違うようだし。

Perlのパス、はてなIDパスワードgzipのパス、一時ファイルのパス、メールのToとFromを設定する必要があります。

agent_aliasを 'Windows IE 6' にしているのに特に意味はありません。最近はIE6を動作対象外にする流れがありますし、他のものに変えておいてもいいかもですね。