Search code examples
phphtmlsecurityyii2csrf

How to properly validate CSRF Token in Yii2?


I use enableCsrfValidation on my main config

    'components' => [
        'request' => [
            'enableCsrfValidation' => true,
            'enableCookieValidation' => true,
            'cookieValidationKey' => '[somerandomstring]',
            'csrfParam' => '_csrf-backend',
            'hostInfo' => YII_ENV_PROD ? Yii::$app->params['hostInfo'] : null,
            'csrfCookie' => [
                'httpOnly' => true,
                'secure' => YII_ENV_PROD,
                'sameSite' => 'Lax',
            ],
        ],

and here is one of the controller's action (to change the user's fullName)

public function actionIndex()
{
    $model = new ProfileForm();
    $model->fullName = Yii::$app->user->identity->full_name;

    if ($model->load(Yii::$app->request->post()) && $model->save()) {
        Yii::$app->session->setFlash('success', 'Profile updated successfully.');
        return $this->refresh();
    }

    return $this->render('index', [
        'model' => $model,
    ]);
}

here is my profile/index view

<?php $form = ActiveForm::begin(['layout' => 'horizontal']); ?>

<?= $form->field($model, 'fullName')->textInput(['maxlength' => true]) ?>

<div class="form-group">
    <?= Html::submitButton(Yii::t('app', 'Save'), ['class' => 'btn btn-primary']) ?>
</div>

<?php ActiveForm::end(); ?>

From the rendered HTML page, we already get the _csrf param and token, in both the header and the form.

rendered HTML page

My question is why, from the penetration testing, the attacker still can submit the form using GET method and change the value of user's fullName, even without submitting the csrf token in the form and/or the header (image taken from the BurpSuite application)?

still can change the value of Profile's fullName

Note: The issue also happens on other controller/action as well, generated from the Gii.


Solution

  • CSRF token is not validated for GET, HEAD or OPTIONS request.

    The problem with your code is that Yii2 by default tries to parse request body even for GET requests.

    That's why if you send body with GET request Yii::$app->request->post() call will return non-empty array. Submitted data are loaded to model and it's saved.

    If you want to be sure data are saved only for POST requests you should use Yii::$app->request->isPost or Yii::$app->request->getIsPost() in your condition. For example like this:

    if (
        Yii::$app->request->isPost
        && $model->load(Yii::$app->request->post()) 
        && $model->save()
    ) {
        // ...
    }