Mass Assignment Vulnerabilities in Laravel Applications
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 cautionforceFill
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.