Уязвимость моделей в Yii2 или о важности safe аттрибутов

23 июля, 2015
Метки: ,

Давайте посмотрим на метод update контроллера, генерируемого с помощью gii

public function actionUpdate($id)
    {
        $model = $this->findModel($id);

        if ($model->load(Yii::$app->request->post()) && $model->save()) {
            return $this->redirect(['view', 'id' => $model->id]);
        } else {
            return $this->render('update', [
                'model' => $model,
            ]);
        }
    }

Подразумевается, что у вас есть форма, которая отправляет POST-запрос на данный action. Данные загружаются в модель, валидируются и сохраняются. Допустим, что данная форма нужна чтобы дать возможность пользователю обновить свое отображаемое имя (username). Но при неправильном указании safe-атрибутов в модели, злоумышленник сможет обновить и другие поля, такие как пароль или статус, причем не только для своего id. Для этого достаточно отправить POST запрос на update/чужой-id передав не только значение username, но и значение password.

Безопасные атрибуты и сценарии в Yii

Виной всему метод $model->load(), который позволяет массово присвоить значения в модель. Это гораздо удобнее, чем присваивать каждое значение по одному. Но не стоит забывать о safe-атрибутах (безопасных атрибутах).
В Yii 1.x «безопасными», т.е доступными для массового присвоения были только те атрибуты, которые явно указаны в правилах модели как safe.
В Yii 2.x безопасными по умолчанию являются все атрибуты, когда либо упомянутые в правилах модели (метод rules()). А валидатор safe нужен лишь для того, чтобы атрибут «засветился» в правилах модели и тем самым стал безопасным. Т.е safe-валидатор в Yii 2.x не влияет на безопасность атрибута уже упомянутого в правилах!
Изменить данное поведение можно лишь переопределив сценарий по умолчанию в модели

 
    public function scenarios()
    {
        $scenarios['default'] = ['login', 'username', '!password'];
        $scenarios['example'] = ['login', 'phone'];   
        return $scenarios;
    }

Перечисленные в сценарии атрибуты нуждаются в валидации, по правилам указанным в методе rules(). Т.е если в сценарии не указать атрибут, его нельзя будет присвоить массово, но и валидироваться он тоже не будет. Для атрибутов, которые нуждаются в валидации, но не должны учавствовать в массовом присвоении, перед именем атрибута нужно поставить «!».
Вообще механизм сценариев — штука очень удобная. С помощью сценариев можно легко манипулировать полями, обязательными для заполнения, валидируемыми и массово присваиваемыми. Напомню о том, как задать текущий сценарий для модели:

$model = $this->findModel($id);
$model->scenario = 'example';
//теперь можно смело использовать массовое присвоение атрибутов
if ($model->load(Yii::$app->request->post()) && $model->save()) {
...

Хочешь быть уверенным в своем коде? Пиши тесты!

А вы уверены в своем коде? Уверены, что в вашем коде все «опасные» поля исключены из массового присвоения и через безобидную с вида форму злоумышленник не сможет обновить и другие поля модели? Уверены, что со временем с разрастанием проекта, кто нибудь не удалит какое-либо правило из rules или не добавит новое, так что оно повлияет на список безопасных атрибутов текущего сценария?
Но есть простой способ быть в этом уверенным — написать unit-тесты. А с помощью моего класса для облегченного тестирования писать подобные тесты проще простого.

public function testPasswordIsNotSafe()
{
   $userModel = new ModelMatcher('app\models\User');
   $userModel->shouldBeNotSafe('password');
}

Когда-то разработчики Ruby on rails сказали, что safe-атрибуты это ерунда, что в документации описана необходимость их указания и что все «нормальные» разработчики обязательно указывают список безопасных атрибутов. Так что это не баг — это фича. Но после того, как через эту уязвимость взломали Github, в Ruby on rails поменяли данную концепцию. Жаль что на Yii пока нет таких громких проектов 🙂 Безопасные атрибуты остаются на совести программиста, помните о них и не ленитесь их указывать. А я, как всегда, жду комментариев и предложений.


Метки: ,

Оставить комментарий

7 комментариев »

    User

    Если бы ещё написали статью о сценариях- вообще было бы круто.
    Хотелось бы больше статей для новичков.

      Developer

      Ок, напишу. Сценарии это достаточно просто и удобно.


    Александр

    …Для этого достаточно отправить POST запрос на update/чужой-id…

    По-моему вот, где беда-то. Вот, что нужно ловить в первую очередь.

      Developer

      По умолчанию в Yii2 включена защита от csrf-атак, поэтому POST запрос с чужого домена не пройдет. А если программист умышленно отключил эту защиту, то наверное предпринял другие меры безопасности. Вообще в плане безопасности Yii отличный фреимворк. Только использовать его нужно правильно.


    womanblog

    Изначально Yii2 не вводит такого понятия как слоистая архитектура. В результате чего появляются божественные модели в лучшем случае, ну а в худшем все разбросанно абы как.


    Сергей

    Перечитал статью раз 10, но так и не понял, в чём проблема.

    >Для этого достаточно отправить POST запрос на update/чужой-id передав не только значение username, но и значение password.

    А разве id пользователя надо брать не из Yii:$app->user->id ?! Логично, что если id берётся из запроса, то данные запрошенного пользователя может поменять кто угодно и не надо тут удивляться.

    Какие проблемы с safe? По-сути, это означает «Эй, модель, этот аттрибут ты можешь сохранить у себя без валидации». Не указывайте аттрибут в rules ВООБЩЕ, если не хотите позволить кому-то http-запросом изменить значение аттрибута. К чему это нагромождение из scenario??

      Developer

      А у вас id бывает только у юзер модели? А если это товар или id заказа?
      А если мне нужно валидировать поле но не нужно давать возможность присваивать его автоматически? Когда отказываться от валидации полностью — не вариант, на помощь приходят сценарии.


Оставить комментарий:









Копирование материалов разрешено при наличии активной ссылки на источник