August 20, 2026
Mass Assignment: How βisAdminβ:true Still Works in 2026
Whatβs up everyone! Nitin here π

By Nitin yadav
2 min read
Mass assignment is one of those bugs that feels almost too dumb to be real β until you send "isAdmin":true in a profile-update request and the server justβ¦ makes you an admin. It happens because modern frameworks love to auto-bind incoming JSON straight onto database objects. Convenient for devs, delicious for us. The trick is knowing which fields the app has but the form doesn't send. Let's go find them.
Why mass assignment happens
Frameworks (Rails, Laravel, Spring, Express + ORMs, Django) offer "bind the whole request body to the model" as a one-liner. If the developer doesn't explicitly allow-list which fields users may set, then any field on the model becomes user-writable β including role, isAdmin, verified, balance, account_id, or email_confirmed. The UI only shows you name and email, but the object behind it has a dozen more properties, and the binder will happily set whatever you send.
Step 1: Capture the normal request
Do the legit action β update your profile, your settings, your cart β and grab the request in Burp:
PATCH /api/profile
{"name":"nitin","email":"a@a.com"}PATCH /api/profile
{"name":"nitin","email":"a@a.com"}That's what the form sends. Now the question is: what else does this object contain?
Step 2: Discover the hidden fields
The fastest way to learn the object's real shape is to read it back. Hit the corresponding GET:
GET /api/profile
{"name":"nitin","email":"a@a.com","role":"user","isAdmin":false,"verified":false,"credits":0,"plan":"free"}GET /api/profile
{"name":"nitin","email":"a@a.com","role":"user","isAdmin":false,"verified":false,"credits":0,"plan":"free"}Every property here that the form doesn't let you edit is a mass-assignment candidate. No GET that returns the full object? Look at: the registration response, other API responses that include user objects, GraphQL introspection, JS bundles referencing field names, and API docs. Collect every field name you can.
Step 3: Add the privileged fields
Now inject those fields into your update request and see which ones stick:
PATCH /api/profile
{
"name":"nitin",
"email":"a@a.com",
"isAdmin":true,
"role":"admin",
"verified":true,
"credits":999999,
"plan":"enterprise"
}PATCH /api/profile
{
"name":"nitin",
"email":"a@a.com",
"isAdmin":true,
"role":"admin",
"verified":true,
"credits":999999,
"plan":"enterprise"
}Try naming variants too β frameworks differ: is_admin, isAdmin, admin, roles:["admin"], nested account[role]=admin, user.role. If the field exists under a slightly different name, one of these forms usually hits.
Step 4: Confirm it stuck
Re-GET your profile (or check the relevant behavior). If isAdmin is now true, or your credits jumped, or your plan upgraded β it bound. Screenshot the before object, the request with the injected field, and the after object showing the changed value. That three-shot sequence is a clean, undeniable PoC.
Common high-value targets
role/isAdmin/permissionsβ vertical privilege escalationverified/email_confirmed/kyc_statusβ bypass verification gatesbalance/credits/points/discountβ financial manipulationplan/subscription/tierβ unlock paid features freeuser_id/owner_id/account_idβ assign objects to victims (IDOR-flavored)price/totalon order objects β pay lessstatuson moderation/approval objects β self-approve
Where it hides
- Profile / account update endpoints (the classic)
- Registration (
POST /registerwith an extrarole) - Any "edit object" API (posts, orders, teams, invites)
- Bulk-import or CSV/JSON upload endpoints
- GraphQL mutations that accept a full input object
The impact ladder
- Setting a low-value field you shouldn't β low/medium
- Financial fields (credits, discount, plan) β medium/high
isAdmin/roleβ privilege escalation β high/critical- Assigning objects across users β account/data compromise β critical
Conclusion β the mass-assignment playbook
- Frameworks auto-bind JSON to objects β unlisted fields become user-writable.
- Capture the normal write request.
- Read the full object (GET/registration/GraphQL) to learn hidden fields.
- Inject
isAdmin/role/verified/credits/plan(+ naming variants). - Re-GET to confirm; screenshot before β request β after.