yii2: Yii2 (v 2.0.16-dev) Randomly fail to validate _csrf token on login.

What steps will reproduce the problem?

  1. Application is Yii2 Advanced Template
  2. On configurations have these settings:
'user' => [
            'identityClass' => 'backend\models\User',
            'enableAutoLogin' => false,
            'identityCookie' => [
                'name' => '_cookieBackend', // unique name
            ],
            'loginUrl' => ['site/login']
        ],

Also I have ‘cookieValidationKey’ property setted :

'components' => [
       'request' => [
           // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
           'cookieValidationKey' => 'SOMEKEY',
       ],
   ],
  1. Creating form for Login with ActiveForm (method POST) and Login

What is the expected result?

Login in the application.

What do you get instead?

[yii\web\HttpException:400] yii\web\BadRequestHttpException: Unable to verify your data submission.

Additional info

Q A
Yii version 2.0.16-dev
PHP version 5.6.30
Operating system

This issue appear Randomly, sometimes I can login successfully and sometimes not. On getting the error reload resolve the problem; I’m redirected inside the application and the user is logged. Disabling csrfValidation resolve the problem but it compromise the application security.

I dumped the function that validate the token:

/**
     * Validates CSRF token.
     *
     * @param string $clientSuppliedToken The masked client-supplied token.
     * @param string $trueToken The masked true token.
     * @return bool
     */
    private function validateCsrfTokenInternal($clientSuppliedToken, $trueToken)
    {
        if (!is_string($clientSuppliedToken)) {
            return false;
        }

        $security = Yii::$app->security;

        var_dump($security->unmaskToken($clientSuppliedToken));
        echo "<br \>";
        echo "<br \>";
        var_dump($security->unmaskToken($trueToken));
        echo "<br \>";
        echo "<br \>";
        var_dump($security->compareString($security->unmaskToken($clientSuppliedToken), $security->unmaskToken($trueToken)));
        die;
        
        return $security->compareString($security->unmaskToken($clientSuppliedToken), $security->unmaskToken($trueToken));
    }

In some cases variables $clientSuppliedToken and $trueToken have different values causing validateCsrfTokenInternal to return FALSE.

Also reporting this similar issue: 353181592

About this issue

  • Original URL
  • State: open
  • Created 6 years ago
  • Reactions: 3
  • Comments: 38 (20 by maintainers)

Most upvoted comments

привет)

зачем в этом методе вызывается $request->getCsrfToken(true); с $regenerate = true ?

Чтобы нельзя было воспользоваться токеном отлогинившегося пользователя. См. #15783

но дело в том что сейчас это создает проблемы только для “честных пользователей” в ситуациях которую я описал выше при первом заходе на страницу где после загрузки страницы происходят аякс запросы. значение куки изменяется в этом случае а токен в метатеге и скрытых инпутах остается старым.

наверное это не очень здорово так делать я вот поотлаживал и вижу что устаревшие пары кука + токен - все рабочие и с ними можно проходить цсрф защиту - т.е. для “недобросовестных юзеров” (для которых цсрф и включаем) проблем нет - один раз можно получить пару и спамить с ней сколько влезет.

вот на скринах как выглядит тест: https://monosnap.com/file/kKApRHVS2N7ykLkZMXXs3GLXRPk3DB - взял 4 пары кука + токен и прогнал через цсрф защиту https://monosnap.com/file/ithyKc1Cc1QSyHXi7T3edA6B3HaYkm - все 4 пары успешно проходят защиту

т.е. в текущей реализации перегенерация не добавляет стойкости цсрф защите но создает проблемы при штатном использовании сайта где она используется (только при первом посещении за сессию и если со страницы после готовности идут аяксы)

поэтому пока что выкрутились перегрузив этот метод: public function getCsrfToken($regenerate = false) чтобы перегенерации не происходило если токен есть в куках

You could disabled the submit button with javascript

$('form').on('beforeValidate', function (e) {
	$(e.target).find(':submit').prop('disabled', true);
}).on('afterValidate', function (e) {
	$(e.target).find(':submit').prop('disabled', false);
});

Ah ok - I think I debugged the source of the problem. Its not CSRF but apparently the exit in PHP 7.2.x is different.

If you have a controller action for an ajax response that returns a JSON like this - you may get a bad request in PHP 7.2

public function actionAjaxOutput()
{
     echo \yii\helpers\Json::encode(['output' => 'something', 'message' => 'something']);
    // this will throw a bad request in PHP 7.2
}

Instead change the above code to something like below:

public function actionAjaxOutput()
{
     Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
     return ['output' => 'something', 'message' => 'something']; 
    // works fine in PHP 7.2.x
}