Mass Assignment
ID |
mass_assignment |
Severity |
high (privilege fields) / low (identity / financial fields) |
Remediation Complexity |
medium |
Remediation Risk |
medium |
Remediation Effort |
medium |
Family |
API3:2023 - Broken Object Property Level Authorization |
CWE |
CWE-915 |
Resource |
data_exposure |
Language |
any |
Description
Walks each endpoint’s request body and parameter list for protected-attribute field names that the caller shouldn’t be allowed to set — typically privilege fields (admin, role, is_admin, is_staff, scopes, permissions) and identity / financial fields (owner, userId, balance, verified).
Severity is tiered:
-
HIGH — privilege fields (the GitHub-2012 / Rails mass-assignment class of vulnerability — write
is_admin=truein the signup form). -
LOW — identity / financial fields (advisory; some legitimate flows write these).
Rationale
Modern web frameworks bind incoming JSON bodies directly to ORM entities (Rails attributes=, Spring @RequestBody MyEntity, Django Model(**request.data), ASP.NET model binding, Laravel fill($request→all())). The mechanism is ergonomic — and dangerous when the entity has fields the request was never supposed to set.
The canonical pattern is the signup endpoint that accepts {username, password, email} but is implemented by binding to a User entity that also has an isAdmin boolean. A caller who adds "isAdmin": true to the JSON gets an admin account at sign-up time — no further exploit needed. The 2012 GitHub Rails breach was exactly this pattern. It still re-appears in modern code regularly.
Identity and financial fields are the lower-tier variant: write userId and the new record is owned by someone else; write balance and the value is whatever the caller specified. These are LOW because legitimate flows sometimes write them (admin tooling, internal mutators) — but the same field being settable from an end-user endpoint is a finding.
Remediation
The fix is to never bind a request body directly to a persistence model. Use an explicit allowlist:
-
Spring: define a dedicated request DTO (record / class) with only the fields the operation accepts.
@RequestBody CreateUserRequest req, then map to the entity in the service layer. Do not annotate the entity itself with@RequestBody. -
Django / DRF: declare
fields = ('username', 'email')(notall) on theSerializer.Meta. -
FastAPI: use a Pydantic model whose fields are exactly the accepted inputs. Pydantic ignores unknown fields by default; set
model_config = ConfigDict(extra='forbid')to reject them outright. -
Rails:
params.require(:user).permit(:username, :email)— strong parameters. -
Laravel:
$request→validated()+ aFormRequestwith explicit rules, or$request→only(['username', 'email']). -
Express + Mongoose: a Joi / Zod /
yupschema in front of the route;Model.create(validated).
If the ORM auto-binds the full body and you cannot stop that, strip the protected fields server-side before persisting (req.body.isAdmin = undefined), and add a database-level constraint (CHECK (is_admin = false) for the user-signup path) as a safety net.
Configuration
The detector ships with a built-in list of privilege fields (HIGH-tier) and identity / financial fields (LOW-tier). Both lists are extensible via the detector configuration — add project-specific protected attributes if your domain has them (isSuperuser, creditLimit, tenantId, …).
Unless your project has bespoke privilege fields, you typically do not need to configure this detector.
References
-
OWASP API Security Top 10 (2023) - API3:2023 - Broken Object Property Level Authorization.
-
CWE-915 : Improperly Controlled Modification of Dynamically-Determined Object Attributes.
-
OWASP Cheat Sheets Series: Mass Assignment Cheat Sheet.
-
OWASP API3:2023 walk-through (mass assignment is the write side of API3; excessive data exposure is the read side).