CSRF対策としてのPHPでのトークン発行方法

トークンの発行方法を勉強する機会があったのでメモ。

CSRFとは?

CSRF:Cross-site Request Forgery(クロスサイト リクエスト フォージェリ)の略。 日本語にすると「リクエスト強要」ということらしい。

悪意のある第三者Webサービスの利用者に意図しないHTTPリクエストを送信させ、サービス提供側が意図しない処理を実行させること。

forjeryは「偽造」という意味。

対策方法としてのトークン発行

CSRF対策方法の一つとして、トークンを利用するやり方がある。

Webサービスに何らかのフォームを設置する場合、

① あらかじめセッション(通信の一連の処理)に推測されにくい文字列(トークン)を格納しておき、

② ユーザーがフォーム送信する際に、フォームからもセッションに格納されたトークンを引っぱってきて送信

③ サーバー側でセッションの中のトークンとフォームから送られてきたトークンが一致するかをチェックする

これによってフォームから変なデータを受け取らないようにする。

PHPでのトークン発行方法

index.php  フォーム側のコード例

<?php

session_start(); //セッションを開始

require_once(__DIR__ . '/Hoge.php');

?>
<!DOCTYPE html>
<html lang="ja">
<body>
  <div id="container">
    <h1>Sample Site</h1>
    <form action="">
      <input type="text">
      <input type="hidden" id="token" value="<?= h($_SESSION['token']); ?>"> //hiddenでセッションからとったトークンを一緒に送信
      <input type="submit" value="送信">
    </form>
  </div>
</body>
</html>

Hoge.php  サーバー側の処理例

<?php

class Hoge {

  public function __construct() {
    $this->createToken(); //トークン発行のメソッドを呼び出し
  }

  //トークン発行(メソッドとしてまとめておく)
  private function createToken() {
    if (!isset($_SESSION['token'])) { //トークンがセットされていなかったら
      $_SESSION['token'] = bin2hex(openssl_random_pseudo_bytes(16));
    } //32桁のトークンを発行
  }

  //フォームが送信されたら
  if ($_SERVER['REQUEST_METHOD']==='POST') {
    $this->validateToken(); 
  }

  //トークンの照合
  private function validateToken() {
    if (
      !isset($_SESSION['token']) ||  //トークンがセットされていない場合
      !isset($_POST['token']) || //トークンが送信されていない場合
      $_SESSION['token'] !== $_POST['token']  // 両者が一致しない場合
    ) {
      throw new \Exception("invalid token!"); //バリデーションエラーとする
    }
  }

}