CWE-915, Improperly Controlled Modification of Dynamically-Determined Object Attributes, is the weakness behind what developers call mass assignment, autobinding and object injection. The product receives input that names several attributes, properties or fields to set on an object, but does not control which ones may be changed.
It affects web frameworks and APIs in Ruby, ASP.NET, PHP, Python and others, in any code that copies a request body onto a model.
How it happens
Frameworks make it easy to turn a request into an object. One call takes every key in the payload and sets the matching field. The convenience is the problem. If the object has attributes meant only for internal use, such as a role, a balance, a price or an owner id, then an attacker who adds those keys to the request changes them.
# Vulnerable: every key in the JSON body is applied to the user
@app.route("/profile", methods=["PATCH"])
@login_required
def update_profile():
for key, value in request.json.items():
setattr(current_user, key, value)
db.session.commit()
return "", 204
A request body of name plus is_admin set to true would make the user an administrator.
# Fixed: only listed fields can change
ALLOWED = {"name", "email", "timezone"}
@app.route("/profile", methods=["PATCH"])
@login_required
def update_profile():
for key in ALLOWED & request.json.keys():
setattr(current_user, key, request.json[key])
db.session.commit()
return "", 204
The observed CVEs in the record:
- CVE-2024-3283 (details): an application for using LLMs allowed modification of a sensitive variable through mass assignment.
- CVE-2012-2054: mass assignment allowed modification of arbitrary attributes using a modified URL.
- CVE-2012-2055: a source version control product allowed modification of a trusted key through mass assignment.
- CVE-2008-7310: attackers could bypass the payment step in an e-commerce product.
The last one shows the business impact. A field that tracks payment status is just another attribute if nothing protects it.
What an attacker gains
MITRE lists:
- Integrity: modify sensitive data or program variables.
- Integrity: execute unauthorized commands or code.
- Other: alter execution logic, varying by context.
Which one applies depends on which attributes the object exposes. The trusted key example hints at how far it can reach.
How to find it
MITRE says automated static analysis (SAST) can find some instances by modeling data flow from sources of input to sinks, with high effectiveness. In manual review:
- Find every place a request body, query string or deserialized payload is bound onto a model or object.
- List the attributes of that model, and mark which are internal.
- Send a request that includes an internal attribute and see whether it changes.
That third test takes minutes and works against live endpoints. Add it to the test suite for every update and create endpoint.
How to fix it
The MITRE mitigations, in plain steps:
- Use the framework's allowlist feature where it exists, and prefer allowlists over denylists. In Ruby on Rails, MITRE names attr_accessible (allowlist) and attr_protected (denylist) macros on each class that may be used in mass assignment.
- Where data is deserialized, use the language's signing or sealing features to make sure it has not been tainted. For example, a hash based message authentication code (HMAC) can show data has not been modified.
- For any externally influenced input, check it against an allowlist of object attributes or fields that may be modified.
- Refactor so attributes do not need to be dynamically identified, and expose getter and setter functionality only for the intended attributes.
A good habit is a dedicated input type for each endpoint. The request is parsed into a small object that contains only the fields the user may send, and the code copies those fields onto the model by hand.
Quick check
Why is an allowlist better than a denylist here?
New sensitive fields get added over time. With a denylist each one must be remembered. With an allowlist new fields are protected by default.
How can you test an endpoint for this in minutes?
Add an internal field, such as a role flag, to a normal update request and check whether the stored value changes.
What do the other names for this weakness mean?
MITRE says it is sometimes known by language specific mechanisms: mass assignment, autobinding and object injection.