こんなtokenのつくりかたは嫌だ

            たぶんCSRFの対策なんだろうけど、対策になっていないコードをみつけた。

こういうことはしてはいけませんよ!というメモ。 記述はCakePHPを対象にしています。 [php] <?php class BonusController extends AppController { public function index() { if ($this->gotBonus()) { return $this->showError('Bonus is already received.'); } $user = $this->getUser(); if (!$user) { return $this->showError('Not found user.'); } $token = $this->getToken($user);
$this->set('token', $token); // viewでhttp://hoge.com/bonus/receive/?user_id=xxxx&token=$token なアンカーリンクが表示される // ユーザはこのリンクを踏むことでボーナスを得ることができる }

public function receive() {
    $user = $this->getUser();
    if (!$user) {
        return $this->showError('Not found user.');
    }
    $_token = $this->getToken($user);
    $token = isset($_GET['token']) ? $_GET['token'] : null;
    if ($_token !== $token) {
        return $this->showError('Not found token.');
    }
    // ボーナスをあげる
    $this->redirect('bonus/finish');
}

public function finish() {
}

private function gotBonus() {
    // すでにbonusをもらっていればtrue、そうでなければfalseを返す。
}

private function getUser() {
    $user_id = isset($_GET['user_id']) ? $_GET['user_id'] : null;
    if ($user_id === null) {
        return false;
    }

    $user = $this->User->findById($user_id);
    if (!$user) {
        return false;
    }

    return $user;
}

private function getToken($user) {
    $session_id = $user['User']['session_id'];  // session_idはログアウトして、再度ログインすると変わる値
    $salt = 'hgqpvnpqhpwgpwqnpwj';
    return Security::hash($session_id, null, $salt);  // sha1($session_id + $salt); が行われる
}

} [/php] ※実際にみたコードではuser_idをGETで送ったりはしてません。

$tokenの作り方がまずい。 $tokenの元になっている - $session_idは再ログイン(もしくはセッションが時間で切れる)しない限り永遠に同じ値。 - $saltは固定。 というものになっている。 つまり、$session_idが変わらない限り$tokenも変わらないということになる。

確かにindexで2回以上ボーナスがもらえないようにgotBonus()で確認しているが、receiveでは確認していないという・・・。 つまり、 >http://hoge.com/bonus/receive/?user_id=xxxx&token=$token このURLさえつかんでしまえば、このURLをブラウザでたたきまくれば何回でもボーナスをゲットできてしまうという。 そしてこのURLをつかむのなんて全く簡単な話だということ。

ちゃんとワンタイムトークンなものを使うようにしましょう。