CakePHP3.0でCSRF対策済みのアクションテストを行う

2019/11/28 2019/12/12 #テスト #CakePHP3 #CSRF #CakePHP3.0

CakePHP「3.0」で「CSRF」のアクション「テスト」を行う

こんにちはNonです。
今回はあるプロダクトの開発に出くわしたちょっとした内容の課題を解決する手法についてお話しようと思います。
かなりニッチな需要となりますので、お役に立てるかわかりませんが参考になれば嬉しいです。

はじめに

環境は

  • CakePHP「3.0」

です。CakePHP「3.1」以降ではCSRF対策のテスト手法は確率されているので、この記事の対象者はCakePHP3.0のユーザーということになります。

結論

CakePHP3.0では

/**
 * Test sample method
 */
public function testSample()
{
    // sample-tokenの部分は任意の値で大丈夫です。
    $this->cookie('csrfToken', 'sample-token');
    $url = Router::url(['controller' => 'Sample', 'action' => 'sample']);
    $data = [
        'sample' => 'サンプル',
        '_csrfToken' => 'sample-token'
    ];
    $this->post($url, $data);
}

sample-tokenの部分は任意の値で大丈夫です。

CakePHP3.1以降では

/**
 * Test sample method
 */
public function testSample()
{
    $this->enableCsrfToken();
    $this->enableSecurityToken();
    $url = Router::url(['controller' => 'Sample', 'action' => 'sample']);
    $data = [
        'sample' => 'サンプル',
    ];
    $this->post($url, $data);
}

事例

とあるプロダクトの開発中、こんな出来事に遭遇しました。

あれ?このプロダクトCSRF対策されてないやん……

このプロダクトは簡素なもので、最近引き継いだばかり、プロトタイプと言っていいほどの機能にしか持っていないので、当時の作成者は面倒臭くてCSRF対策を忘れていたのでしょう。

よっしゃ、CakePHP3だしCSRF対策楽勝やろ!やったろ!

軽い気持ちで実装しました。

問題

CSRF対策は導入できたけどPHPUnit通らなくなってもうた……

ogp
CSRFアクションのテストの項目

CSRFアクションのテストの項目を見る

では……

CsrfComponent や SecurityComponent で保護されたアクションのテスト
SecurityComponent または CsrfComponent のいずれかで保護されたアクションをテストする場合、 テストがトークンのミスマッチで失敗しないように自動トークン生成を有効にすることができます。

public function testAdd()
{
    $this->enableCsrfToken();
    $this->enableSecurityToken();
    $this->post('/posts/add', ['title' => 'Exciting news!']);
}

また、トークンを使用するテストで debug を有効にすることは重要です。SecurityComponent が 「デバッグ用トークンがデバッグ以外の環境で使われている」と考えてしまうのを防ぐためです。 requireSecure() のような他のメソッドでテストした時は、適切な環境変数をセットするために configRequest() を利用できます。

// SSL 接続を装います。
$this->configRequest([
    'environment' => ['HTTPS' => 'on']
]);

バージョン 3.1.2 で追加: enableCsrfToken() と enableSecurityToken() メソッドは 3.1.2 で追加されました。

あれ?

スクリーンショット 2019-11-28 10.35.19.png

あかんやん
そら通らんわ。Cakeのソース追ってもenableCsrfToken()enableSecurityToken()も通りで無いわけや……

解決

でもよく考えて見ると、CSRFってトークン送信してそのトークンが合ってるだけで良い訳で、しかもPHPUnitでのリクエストが送信されれば良い(CSRF対策のテストがしたいわけではない)ので、普通にトークン送信すれば良いんじゃない?

ということで、やってみました。

CakePHP3の仕様では、HTML側に埋め込まれるCSRFトークンは

<input type="hidden" name="_csrfToken" value="43b804c28f3bd305a513c7c8ba1f1ce61e5ac6a9">

こんな感じなので、PHPUnitでリクエストするときは

$this->post($url, [
    '_csrfToken' => '43b804c28f3bd305a513c7c8ba1f1ce61e5ac6a9',
])

という形式になっていれば、OK。

この送信したトークンをサーバー側のクッキーと比較するはずなので、同様にPHPUnitでは

$this->_cookie('csrfToken', '43b804c28f3bd305a513c7c8ba1f1ce61e5ac6a9');

