Mass Assignment Vulnerabilities Analyzer
| Analyzer ID | Category | Severity | Time To Fix |
|---|---|---|---|
mass-assignment-vulnerabilities | 🛡️ Security | High | 25 minutes |
What This Checks
Detects mass assignment vulnerabilities where unfiltered user input is passed to Eloquent models or query builders, allowing attackers to modify unintended database fields. This analyzer checks for models without $fillable or $guarded protection, dangerous method calls with request()->all(), query builder operations with raw request data, and multiple unsafe request data retrieval patterns.
Why It Matters
Mass assignment is one of the most dangerous security vulnerabilities in Laravel applications because it allows attackers to:
- Privilege Escalation: Set
is_admin = 1to gain administrative access - Account Takeover: Modify email addresses or passwords of other users
- Data Manipulation: Update protected fields like prices, status, or verification timestamps
- Bypass Business Logic: Circumvent validation rules and access controls
Real-World Impact: GitHub 2012
In March 2012, GitHub suffered a mass assignment vulnerability that allowed attackers to add SSH keys to any repository, including Rails's own codebase. This single vulnerability led to:
- Complete system compromise requiring emergency shutdown
- Forced suspension of GitHub services for security audit
- Major architectural security overhaul
The attack exploited unprotected mass assignment by sending:
POST /repositories
{
"public_key[id]": "attacker_key_id"
}Modern Laravel applications face identical risks when using request()->all() without proper model protection.
How to Fix
Quick Fix (5 minutes)
Add mass assignment protection to all Eloquent models immediately:
Before (❌):
class User extends Model
{
// No protection - vulnerable to mass assignment
}After (✅):
class User extends Model
{
// Whitelist approach (recommended)
protected $fillable = ['name', 'email', 'bio', 'avatar'];
// OR blacklist approach
protected $guarded = ['id', 'is_admin', 'role', 'created_at', 'updated_at'];
}Controller update:
Before (❌):
public function update(Request $request, User $user)
{
$user->update($request->all()); // Dangerous
}After (✅):
public function update(Request $request, User $user)
{
$user->update($request->only(['name', 'email', 'bio'])); // Safe
}Proper Fix (25 minutes)
Step 1: Define model protection strategy
Choose between $fillable (whitelist - recommended) or $guarded (blacklist):
// RECOMMENDED: Whitelist approach
class User extends Model
{
protected $fillable = [
'name',
'email',
'bio',
'avatar',
];
}
// ALTERNATIVE: Blacklist approach
class User extends Model
{
protected $guarded = [
'id',
'is_admin',
'role',
'email_verified_at',
'remember_token',
'created_at',
'updated_at',
];
}Best Practice: Use $fillable for better security - new fields are protected by default.
Step 2: Create Form Request validation
Generate validated request class:
php artisan make:request UpdateUserRequestBefore (❌):
public function update(Request $request, User $user)
{
$user->update($request->all());
return $user;
}After (✅):
// app/Http/Requests/UpdateUserRequest.php
class UpdateUserRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()->id === $this->route('user')->id;
}
public function rules(): array
{
return [
'name' => 'sometimes|string|max:255',
'email' => 'sometimes|email|unique:users,email,'.$this->user()->id,
'bio' => 'nullable|string|max:1000',
'avatar' => 'nullable|url',
];
}
}
// Controller
public function update(UpdateUserRequest $request, User $user)
{
$user->update($request->validated()); // Only validated fields
return $user;
}Step 3: Fix all dangerous patterns
Pattern 1: Eloquent create/update with request()->all()
Before (❌):
User::create($request->all());
Product::create($request->input());
$user->update($request->post());
$user->fill($request->get())->save();After (✅):
User::create($request->validated());
Product::create($request->only(['name', 'description', 'price']));
$user->update($request->validated());
$user->fill($request->validated())->save();Pattern 2: Query Builder operations
Before (❌):
DB::table('users')->update($request->all());
DB::table('products')->insert($request->all());After (✅):
DB::table('users')->update([
'name' => $request->validated('name'),
'email' => $request->validated('email'),
'updated_at' => now(),
]);
DB::table('products')->insert([
'name' => $request->validated('name'),
'price' => $request->validated('price'),
'created_at' => now(),
]);Pattern 3: Empty $guarded array
Before (❌):
class Product extends Model
{
protected $guarded = []; // Allows mass assignment of ALL fields
}After (✅):
class Product extends Model
{
protected $fillable = ['name', 'description', 'price', 'stock'];
}Step 4: Test your implementation
Create automated tests:
// tests/Feature/MassAssignmentTest.php
public function test_cannot_mass_assign_admin_role()
{
$response = $this->postJson('/api/users', [
'name' => 'John Doe',
'email' => 'john@example.com',
'is_admin' => 1, // Attempt to set admin
]);
$user = User::where('email', 'john@example.com')->first();
$this->assertFalse($user->is_admin); // Should NOT be admin
}Manual testing with curl:
# Attempt malicious request
curl -X POST http://localhost/api/users \
-H "Content-Type: application/json" \
-d '{"name":"John","email":"john@test.com","is_admin":1,"role":"admin"}'
# Verify in database
php artisan tinker
>>> User::latest()->first()
>>> # is_admin and role should NOT be setReferences
- Eloquent: Mass Assignment
- Validation: Form Request Validation
- OWASP: Mass Assignment Cheat Sheet
- CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes
- GitHub Mass Assignment Incident (2012)
- Mass Assignment: A New Software Vulnerability (OWASP PDF)
Related Analyzers
- SQL Injection Analyzer - Detects raw SQL queries with user input
- XSS Vulnerabilities Analyzer - Prevents cross-site scripting attacks
- CSRF Protection Analyzer - Validates CSRF token protection
- Authentication & Authorization Analyzer - Validates policy and gate checks
- Fillable Foreign Key Analyzer - Detects foreign keys in fillable arrays