SecureBlockLog inStart a pentest
Vulnerability Repository
HighAccess Control

Mass Assignment

Frameworks that auto-bind request parameters to model objects allow attackers to set internal fields like role or balance that were never intended to be user-controlled.

CVSS 8.1CWE CWE-915OWASP A04:2021 — Insecure Design

Description

Mass assignment is a vulnerability (CWE-915 — Improperly Controlled Modification of Dynamically-Determined Object Attributes) that arises from the convenience feature built into many web frameworks: the ability to automatically bind HTTP request parameters to the properties of a model or data object. Frameworks including Ruby on Rails, Laravel, Spring, Django REST Framework, and Express with Mongoose all support this pattern. When developers expose these binding operations without restricting which fields are writable, an attacker can send additional parameters not shown in the UI — such as role, isAdmin, balance, or creditLimit — and have the server persist them directly.

This vulnerability is classified under A04:2021 — Insecure Design in the OWASP Top 10 because the flaw is not merely a missing check but a fundamental design gap: the application was designed without considering which attributes should be user-controllable. It is distinct from privilege escalation in that the attacker does not bypass an explicit access control check — there simply is no check, because the developer did not realize the field was reachable via the API.

Mass assignment is particularly prevalent in RESTful JSON APIs and GraphQL mutations, where the API surface is broad and the mapping between request payloads and internal model fields is often implicit.

How It Works

Consider a user profile update endpoint. The front-end sends:

PUT /api/v1/users/profile HTTP/1.1
Host: app.example.com
Authorization: Bearer <user_token>
Content-Type: application/json

{
  "name": "Alice",
  "email": "alice@example.com"
}

The server-side controller might look like this in a Node.js/Mongoose application:

// Vulnerable
router.put("/profile", auth, async (req, res) => {
  const user = await User.findByIdAndUpdate(req.user.id, req.body, { new: true });
  res.json(user);
});

The req.body is passed directly to the update operation. An attacker inspects the API response or the application's JavaScript bundle and discovers that User objects have a role field. They replay the request with an extra field:

{
  "name": "Alice",
  "email": "alice@example.com",
  "role": "admin",
  "isAdmin": true
}

The server updates all supplied fields without question, granting the attacker administrator access. The same technique applies to fields like balance, subscription_tier, email_verified, account_locked, or credit.

GraphQL mutations are equally susceptible. If an input type exposes all model fields, a client can set any of them. Tools like InQL and GraphQL Voyager help enumerate the full mutation schema during penetration testing.

Impact

  • Privilege escalation — setting role or isAdmin fields grants unauthorized administrative access.
  • Financial fraud — modifying balance, credits, or discount_rate fields enables financial manipulation without authorization.
  • Account takeover — setting email_verified: true or password_reset_token to a known value can bypass authentication controls.
  • Data integrity violation — internal metadata fields (timestamps, audit flags, soft-delete markers) can be corrupted.
  • Unauthorized subscription changes — setting plan, subscription_tier, or feature_flags grants access to premium features without payment.

Detection

  1. Review API responses and JavaScript bundles to identify all fields present in model objects. These are potential injection targets even if not present in the UI.
  2. Add extra fields to all write requests (POST, PUT, PATCH) and observe whether they are reflected in the response or affect application behavior. Common targets: role, isAdmin, admin, superuser, balance, credits, plan, verified, approved.
  3. Fuzz with schema-derived parameters — use Arjun or ParamMiner to discover hidden parameters accepted by the endpoint.
  4. Test GraphQL mutations with InQL Scanner or Altair — introspect the full schema and identify writable fields on input types that correspond to sensitive model attributes.
  5. Check framework auto-binding behavior — review the codebase (or documentation) for use of req.body, params, or equivalent mass-binding calls passed directly to ORM update functions.

Remediation

Use explicit field allowlists (strong parameters). Never pass the raw request body to a model update function. Explicitly list every field the user is permitted to set:

// Safe — explicit allowlist
const allowedFields = ["name", "email", "bio"];
const updates = _.pick(req.body, allowedFields);
await User.findByIdAndUpdate(req.user.id, updates, { new: true });

In Ruby on Rails, use strong parameters: params.require(:user).permit(:name, :email). In Laravel, use $request->only([...]) or $fillable on the model.

Define separate DTOs for input and output. Use distinct Data Transfer Objects for API input that contain only writable fields, separate from the full internal model. This makes the permitted surface explicit and auditable.

Blocklist sensitive fields as a secondary control. In addition to allowlisting, explicitly mark security-sensitive fields (e.g., role, isAdmin, balance) as non-mass-assignable at the model level (e.g., $guarded in Laravel, attr_protected patterns).

Audit all ORM update calls. In a code review, flag every call that passes unsanitized request data to a model's update, save, or create method.

Ready when you are
Scope a pentest in the next two minutes.
Start scoping