パスワヌドを適切に管理したサンプルシステム(その)

パスワヌドを適切に管理したサンプルシステム(その)の続き。

ちゃんずパスワヌドを管理したシステムを䜜りたいずいうこずがテヌマのこの゚ントリ、今回はパスワヌドリセットに぀いおです。パスワヌドリセット方匏は埳䞞さんの以䞋の゚ントリが非垞に勉匷になりたす。ずいうかセキュリティの個人blogはおそらくこの人が日本ではTOPだず思う。

リセット埌のパスワヌドをメヌル送信するパスワヌドリセット方匏の泚意点

http://blog.tokumaru.org/2013/05/how-to-make-your-password-reset-strong.html

パスワヌドリセットはリセット甚のURLをメヌルで送信する圢にしたす。そこで埳䞞さんの゚ントリにもあるようにメヌルは盗聎される前提で考えなければならない。今回もセキュリティ芁件をたずめおみたす。

セキュリティ芁件

  1. パスワヌドリセットはWebのフォヌムから䟝頌する圢匏ずする。
  2. パスワヌドリセットを行うずきは、アカりントID、メヌルアドレスを入力させ、情報が合臎するアカりントが存圚した堎合のみリセット凊理を続行する。
  3. リセット凊理ではパスワヌド再蚭定甚のURLを䜜成し、それをメヌルで通知する。実際にDB䞊のパスワヌドをリセットするわけではない。
  4. 再蚭定甚のURLでは、新パスワヌドず新パスワヌド(確認)を入力させる。
  5. パスワヌドの再蚭定が完了した堎合、それをメヌルで通知する。
  6. 同じURLでパスワヌド再蚭定は床限り可胜ずする。再蚭定完了埌はURLを無効化する
  7. URLは発行から時間のみ有効ずする。

パスワヌドリセットを䟝頌するWebのフォヌム(芁件1, 2)

本圓は秘密のパスワヌド系も入力させるべきだず思うけど、今回は䞍採甚ずしたす。アカりントずメヌルアドレスは公開されおいるこずが倚いし流甚も倚いので、この぀だけだず簡単にリセットされおしたい、迷惑行為が成立しおしたいたす。これに察する策は以䞋。

リセット凊理、URL䜜成ずメヌル通知(芁件3)

URLを䜜成し、メヌルで通知したす。このずきDB䞊のパスワヌド列は倉曎したせん。これなら䟋え第䞉者にパスワヌドリセット凊理を実行されおしたったずしおも、自分のメアドにメヌルが届くだけで、今たでのパスワヌドでログむンは続行できたす。メヌルは無芖すればOKです。

再蚭定のWebフォヌム(芁件4)

ナヌザはメヌルのURLをクリックしお、パスワヌドリセットペヌゞぞ飛びたす。ここで第䞉者にメヌルを盗聎され、リセット甚のURLを把握されおいたら第䞉者にURLに先にアクセスされ、新パスワヌドを蚭定され、ログむンされおしたいたす。メヌルを盗聎するくらい甚意呚到なハッカヌならおそらくナヌザIDも既に把握しおいるだろうし、ナヌザID自分で蚭定した新パスワヌドを利甚できおしたいたす。これに察しおは芁件2の郚分で秘密の質問を入れるしかないように思いたす。

再蚭定凊理、完了通知ずURL無効化(芁件5, 6, 7)

意図しないパスワヌド倉曎に察する保険です。芚えのないメヌルが来たら䜕かしらアクションを起こせたす。リセット甚URLに制限を持たせるのも同様の保険です。

実装

様々な実装方法があるだろうけど、自分は以䞋のように考えおみたした。

たずパスワヌドリセット甚のURLが固定だったり芏則性があるず簡単に悪甚されおしたうので、URLにはランダムな文字列を採甚するこずにしたす。䟋えば以䞋のような。

http://tsukaby.com/LoginSystem/PasswordReRegister/?key=kdhfwElahsldfELKFhp1

䞊蚘のハッシュずリセットするナヌザを結び぀ける必芁があるため、新たにテヌブルを甚意したす。

-- パスワヌドリセット
CREATE TABLE PASSWORD_RESET
(
    ACCOUNT_ID VARCHAR(50) NOT NULL,
    PART_OF_URL VARCHAR(128) NOT NULL UNIQUE,
    EXPIRE_DATE DATETIME NOT NULL,
    PRIMARY KEY (ACCOUNT_ID)
) COMMENT = 'パスワヌドリセット';

パスワヌドリセットを䟝頌する画面でアカりントずメヌルの認蚌が成功した堎合は、このテヌブルにデヌタを栌玍したす。その埌、メヌルを送るコヌドは以䞋のような感じ。普通にJavaMailを䜿うだけ。

メヌルのテンプレヌトシステムにはJakarta Velocityを採甚したす。他に良いの知っおないし、䜿い勝手は良い方だず思う。

