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 -> falseIn 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?