Mass Assignment Vulnerabilities in Laravel Applications

CyberPanda
CyberPanda
Published in
3 min readAug 10, 2020
Photo by Karol Kasanicky on Unsplash

Eloquent like many other ORMs have a nice feature that allows assigning properties to an object without having to assign each value individually, this is a nice feature that saves a lot of time and lines of code but can lead to a vulnerability if used incorrectly.

For example here is a simplified example of an insecure code that we found during a security code review for one of our clients

The issue here is if a malicious attacker submits the payload below, he will be able to register an admin user with higher privileges and probably take over the admin panel of the application.

{ 
"name" : "Hacker",
"email" : "hacker@example.com",
"role" : "administrator",
"password" : "some_random_password",
"confirm_password" : "some_random_password"
}

Also, you might wonder here why role is in the $fillable attribute, if the "role" wasn't there, there would not be a security issue. The reason that it was added there is that there was another API that would allow changing role too, this is a common pattern that we see in Laravel applications that we review or pen-test, while $fillable is great if you have a simple application, it gets hard to manage when application growths and there are multiple APIs(or Roles) that are updating/creating a same type of model with different ACL roles.

Here are a few tips on how you can prevent mass assignment vulnerabilities in Laravel applications.

Prevention tips

Pass to model only fields that have been validated
This is probably the most effective method of dealing with mass assignment attacks, instead of passing the full data from the request, you can pass only fields that have been validated. In the above code example that will be the name, email and password. To do so, Laravel provides you with a $request->validated() method, that returns you only fields that have been validated. So in the code above, replacing $request->all() with $request->validated() would fix the issue.

$user = new User(); 
$user->role = User::ROLE_USER;
$user->fill($request->validated());
$user->save();

If you are not using Laravel’s request validation, you can also use $request->validate() or $validator->validated() which also returns only data that has been validated.

Use whitelisting instead of blacklisting
We encourage using $fillable(which is whitelisting only columns that can be mass assigned) instead of $guarded(defines properties that can't be mass assigned) because you may easily forget to add a new column to a $guarded array, leaving it open for mass assignment by default.

Use $model->forceFill($data) method with caution
forceFill ignores all the config that is set in $guarded and $fillable properties and saves passed data as it is into the model. If you have to use forceFill, make sure passed data can not be manipulated by the user.

Originally published at https://cyberpanda.la.

If you want to read more about Laravel Security, check out our Laravel Security Ebook.

--

--

CyberPanda
CyberPanda

We help startups to secure their web applications within reasonable cost — https://cyberpanda.la