Improperly Controlled Modification of Dynamically-Determined Object Attributes
Description
Improperly Controlled Modification of Dynamically-Determined Object Attributes (also known as Mass Assignment, Autobinding, or Object Injection) occurs when software receives input that specifies multiple object attributes for initialization or update, but fails to properly restrict which attributes can be modified. When an application blindly assigns all incoming parameters to an object's attributes, attackers can modify internal or sensitive attributes that should not be externally accessible, such as privilege levels, account balances, or administrative flags.
Risk
This vulnerability can lead to severe security breaches. Attackers can escalate privileges by modifying role or permission attributes. Financial systems may be compromised by manipulating balance or transaction fields. Authentication can be bypassed by modifying verification flags. In JavaScript environments, prototype pollution attacks can affect all objects in the application. The risk is amplified because developers often assume certain attributes are "internal" and protected, when in reality the binding mechanism exposes them to modification. This vulnerability is particularly common in web frameworks that provide automatic parameter binding.
Solution
Implement explicit attribute whitelisting that specifies exactly which attributes can be modified through external input. Use framework-provided protection mechanisms (strong parameters in Rails, @JsonIgnore in Java, etc.). Separate DTOs (Data Transfer Objects) from domain models to control what can be bound. Never directly bind request parameters to internal domain objects. For JavaScript, filter out dangerous keys like __proto__, constructor, and prototype. Implement input validation that rejects unexpected attributes. Consider using immutable objects where appropriate.
Common Consequences
| Impact | Details |
|---|---|
| Integrity | Scope: Integrity Modify Application Data - Attackers can alter sensitive data or program variables that should be protected. |
| Integrity | Scope: Integrity Execute Unauthorized Code or Commands - Through prototype pollution or object manipulation, attackers may alter execution logic. |
| Access Control | Scope: Access Control Gain Privileges or Assume Identity - Modifying role or permission attributes enables privilege escalation. |
Example Code
Vulnerable Code
# Vulnerable: Mass assignment in Rails (pre-Rails 4 style)
class User < ActiveRecord::Base
# All attributes are mass-assignable by default
end
class UsersController < ApplicationController
def create
# Vulnerable: All params directly assigned
@user = User.new(params[:user])
@user.save
end
def update
@user = User.find(params[:id])
# Vulnerable: Can modify any attribute including :admin, :role
@user.update(params[:user])
end
end
# Attack: POST /users with params: user[name]=hacker&user[admin]=true
# Vulnerable: Django mass assignment
from django.db import models
class User(models.Model):
username = models.CharField(max_length=100)
email = models.EmailField()
is_admin = models.BooleanField(default=False)
balance = models.DecimalField(max_digits=10, decimal_places=2)
def vulnerable_update_user(request, user_id):
user = User.objects.get(id=user_id)
# Vulnerable: All POST data assigned to model
for key, value in request.POST.items():
setattr(user, key, value)
user.save()
# Attack: POST with is_admin=True or balance=999999
// Vulnerable: Spring autobinding
@Controller
public class VulnerableUserController {
@PostMapping("/register")
public String register(User user) {
// Vulnerable: All request parameters bound to User
// Attacker can set user.role=ADMIN
userRepository.save(user);
return "success";
}
}
public class User {
private String username;
private String password;
private String role = "USER"; // Can be overwritten!
private boolean verified = false; // Can be overwritten!
// Getters and setters for all fields
}
// Vulnerable: Prototype pollution via mass assignment
function vulnerableUpdateConfig(config, updates) {
// Vulnerable: Copies all properties without filtering
Object.assign(config, updates);
}
// Attack:
const config = { theme: 'dark' };
const maliciousUpdates = JSON.parse(
'{"__proto__": {"polluted": true}}'
);
vulnerableUpdateConfig(config, maliciousUpdates);
// Now ALL objects have .polluted property!
console.log({}.polluted); // true
// Vulnerable: Path-based object modification
function vulnerableSetByPath(object, path, value) {
const pathArray = path.split('.');
let current = object;
for (let i = 0; i < pathArray.length - 1; i++) {
// Vulnerable: No filtering of dangerous paths
current = current[pathArray[i]];
}
current[pathArray[pathArray.length - 1]] = value;
}
// Attack:
const obj = {};
vulnerableSetByPath(obj, '__proto__.polluted', true);
console.log({}.polluted); // true - prototype polluted!
// Vulnerable: PHP object injection via unserialize
<?php
class User {
public $username;
public $role = 'user';
public $isAdmin = false;
}
// Vulnerable: Deserializing user input
$userData = unserialize($_COOKIE['user_data']);
// Attacker crafts serialized string:
// O:4:"User":3:{s:8:"username";s:6:"hacker";s:4:"role";s:5:"admin";s:7:"isAdmin";b:1;}
// This creates User with role=admin, isAdmin=true
?>
Fixed Code
# Fixed: Strong parameters in Rails 4+
class UsersController < ApplicationController
def create
@user = User.new(user_params)
@user.save
end
def update
@user = User.find(params[:id])
@user.update(user_params)
end
private
# Fixed: Explicit whitelist of allowed parameters
def user_params
params.require(:user).permit(:name, :email, :password)
# :admin, :role, :balance are NOT permitted
end
end
# Fixed: Explicit attribute whitelist
from django.db import models
class User(models.Model):
username = models.CharField(max_length=100)
email = models.EmailField()
is_admin = models.BooleanField(default=False)
balance = models.DecimalField(max_digits=10, decimal_places=2)
# Define which fields can be updated via API
ALLOWED_UPDATE_FIELDS = {'username', 'email'}
def fixed_update_user(request, user_id):
user = User.objects.get(id=user_id)
# Fixed: Only update allowed fields
for key, value in request.POST.items():
if key in User.ALLOWED_UPDATE_FIELDS:
setattr(user, key, value)
else:
# Log attempted modification of forbidden field
logger.warning(f"Rejected field update: {key}")
user.save()
# Better: Use serializers (Django REST Framework)
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['username', 'email'] # Only these are assignable
read_only_fields = ['is_admin', 'balance'] # Never assignable
// Fixed: Use DTO with explicit fields
@Controller
public class FixedUserController {
@PostMapping("/register")
public String register(@Valid UserRegistrationDTO dto) {
// Fixed: Only DTO fields are bound
User user = new User();
user.setUsername(dto.getUsername());
user.setPassword(passwordEncoder.encode(dto.getPassword()));
user.setRole("USER"); // Set internally, not from input
user.setVerified(false); // Set internally
userRepository.save(user);
return "success";
}
}
// DTO only contains allowed fields
public class UserRegistrationDTO {
@NotBlank
private String username;
@NotBlank
private String password;
// No role, no verified - these cannot be set by user
}
// Or use @JsonIgnore on sensitive fields
public class User {
private String username;
private String password;
@JsonIgnore // Never bound from JSON
private String role = "USER";
@JsonIgnore
private boolean verified = false;
}
// Fixed: Filter dangerous properties
function fixedUpdateConfig(config, updates) {
const FORBIDDEN_KEYS = ['__proto__', 'constructor', 'prototype'];
// Fixed: Filter out dangerous keys
const safeUpdates = Object.fromEntries(
Object.entries(updates).filter(([key]) =>
!FORBIDDEN_KEYS.includes(key)
)
);
Object.assign(config, safeUpdates);
}
// Better: Whitelist allowed keys
function saferUpdateConfig(config, updates) {
const ALLOWED_KEYS = ['theme', 'language', 'timezone'];
const safeUpdates = Object.fromEntries(
Object.entries(updates).filter(([key]) =>
ALLOWED_KEYS.includes(key)
)
);
Object.assign(config, safeUpdates);
}
// Fixed: Safe path-based modification
function fixedSetByPath(object, path, value) {
const FORBIDDEN_SEGMENTS = ['__proto__', 'constructor', 'prototype'];
const pathArray = path.split('.');
// Fixed: Validate path segments
if (pathArray.some(segment => FORBIDDEN_SEGMENTS.includes(segment))) {
throw new Error(`Forbidden path segment in: ${path}`);
}
let current = object;
for (let i = 0; i < pathArray.length - 1; i++) {
const segment = pathArray[i];
// Create intermediate objects if needed
if (!(segment in current)) {
current[segment] = {};
}
current = current[segment];
}
current[pathArray[pathArray.length - 1]] = value;
}
// Even safer: Use a library like lodash with prototype pollution fix
const _ = require('lodash');
// Modern lodash versions protect against prototype pollution
// Fixed: Explicit property assignment
<?php
class User {
public $username;
private $role = 'user';
private $isAdmin = false;
public function setUsername($username) {
$this->username = $username;
}
// No public setter for role or isAdmin
// They can only be set through trusted internal methods
public function promoteToAdmin() {
// This would require proper authorization check
$this->role = 'admin';
$this->isAdmin = true;
}
}
// Fixed: Never unserialize user input
// Use JSON instead
$userData = json_decode($_COOKIE['user_data'], true);
$user = new User();
// Only set allowed fields
if (isset($userData['username'])) {
$user->setUsername($userData['username']);
}
// role and isAdmin cannot be set from external input
?>
CVE Examples
- CVE-2024-3283: LLM application allowed modification of sensitive variables through mass assignment.
- CVE-2012-2054: Mass assignment vulnerability in web application allowed privilege escalation.
- CVE-2012-2055: Mass assignment via URL parameters in version control system.
- CVE-2008-7310: E-commerce application allowed payment bypass through mass assignment.
- CVE-2013-1465: PHP unserialize vulnerability enabled object injection attack.
Related CWEs
- CWE-913: Improper Control of Dynamically-Managed Code Resources (parent)
- CWE-1321: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution') (child)
- CWE-502: Deserialization of Untrusted Data (related)
References
- MITRE Corporation. "CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes." https://cwe.mitre.org/data/definitions/915.html
- OWASP. "Mass Assignment Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Mass_Assignment_Cheat_Sheet.html
- Ruby on Rails Security Guide. "Mass Assignment."