LoginSignup
7
7

More than 5 years have passed since last update.

CakePHP2.x フォームのPOSTでCSRF判定されハマったときに確認すること

Last updated at Posted at 2018-06-14

はじめに

普通のフォームの場合は殆どおこらないが、選択肢によりフォームの表示切り替えをしたり、AjaxでPOSTさせたりした場合に、POSTしたのに、リダイレクトされてしまい、うまく動かない!なんでだ!とハマってしまったときに確認する事項を紹介します。

ハマる原因はなにか?

SecurityComponent と CSRF

POSTしたのにリダイレクトされるのは、SecurityComponentの仕業です。なぜそうなるかというと、SecurityComponentが「このPOSTリクエストはCSRFなので、不正だ!」と判定しているのです。

CSRFについては、詳しい人がいると思うのでその人に任せますが、簡単に言うと、HTMLやリクエストの改ざんにより、サイト運営者が意図しないデータをPOSTすることで、意図しないデータの保存や不具合を発生させる攻撃です。

一見やなSecurityComponentですが、実はとてもいいやつなのです。誤解しないでください。

SecurityComponentはどういうチェックをしているか?

POSTされた データの中に、_Token という配列データが含まれるのはご存知でしょうか?
これが重要なのですが、SecurityComponentはこの _Token 内の fieldskey をチェックしています。

POSTされたデータのフィールドと値を元に、ハッシュ値などを生成し、POSTされたfields値とkey値の値が一致しているかチェックしています。

該当箇所は以下

// lib/Cake/Controller/Component/SecurityComponent.php
    protected function _validatePost(Controller $controller) {
        $token = $this->_validToken($controller);
        $hashParts = $this->_hashParts($controller);
        $check = Security::hash(implode('', $hashParts), 'sha1');
        if ($token === $check) {
            return true;
        }
        $msg = self::DEFAULT_EXCEPTION_MESSAGE;
        if (Configure::read('debug')) {
            $msg = $this->_debugPostTokenNotMatching($controller, $hashParts);
        }
        throw new AuthSecurityException($msg);
    }

実際のソースはこれ

で、POSTされたデータに_Tokenを入れてるのは、 FormHelper がやっております。
という前提の元、ハマっている場合、以下を確認すると解決できる(はずです)。

チェックリスト

POSTのフィールドと初回表示のフォームのフィールドが同じか?

選択肢により、フォームが変わったりすることがある場合は CSRF 判定されてしまうので、入るかわからないフィールドはコントローラの beforeFilter() などで、SecurityComponentdisabledFields プロパティに設定する必要があります。

function beforeFilter()
{
    parent::beforeFilter();
    $this->Security->disabledFields = array('ModelName.fieldName1', 'ModelName.fieldName2');
}

disabledFields プロパティに設定すれば、SecurityComponentのチェックの際に該当フィールドを無視してチェックしてくれるので、CSRF判定されません。

※ 注意 ※
ただし、該当フィールドは悪意の第三者に任意の値をPOSTされるリスクを許容するという設定になるので、POSTの値を信じず、必ずバリデーション等値のチェックを行ってください。

_Token をPOST値に含めてるか?

AjaxなどでPOSTする値を明示的に指定し直している場合、_Token の値を指定し忘れると CSRF 判定されてしまうので、明示的に指定してください。

$.ajax({
    type: 'post',
    url: 'URL',
    dataType: 'json',
    data: {
        ModelName: {
            fieldName1: $('#ModelNameFieldName1').val(),
            fieldName2: $('#ModelNameFieldName2').val(),
        },
        // ※ これを指定する必要ありますよ
        _Token: {
            // もっと素敵なやり方ある気がするので詳しい方教えてください。
            fields: $('input[name="data[_Token][fields]"]').val(),
            key: $('input[name="data[_Token][key]"]').val(),
            unlocked: $('input[name="data[_Token][unlocked]"]').val(),
        }
    },
// 以下省略

Formヘルパーを使ってるか?

HTMLで直接記載している場合、Formヘルパーが fields値とkey値を作る場合に計算されないので、SecurityComponentがチェックするのと差異が出るためCSRF判定されてしまうので、必ずFormヘルパーを利用して下さい。

最後に

僕自身このケースでハマること合計数十時間。コアライブラリのコードを読んでスッキリ理解してハマることがなくなりました。参考になれば嬉しいです。

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7