Programming frameworks have gained popularity due to their ability to make software development easier than using the underlying language alone. However, when developers don’t fully understand how framework functionality can be abused by attackers, vulnerabilities are often introduced.

One commonly used framework feature is known as mass assignment, a technique designed to help match front end variables to their back end fields, for easy model updating.

Implementing mass assignment

We’ll be using PHP/Laravel as an example to demonstrate how mass assignment works via the Laravel framework. Let’s imagine you have a form which allows a user to update some of their profile details, and that form contains the following fields:

<form method="POST" action="/updateuser">
    <input type="text" name="name" />
    <input type="text" name="email" />
    <input type="text" name="address" />
    <input type="text" name="phone" />
    <button type="submit">Signup</button>
</form>

Within the Laravel controller, one way to update those fields would be as follows:

public function updateUser(Request $request)
{
    $user = Auth::user();
    $user->name = $request->post('name');
    $user->email = $request->post('email');
    $user->address = $request->post('address');
    $user->phone = $request->post('phone');
    $user->save();
}

An alternative way to do this would be to take advantage of mass assignment, which would look something like this:

public function updateUser(Request $request)
{
    $user = Auth::user();
    $user->update($request->all());
}

This code updates the User model with the values from the Request (in this case the HTML fields for name, email, address and phone) assuming that the input names match the models fields. This obviously saves superfluous lines of code, since all fields can be updated at once, instead of specifying individually.

The mass assignment vulnerability

So, how might an attacker exploit this?

As may be evident from the code above, the framework is taking all the input fields from the Request variable and updating the model without performing any kind of validation. Therefore, its trusting that all the fields provided are intended to be updateable.

Although the example currently only provides options for updating fields such as name and email, there are usually more columns in the User table which aren’t displayed on the front end. In this case, lets imagine that there is also a field named role, which determines the privilege of the user. The role field wasn’t displayed in the HTML form because the developers didn’t want users changing their own role.

However, with our understanding that the controller is simply trusting all input provided by the request to update the User model, an attacker can inject their own HTML into the page to add a field for role, simply by using built in browser tools. This can also be done by intercepting the request using a proxy and appending the field name and value, or by any other technique that allows client side modification.

<form method="POST" action="/updateuser">
    <input type="hidden" name="role" value="admin">
    <input type="text" name="name" />
    <input type="text" name="email" />
    <input type="text" name="address" />
    <input type="text" name="phone" />
    <button type="submit">Signup</button>
</form>

This time, when the controller is reached, the user model will be updated with the expected fields (name, email, address, phone) as well as the additional role field provided.  In this case, the vulnerability leads to privilege escalation.

This particular example demonstrates how mass assignment can be exploited to achieve privilege escalation, however it is often possible to bypass other controls using the same technique. For example, an application might prevent a username from being edited when updating profile information, to ensure integrity and accountability across audit trails. Using this attack, a user could perform malicious actions under the guise of one username before switching to another.

Countermeasures

There are several ways to protect against mass assignment attacks. Most frameworks provide defensive techniques such as those discussed in this section.

The general idea is to validate input before updating the model. The safest way to do this is to somewhat fall back to the original and more convoluted process of specifying each field individually. This also has the added benefit of providing the ability to add additional validation to each field beyond ensuring only expected fields are updated.

In Laravel, one way to do this would be as shown below; include some validation such as the maximum number of permissible characters for the name field, and then update the User model with the validated data. As the validate() function lists the exact fields expected, if the role field was appended as demonstrated in our previous sample attack, it would be ignored and have no effect.

public function updateUser(Request $request)
{
    $validatedData = $request->validate([
        'name' => ['required', 'max:255'],
        'email' => ['required', 'unique:users'],
        'address' => ['required'],
        'phone' => ['numeric']
    ]);
    $user = Auth::user();
    $user->update($validatedData);
}

An alternative method is to utilize allow lists and deny lists to explicitly state what fields can and cannot be mass assigned. In Laravel, this can be done by setting the $fillable property on the User model to state which fields may be updated in this way. The code below lists the four original fields from the HTML form, so if an attacker tried to append the role field, since its not in the $fillable allow list, it won’t be updated.

class User extends Model
{
    protected $fillable = [
        'name',
        'email',
        'address',
        'phone'
    ];
}

Similarly, deny lists can be used to specify which fields cannot be updated via mass assignment. In Laravel, this can be done using the $guarded property in the model instead. Using the following code would have the same effect as the above, since the role parameter has been deny listed.

class User extends Model
{
    protected $guarded = ['role'];
}

Conclusion

Mass assignment vulnerabilities are important issues to check for during software development and during penetration tests because they are often not picked up by automated tools, due to them often having a logic component. For example, a tool will not likely have the context to understand if a user has managed to escalate their privilege after a specially crafted request.

They are also often overlooked by developers, partly due to lack of awareness for how certain features can be exploited, but also due to pressure to complete projects since its faster to use mass assignment without performing input validation.

It’s important to understand that mass assignment vulnerabilities exist and can be exploited with high impact. A strong software development lifecycle and associated testing regime will reduce the likelihood of these vulnerabilities appearing in code.