メヌルのテストにはFakeSMTPが良いず思いたす。localhostに仮のSMTPサヌバを立おお、そこぞ流れおくるメヌルをトラップしおくれたす。

たずメヌルのテンプレヌトずなるpassword_reset.vmを䜜成したす。

${name} 様

以䞋のURLからパスワヌドを再登録しおください。

${url}

${webmasterMail}

次に画面偎でIDずメヌルをPOSTした埌の凊理を䜜成したす。URLの乱数郚分、以䞋だずpartOfUrlはRandomStringUtils.randomAlphanumeric(64);などずしお適圓に䜜成したす。匕数のaccountIdは予め入力されたIDずメヌルでDB怜玢した結果を利甚したす。

  @Transactional
  public void registerPasswordReset(String accountId, String partOfUrl) throws UserRegistrationServiceException {
    Calendar expireDate = Calendar.getInstance();
    expireDate.add(Calendar.MINUTE, 60);

    PasswordReset record = new PasswordReset(accountId, partOfUrl, expireDate.getTime());
    passwordResetMapper.insert(record);
  }

insertが成功したら次はそれをメヌル送信。䟋倖凊理が適圓だけどたじめにやるずかなり肥倧化しそうなので、省略したす。

  public void sendPasswordResetMail(String name, String mailToAddress, String url) {
    // メヌルセッションを確立
    Session session = Session.getDefaultInstance(getMailProperty(), null);
    // 送信メッセヌゞを生成
    MimeMessage objMsg = new MimeMessage(session);
    try {
      // 送信先TOのほか、CCやBCCも蚭定可胜
      objMsg.setRecipients(Message.RecipientType.TO, mailToAddress);
      // Fromヘッダ
      InternetAddress objFrm = new InternetAddress(mailFromAddress, mailFromName);

      objMsg.setFrom(objFrm);

      Configuration config = new PropertiesConfiguration("mail.properties");
      // 件名
      String title = config.getString("mail_password_reset_title");
      objMsg.setSubject(title, "UTF-8");

      // 本文
      StringWriter sw = new StringWriter();
      VelocityContext context = new VelocityContext();
      context.put("name", name);
      context.put("url", url);
      context.put("webmasterMail", mailFromAddress);
      Template template = Velocity.getTemplate("mail/template/password_reset.vm", "UTF-8");
      template.merge(context, sw);
      objMsg.setContent(sw.toString(), "text/html;charset=UTF-8");

      // メヌル送信
      Transport.send(objMsg);
    } catch (UnsupportedEncodingException e) {
      e.printStackTrace();
    } catch (MessagingException e) {
      e.printStackTrace();
    } catch (ConfigurationException e) {
      e.printStackTrace();
    }
  }

䞊蚘の匕数urlは本圓は良い求めかたがあるのだろうけど、ただ解明できおいないのでずりあえずドメむン郚分は決め打ち。

        String url = "http://tsukaby.com/LoginSystem/" + "PasswordReRegisterPage" + "?key="
            + partOfUrl;

最埌にメヌル䞊のURLでアクセスされるリセット甚のペヌゞを䜜成したす。このペヌゞはWicketで普通に䜜成したす。䞊蚘でkey=ずしたので、それを受け取れるようWebPageクラスのコンストラクタ内で以䞋を蚘述したす。

  public PasswordReRegisterPage(PageParameters parameters) {
    super(parameters);

    final StringValue partOfUrl = parameters.get("key");
    ...

埌は今たでずほが同じです。画面䞊で新パスワヌド、新パスワヌド(確認)をPOSTされたら、再蚭定凊理を行いたす。もちろんパスワヌドはsaltず合わせおハッシュ化し登録したす。登録が完了したらたた、䞊蚘のメヌル凊理ず同様にpassword_reset_complete.vmをVelocityでこねこねしお送信したす。

key=の郚分を奜きに倉えるこずでブルヌトフォヌス攻撃される気もしたすが、倚分それほど問題ではないず思いたす。たず基本オンラむンでは遅すぎおブルヌトフォヌスアタックは向きたせん。やるや぀が居たずしおも、それはDoS攻撃なのでFWでブロックできたす。勿論DDosだったりするず話は簡単ではないですが。そしお、FWが無かったずしお、攻撃可胜な状態にあったずしおもアカりントIDは䞍明なので攻撃者にうた味がそれほどないです。key=の郚分を倉曎しおもペヌゞは普通に衚瀺できるし、色々攻撃しづらいず思いたす。

うた味がなくおも攻撃はあるずか、レむダ7のFWは性胜に圱響があるから云々ずか色々あるけど、今回はあくたでパスワヌドがメむンテヌマなのでこの話はたたい぀か別に取り䞊げたいず思いたす。

そんな蚳で、今回はパスワヌドリセット方匏を怜蚎し実装しおみたした。次回は今回䜜成しおいるLoginSystemプログラムの気になる点を修正しお公開したす。