Overview [ Documentation]
Data Transfer Objects (DTOs) are a subset of a data model. They are used to limit the data that is input or returned from a web app by using a subset of the model. They are also used to prevent over-posting (aka mass assignment) attacks.
Note: not to be confused with a Microsoft.AspNetCore.Components.Web.DataTransfer
object.
Preventing Overposting with DTOs
Example Overposting Vulnerability
Reference: https://andrewlock.net/preventing-mass-assignment-or-over-posting-in-asp-net-core/
Consider this model:
public class UserModel
{
public string Name { get; set; }
public bool IsAdmin { get; set; }
}
The user can edit the Name
property. The IsAdmin
property is used to control the markup they see:
@model UserModel
<form asp-action="Vulnerable" asp-Controller="Home">
<div class="form-group">
<label asp-for="Name"></label>
<input class="form-control" type="TextBox" asp-for="Name" />
</div>
<div class="form-group">
@if (Model.IsAdmin)
{
<i>You are an admin</i>
}
else
{
<i>You are a standard user</i>
}
</div>
<button class="btn btn-sm" type="submit">Submit</button>
</form>
Here’s the controller action:
[HttpPost]
public IActionResult Vulnerable(UserModel model)
{
return View("Index", model);
}
In a normal flow, the user can only edit the Name
field. But the HTML can be manipulated: the IsAdmin
field can be set to true
and the model binder will bind that value.
Some ways to defend against such an attack include;
- Using
BindAttribute
on the action method - Using
[Editable]
or[BindNever]
on the model - Using a DTO
- Using
ModelMetadataTypeAttribute
Defending Against Overposting with DTOs
With a DTO, a second version of the model is created that only includes a subset of its properties:
public class UserModel
// Alternatively, UserModel could inherit UserModelDto
{
public string Name { get; set; }
public bool IsAdmin { get; set; }
}
public class UserModelDto
{
public string Name { get; set; }
}
The DTO model is the one provided to the controller action:
public IActionResult Safe3(UserModelDto userModelDto)
{
var model = new UserModel();
model.Name = userModelDto.Name;
return View("Index", model);
}
Now, even if the IsAdmin
property is posted, it cannot be bound (there is no IsAdmin
property on UserModelDto
).