前回:やられアプリ 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 など別のページでトークンチェックエラーとなるためそちらも修正が必要です。
または今回の修正は一旦元に戻しておいてください。