In Depth: Mass-Assignment Vulnerabilities
[InDepth#15] There is a false confidence about mass-assignment vulnerabilities that hides how easy it is for them to occur and be exploited...
#1 → Exposed API Keys & Passwords
#2 → Missing Authorisation
#3 → Missing Content Security Policy (CSP)
#4 → Missing Security Headers
#5 → Insecure Function Use
#6 → Outdated & Vulnerable Dependencies
#7 → Cross-Site Scripting (XSS)
#8 → Insufficient Rate Limiting
#9 → Missing Subresource Integrity (SRI)
#10 → Insufficient Input Validation & Mass-Assignment Vulnerabilities
When you think of Mass-Assignment vulnerabilities on Laravel, I guarantee you’ll immediately think of the humble $fillable
Eloquent property. That’s definitely what the Laravel Docs teach you…
If you’re not familiar with the $fillable
Eloquent property, this is how it works:
First, you define the property on your model:
class User extends Model
{
protected $fillable = ['name', 'email'];
}
Next, when creating or updating the model, only the allowed attributes can be filled.
$input = [
'name' => 'Bilbo Baggins',
'email' => 'bilbo@example.com',
'admin' => true
];
$user = User::create($input);
// $user->name -> 'Bilbo Baggins'
// $user->email -> 'bilbo@example.com'
// $user->admin -> false
In the above example, the admin = true
flag is ignored when creating the User
model, protecting against a mass-assignment exploit to escalate the privileges of the newly created user.
And this is where many people stop learning about mass-assignment vulnerabilities and assume they are covered.
But are they?
I would argue that $fillable
is a less important, and possibly even optional, protection against mass-assignment. It's great to use if you can, but not essential, and definitely not my first choice when protecting against Mass-Assignment.
In fact, I often allow mass-assignment across all fields of a Model using $guarded = [];
, or even Model::unguard()
across the entire app in AppServiceProvider
. As controversial as that may be, I find $fillable
gets in my way more often than it protects me. Disabling the protection actually makes the code cleaner and more maintainable.
So what do I do instead?