What is Mass Assignment?
Mass assignment occurs when a framework automatically maps HTTP request parameters to model/entity properties. If the application binds user input directly to a domain object without restricting which properties are settable, an attacker can include extra fields in their request to set properties they are not supposed to control — such as isAdmin, role, balance, or confirmed.
The vulnerability is especially common in MVC frameworks (Rails, ASP.NET Core, Spring, Laravel) that offer automatic model binding as a convenience feature. It famously affected GitHub in 2012, where a researcher used mass assignment to add their SSH key to any organization.
How exploitation works
A user updates their profile. The intended request:
{ "name": "Alice", "email": "[email protected]" }
The attacker adds an extra field:
{ "name": "Alice", "email": "[email protected]", "role": "admin", "isActive": true }
If the server binds the entire request body to a User entity, all three fields — including role — are written to the database.
Vulnerable code examples
ASP.NET Core — direct entity binding
// VULNERABLE: Binds ALL User properties from request body, including Role
[HttpPut("profile")]
[Authorize]
public async Task<IActionResult> UpdateProfile([FromBody] User user)
{
_db.Users.Update(user); // Attacker can set user.Role = "admin"
await _db.SaveChangesAsync();
return Ok();
}
Ruby on Rails — unsanitized params
# VULNERABLE: Permits all params — attacker can set :admin => true
def update
@user = User.find(params[:id])
@user.update(params[:user]) # No strong parameters — mass assignment
end
Secure code examples
ASP.NET Core — dedicated DTO
// Define a DTO with only the properties users are permitted to change
public class UpdateProfileDto
{
public string Name { get; set; }
public string Email { get; set; }
// No Role, IsAdmin, Balance — not included means not bindable
}
// SECURE: Bind DTO, map explicitly to the entity
[HttpPut("profile")]
[Authorize]
public async Task<IActionResult> UpdateProfile([FromBody] UpdateProfileDto dto)
{
var user = await _db.Users.FindAsync(CurrentUserId);
user.Name = dto.Name;
user.Email = dto.Email;
await _db.SaveChangesAsync();
return Ok();
}
Ruby on Rails — strong parameters
# SECURE: Strong parameters allow-list only safe attributes
def update
@user = User.find(params[:id])
@user.update(user_params)
end
private
def user_params
params.require(:user).permit(:name, :email) # :admin, :role not permitted
end
What Offensive360 detects
- Direct entity binding — Controller actions binding request bodies directly to domain entities or EF/Hibernate models
- Missing DTOs — Absence of Data Transfer Objects separating API input from domain models
- Unfiltered
params.mergeorrequest.body— Rails, Django, or Laravel handlers that update models without strong parameter filtering [Bind]attribute misuse — ASP.NET’s[Bind]include lists that are overly permissive or missing
Remediation guidance
-
Always use DTOs — Define separate Input/Request classes that contain only the fields users are allowed to set. Map these explicitly to domain objects.
-
Use strong parameters (Rails) —
params.require(...).permit(...)is the Rails convention. Never useparams[:user]directly. -
Avoid
[Bind]in favor of explicit mapping — While[Bind(Include=...)]helps, it is error-prone. Prefer separate input models. -
Apply
[BindNever]or[JsonIgnore]on sensitive fields — Decorate properties likeRoleorIsAdminwith attributes that prevent them from being bound from user input. -
Audit model binding on every endpoint — During code review, verify that every model-binding operation has a corresponding restriction.