ラボ > Laravel、Lumen:セキュリティ絡み

Laravel CSRF関連

作成日:2019-03-29, 更新日:2019-04-02

基本

・get送信だとCSRFはあっても無くても大丈夫
・post送信だとCSRFが無いと419エラー

ワンタイム、固定

ワンタイムトークンと固定トークンのどちらが正解なのかはよく分からん。
※考え方によってどっちが正解なのか変わるっぽい。

ひとまずLaravelのCSRFは固定っぽい。
そのためformの多重送信の対策が必要。

form送信

下記のどちらかを用意する

▼csrfをセットしてformで投げる1

<form method="post">
  @csrf
  <input type="submit">
</form>

※「@csrf」がinputタグごと生成してくれる

▼csrfをセットしてformで投げる2

<form method="post">
  <input type="hidden" name="_token" value="{{ csrf_token() }}" />
  <input type="submit">
</form>

※「csrf_token()」がCSRFの値を用意してくれる

多重送信の対策

ボタンクリックされても大丈夫なようにするってヤツ。

・JS側でボタンを無効化
・トークンを再生成
・固定のトークンをワンタイムに変更

JS側でボタンを無効化

▼ボタンクリック後の挙動
・最終的にページ遷移するようなヤツは「無効」でOK
・ページ遷移しないようなヤツは「無効」と条件によって「再有効」が必要

トークンを再生成

▼対象のコントローラーの処理内で「regenerateToken()」を行う

$request->session()->regenerateToken();
// \Session::regenerateToken(); // ←もしくはコッチ

ほとんどのサイトがこれで「ボタン連打による多重送信を防ぐ」とかいいながらまったくもって防いでいない・・・。

ワンタイムトークンの実装

LaravelのCSRFトークンをワンタイムトークン化
→私には難易度が高く、挫折。

疑似ワンタイムトークンの実装

上述のワンタイムトークンの実装を元に自分なりに用意

▼内容
・CSRFのチェックが終わったらトークンを再生成

▼手順
・ミドルウェアの作成
・ミドルウェアの登録

▼ミドルウェアの作成:app\Http\Middleware\OneTimeCsrfToken.php

<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Cache;

class OneTimeCsrfToken {
  public function handle($request, Closure $next) {
    if ($request->method() === 'POST')
      // POSTのときだけトークンリフレッシュ
      $request->session()->regenerateToken();
    }
    return $next($request);
  }
}

▼ミドルウェアの登録:app\Http\Kernel.php

~ 略 ~
protected $middlewareGroups = [
    'web' => [
        ~ 略 ~
        \App\Http\Middleware\VerifyCsrfToken::class,
        \App\Http\Middleware\OneTimeCsrfToken::class, // ←VerifyCsrfTokenの下に追加
        ~ 略 ~
    ],
~ 略 ~

Form:多重送信対策(2019-03-29):ボタン無効化+トークン更新

コレで万全なのかは分からない。
※ミドルウェアは上記と同じ内容

▼ビュー

<form method="post">
  <input type="hidden" name="_token" value="{{ csrf_token() }}" />
  <input type="button">
</form>
※「type="submit"」じゃなく「type="button"」を使う

<script>
  $('form').on('click', '[type="button"]', function(){
    $('[type="button"]').prop('disabled', true);
    $(this).parents('form').submit();
  });
</script>

※formやjqueryのトコは必要に応じて修正。

▼ミドルウェアの作成:app\Http\Middleware\OneTimeCsrfToken.php

<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Cache;

class OneTimeCsrfToken {
  public function handle($request, Closure $next) {
    if ($request->method() === 'POST')
      // POSTのときだけトークンリフレッシュ
      $request->session()->regenerateToken();
    }
    return $next($request);
  }
}

▼ミドルウェアの登録:app\Http\Kernel.php

~ 略 ~
protected $middlewareGroups = [
    'web' => [
        ~ 略 ~
        \App\Http\Middleware\VerifyCsrfToken::class,
        \App\Http\Middleware\OneTimeCsrfToken::class, // ←VerifyCsrfTokenの下に追加
        ~ 略 ~
    ],
~ 略 ~