命名はクライアントの情報になってしまいますが、ここで確認しました。

スクリーンショット 2019-11-28 10.47.04.png

ということで最終的に下記のようなテストコードに。

/**
 * Test sample method
 */
public function testSample()
{
    // sample-tokenの部分は任意の値で大丈夫です。
    $this->cookie('csrfToken', 'sample-token');
    $url = Router::url(['controller' => 'Sample', 'action' => 'sample']);
    $data = [
        'sample' => 'サンプル',
        '_csrfToken' => 'sample-token'
    ];
    $this->post($url, $data);
}

結果

無事成功しました。

余談

スクリーンショット 2019-11-28 10.35.19.png

CakePHP3.1.2以降では……

/**
 * Test sample method
 */
public function testSample()
{
    $this->enableCsrfToken();
    $this->enableSecurityToken();
    $url = Router::url(['controller' => 'Sample', 'action' => 'sample']);
    $data = [
        'sample' => 'サンプル',
    ];
    $this->post($url, $data);
}

これでOKです。

設定するの面倒なので、CakePHPのバージョンあげますか……

最後に

フレームワーク独特の課題にぶつかりまして、少し楽しかったです。
実はもうCakePHP3のバージョンアップソースは完成しているので、またテスト内容をリライトしなければならないのですが、CakePHP3.0特有の課題を見つけることができてよかったと思います。

地味な内容になってしまいましたが、今後もニッチな内容のものを見つけましたら記事にしていこうと思います。

その時はよしなに。

.

のん

所属 : 株式会社スマレジ 開発部

YouTube : のんラボ

Twitter : @nonz250

Github : nonz250

Qiita : @nonz250

My Qiita posts My Qiita contributions My Qiita followers

主にPHPを使用し、サーバーサイドを担当。最近はフロントにも興味津々。

なにかを作ったりいじったりするのが好きで、個人開発なども行っている。

趣味はバイクアイコン画像は大抵愛車の「Z250」である。友達にアイコン描いてもらえて嬉しい。

PHP / Laravel / CakePHP2 / CakePHP3 / Vue / Nuxt / C# / etc...

Tags

#のんラボ #Laravel #Vue #個人開発 #ブログ #プログラミング #javascript #Html5 #WEBサービス #Twitter #今年の抱負メーカー #勉強方法 #PWA #モバイルアプリ #Android #ツーリング #バイクに乗るエンジニア #Z250 #秋吉台 #能登半島 #バイク #冒険 #東尋坊 #Squid #リバースプロキシ #hosts #axios #cropper #AdSense #Bootstrap #MySQL #高速化 #トドTask #Telescope #デバッグ #composer #テスト #セキュリティ #POSレジ #スマレジ #本部機能 #バリデーション #入力チェック #Mac #Chrome #テスト駆動開発 #開発手法 #UI #デザイン #WEBサイト #機能美 #PHP #Laravel 6 #コメント #バージョンアップ #vue-cli #localhost #BIツール #売上分析 #TANAX #MFK250 #ツアーシェルケース2 #RESTful #API #REST API #実務的 #PHP Tech Tutor #Smaregi Tech Talk #勉強会 # ブログ #CakePHP3 #CSRF #VSCode #開発環境 #CakePHP3.0 #さくらのレンタルサーバー #モジュールモード #シェル #メール #Gmail #relay #OGP #エラーページ #抱負 #家庭教師 #ドメイン駆動設計 #DDD #読書会 #那智の滝 #伊勢志摩 #伊勢志摩スカイライン #フロント #三方五湖 #レインボーライン #ボーイスカウト・ルール #プログラマが知るべき97のこと #リファクタリング #ユビキタス言語 #車輪の再発明 #マイクロサービス #デプロイ #QA #laravel-mix #Tips #storybook #@storybook/addon-actions #昇降デスク #コードレス #書斎 #オフィス #リモートワーク #働き方 #エラーハンドリング #スマレジ4 #pixel 5 #レビュー #スマレコ #TDD #RSS #404 #高山ダム #ラーツー #React #Nuxt #node_modules #エラー #インポート #設定方法 #環境構築 #Docker #フォレストパーク神野山 #学生向け #PR #採用 #Node.js #npm #しまなみ海道 #youtube #CSS #IE #SLA