Broken Access Control: The Complete Practical Guide

Mass Assignment (Auto-Binding) Vulnerabilities

How mass assignment works: auto-binding extra fields the UI never sent, adding isAdmin or role to a request, seizing ownership, and discovering bindable fields.

Published

Mass assignment is a quiet privilege escalation hiding inside ordinary create and update features. A framework binds the whole request body onto a model, so if the model has a sensitive field like role or isAdmin, you set it by just including it in the request. The form never showed that field, but the server happily accepts it.

What auto-binding does and why extra fields sneak in

Many frameworks offer a convenience: take the incoming body and map every field onto the object. One line of code turns {"name":"...","email":"..."} into an updated record. The trouble is that the same one line also maps {"role":"admin"} if you include it, because it binds everything, not just the fields the form was supposed to send. The vulnerability is the absence of an allow-list on what may be bound.

Adding isAdmin or role to a body the UI never sent

The highest-value test is to add a privilege field to a legitimate request:

POST /api/profile
{"name":"me","email":"[email protected]","role":"admin"}
 
POST /account/settings
{"username":"me","isAdmin":true,"accountType":"enterprise"}

Your update does what it should, and also sets the field the UI never exposed. This is the mass-assignment path to administrator, and it overlaps directly with privilege escalation.

Setting ownership and foreign-key fields

Beyond privilege, mass assignment can seize records by setting ownership or foreign-key fields:

POST /api/documents
{"title":"mine","ownerId":1}        (create a document owned by someone else)
 
PUT /api/orders/1003
{"userId":52}                       (reassign an order to your account)

If the model exposes an ownerId, userId, or teamId and the code binds it, you decide who owns what.

Discovering bindable fields

You cannot set a field you do not know about, so collect field names:

  • Read a GET on the same object, which usually returns its full property set, sensitive fields included.
  • Check API docs, schema files, and error messages, which leak field names freely.
  • Compare a create response to what you sent, since extra properties in the response hint at bindable inputs.

Then add each candidate to a write request and see which ones take.

JSON versus form-encoded nesting

The same attack works in different encodings, and nesting sometimes reaches fields a flat body cannot:

Form-encoded:   name=me&[email protected]&role=admin&isAdmin=true
Nested JSON:    {"user":{"name":"me","permissions":["*"]}}

If a flat role is rejected, try a nested shape that matches the object graph, since binding logic sometimes guards the top level but not nested objects.

The fix and the mindset

The defense is an explicit allow-list of bindable fields, so role, isAdmin, and ownership keys can never be set from the request, never auto-bind the whole body. As an attacker, treat every create and update endpoint as a chance to set a field the form hid, and read the object's own GET response to learn what to try. Mass assignment is a frequent ingredient in account-takeover chains. Practice it on a free live target.

← Back to Broken Access Control: The Complete Practical Guide