Mass assignment (CWE-915, BOPLA) variants: HTTP body, JSON field, GraphQL mutations, nested attributes, ORM patterns — comparison table and detection workflow.
TL;DR
Mass assignment is a vulnerability class where a web framework or ORM automatically maps HTTP request parameters to server-side object attributes without restricting which fields the caller may write. The OWASP API Security Top 10 2023 classifies this as API3:2023 BOPLA (Broken Object Property Level Authorization), merging the older terms "Mass Assignment" (write direction) and "Excessive Data Exposure" (read direction).
The vulnerability exists wherever user-controlled data flows into a model binding call without an explicit allowlist: User.update(params) in Rails, User.findByIdAndUpdate(id, req.body) in Mongoose, ModelSerializer(data=request.data) in Django DRF, or Object.assign(user, req.body) in plain Express. In each case, the framework's convenience is the developer's attack surface.
The simplest and most prevalent variant. The attacker adds privileged fields directly to the JSON body of a PATCH or PUT request on a profile or account update endpoint. The framework passes the full body to the ORM without filtering.
Target endpoints: PATCH /users/me, PUT /profile, PATCH /accounts/:id, POST /register.
Key payload fields: role, is_admin, isAdmin, is_superuser, is_staff, subscription_tier, credit_balance, verified, mfa_required: false.
See HTTP Body Mass Assignment for the full writeup with framework-specific PoCs.
JSON deserialization performs deep field mapping: the server maps each JSON path to a model field. An attacker exploits this by injecting fields at unexpected nesting depths, using operator-like keys, or sending property names the OpenAPI schema marks as readOnly.
Signals to look for: OpenAPI schema fields with readOnly: true, response fields not in the original request body, error messages mentioning specific field names.
See JSON Field Injection for nested object examples and response differential methodology.
Each ORM has a canonical unsafe pattern:
| Framework | Unsafe Pattern | Safe Pattern |
|---|---|---|
| Rails | params.require(:user).permit! | permit(:email, :name) |
| Django DRF | Meta.fields = '__all__' | fields = ['email', 'name'] |
| Express/Mongoose | User.findByIdAndUpdate(id, req.body) | Destructure only allowed fields |
| Spring Boot | No DTO class | Dedicated UserUpdateDto |
| Laravel | Missing $fillable | $fillable = ['email', 'name'] |
| FastAPI | extra="allow" | extra="forbid" |
See ORM-level Mass Assignment for framework-by-framework analysis.
GraphQL does not automatically restrict which InputObject fields reach the underlying resolver. A mutation like updateUser(input: UserUpdateInput!) where UserUpdateInput includes role or isAdmin exposes those fields to any caller. Low-code platforms (Strapi, Directus, Hasura) generate mutations from database schema, making every column writable by default. CVE-2024-52034 and CVE-2025-32433 both follow this pattern.
See GraphQL Input Abuse for introspection-based discovery and mutation PoCs.
Rails accepts_nested_attributes_for allows parent models to write through to associated models via nested hash syntax. Django related-field serializers and Mongoose populate paths have analogous patterns. The attacker constructs payloads that bypass top-level filtering but reach privileged fields on associated models:
{
"user": {
"email": "attacker@example.com",
"role_attributes": {"name": "admin", "id": 1},
"permissions_attributes": [{"name": "delete:all", "_destroy": false}]
}
}See Nested Object Assignment for Rails, Mongoose, and GORM-specific patterns.
RFC 6902 JSON Patch — endpoints accepting Content-Type: application/json-patch+json that do not validate allowed paths:
[{"op": "replace", "path": "/role", "value": "admin"}]RFC 7396 JSON Merge Patch — null values delete keys per RFC spec, potentially wiping ACL fields:
{"member_acl": null, "private": false}Step 1: GET /target-object → record all fields including privileged ones
Step 2: PATCH /target-object with superset (30+ candidate fields added)
Step 3: GET /target-object with a DIFFERENT session token
Step 4: Diff Step 1 response vs Step 3 response
Step 5: Confirm observable side effect (admin access, feature unlock)False positive patterns to eliminate: Rails silent 200 (fields stripped silently), echo responses (server reflects input without persisting), computed fields (updated_at always changes).
| Framework | Primary Control | Strict Mode |
|---|---|---|
| Rails | params.require().permit() | action_on_unpermitted_parameters = :raise |
| Django DRF | Explicit fields = [...] in Meta | read_only_fields for privileged attributes |
| Express | Destructure from req.body | Mongoose strict: 'throw' |
| Spring | UserUpdateDto class | @InitBinder setAllowedFields() |
| FastAPI | extra="forbid" in model_config | Pydantic v2 default behavior |
| NestJS | ValidationPipe({ whitelist: true }) | forbidNonWhitelisted: true |
BreachVex detects mass assignment across all patterns using superset PATCH probes, cross-session persistence confirmation, and OpenAPI readOnly field write attempts — automatically eliminating false positives by validating each candidate against multiple independent criteria before reporting.
Mass assignment variants include: HTTP body injection (extra fields in POST/PATCH), JSON field injection (nested object properties), ORM-specific patterns (Rails strong_params bypass, Django __all__, Express spread), GraphQL mutation input abuse, nested attribute injection (Rails accepts_nested_attributes_for), JSON Patch RFC 6902 field replacement, JSON Merge Patch RFC 7396 null wipe, HTTP Method Override smuggling, prototype pollution via __proto__, and NoSQL operator injection chains.
Direct JSON field injection in REST PATCH endpoints is the most prevalent pattern in 2024-2026, affecting approximately 30% of APIs scanned according to OWASP API Security research. GraphQL mutation mass assignment is rapidly growing, particularly in headless CMS platforms (Strapi CVE-2024-52034, Directus CVE-2025-32433). Prototype pollution chains remain the most severe variant due to their application-wide impact.
Any framework with auto-binding is affected. In order of prevalence: Rails (strong_params bypass or missing permit), Django DRF (Meta.fields='__all__'), Express/Mongoose (req.body spread), Spring Boot (missing DTO), Laravel (unguarded Eloquent models), FastAPI/Pydantic (extra='allow'), and any headless CMS with auto-CRUD endpoints (Strapi, Directus, NocoDB).
Directly, rarely. Indirectly, yes — via prototype pollution chain. Mass assignment sends __proto__: {isAdmin: true} to a Node.js endpoint using lodash.merge, corrupting Object.prototype. A downstream library using Object.shell triggers child_process.exec, achieving RCE. More commonly, mass assignment leads to account takeover and privilege escalation.
CVSS varies by context. Unauthenticated mass assignment (POST /register with role=admin) reaches CVSS 9.1+. Authenticated low-privilege (PATCH /me with is_admin=true) is typically CVSS 8.1 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N). Prototype pollution chains reach 8.8-9.8. The standard base for authenticated privilege escalation via mass assignment is 8.1.
Low-code/no-code platforms (Strapi, Directus, NocoDB, Hasura, PostgREST) generate CRUD endpoints automatically from data models with no field-level access control by default. All table columns become writable via the generated API unless explicitly blocked. CVE-2024-52034, CVE-2025-32433, and CVE-2024-32887 all exploit this pattern — they are mass assignment by design, not by accident.
An allowlist explicitly defines which fields a user may write — any field not on the list is rejected. A denylist tries to enumerate forbidden fields and fails when developers add new sensitive fields without updating the list. Allowlists are the only reliable pattern: a new field is blocked by default until explicitly permitted.
BFLA (Broken Function Level Authorization) controls whether you can call an endpoint. BOPLA controls whether you can write specific fields on objects you legitimately access. They are frequently chained: BFLA reaches an admin-only update endpoint, BOPLA sets privileged fields on the target object. Argo CD CVE-2024-21652 exemplifies a BFLA+BOPLA chain.
Arjun v3.x for REST APIs (parameter discovery with JSON body support). x8 for GraphQL and deeply nested bodies. Burp Suite Param Miner for response-differential hidden parameter discovery. OpenAPI-fuzzer for APIs with documented schemas targeting readOnly fields for write attempts. BreachVex combines all four approaches in its automated API security testing.
Not without confirmation. A 200 OK is necessary but not sufficient. You must confirm persistence: GET the resource with a different session token and verify the injected field persists with the attacker-controlled value. Without cross-session confirmation, the finding has a high false-positive rate — Rails and Django silently ignore unpermitted fields and return 200.