Опасности в автоматической привязке модели в ASP.NET MVC

В своем посте про безопасность сайтов в ASP.NET MVC я совершенно забыл про одну очень неприятную атаку, применимую к действиям контроллеров, использующим автоматическую привязку модели и UpdateModel/TryUpdateModel.

Представьте себе ситуацию, что на сайте добавляются комментарии, которые обязательно должны проходить модерацию, поэтому у них есть свойство IsApproved, по умолчанию в базе данных заданное как false. Например, модель выглядит как:

 public class Comment {
    int CommentId {get; set;};
    // еще много разных полей …
    string Text {get; set;};
    string Author {get; set;};
    bool IsApproved {get; set;};
}

Представим себе, что при отправке формы пользователь заполняет поле textarea с идентификатором Text и input поле с идентификатором Author, а на стороне сервера действие, сохраняющее комментарий в базу данных выглядит как:

 [HttpPost]
public ActionResult SaveComment( Comment obj) {
    // тут всякий разный код
    UpdateModel( obj );
    // тут еще какой-то разный код
}

В этом случае нехороший пользователь может эмулировать POST обращение на сервер и передать пару значений IsApproved = true, чтобы автоматически показать сообщение без модерации. В этом сценарии, может быть, это не так страшно – комментарий можно удалить, однако похожий сценарий может быть применим к каким-то более важным бизнес-данным, поэтому такого допускать нельзя.

Есть три основных подхода, позволяющие избежать подобной проблемы.

Минимум кода – привязывать только нужные поля.

Для этого можно передавать параметры в метод UpdateModel():

 UpdateModel ( obj, "Comment", new string { "Text", "Author" };

Еще один вариант, пометить аргумент действия атрибутом Bind:

 [HttpPost]
public ActionResult SaveComment( 
                 [Bind(Include = "Text, Author")] Comment obj) {
    // … тут всякий разный код
    UpdateModel( obj );
    // тут еще какой-то разный код
}

Атрибут Bind можно использовать и в варианте [Bind(Exclude = “”)], указывая список полей, привязку которых не следует осуществлять. Однако метод с указанием только нужных полей оказывается надежнее при дальнейших модификациях модели – свойства остаются защищены от изменений, несмотря на то, что добавляются новые свойства в модели.

Чуть больше кода – использовать промежуточные модели.

Такой подход подразумевает создание отдельных классов моделей для использования на стороне представлений, часто называемых ViewModel. Эти модели являются упрощенными вариантами полноценных моделей данных и предоставляют только свойства, которые необходимы обработки получаемых данных. Например, для комментариев такая модель может выглядеть так:

 public class CommentViewModel{
    string Text {get; set;};
    string Author {get; set;}
}

В этом случае при обновлении базы данных придется конструировать новые объекты Comment, присваивать значения полей объектов типа CommentViewModel объектам типа Comment и сохранять уже объекты Comment в базу данных.

Совсем много кода – привязывать поля вручную

Самый гибкий, но и самый кропотливый в реализации способ – отказаться от использования методов UpdateModel и TryUpdateModel и проводить валидацию всех полей и конструирование нужных объектов самостоятельно. Этот метод оправдан когда для передаваемых данных необходимо выполнять сложные проверки и конструировать не один объект, а набор объектов со сложной логикой связи между ними. Кроме того, этот метод любят те, кто боится доверять привязку данных MVC Framework.