Опасности в автоматической привязке модели в 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.