PHP open_basedir, disable_functions — hardening php.ini
Concept
Mass assignment is a vulnerability where an attacker injects additional fields into a form submission to modify model attributes that shouldn't be user-settable (e.g., is_admin = 1, balance = 99999).
Laravel's protection mechanisms:
$fillable: Allowlist — only listed fields can be mass-assigned viacreate(),update(),fill().$guarded: Blocklist — all fields EXCEPT listed ones can be mass-assigned.$guarded = []disables protection entirely — dangerous.
Insecure direct object reference (IDOR): Accessing resources by ID without verifying ownership. User 1 accesses /orders/456 which belongs to User 2. Fix: always scope queries to the authenticated user: auth()->user()->orders()->findOrFail($id).
Directory traversal: Accessing files outside the intended directory via ../ sequences. Fix: use realpath() and verify the path starts with the allowed base directory.
Sensitive data exposure: Logging or returning sensitive data (passwords, API keys, PII). Fix: use $hidden on Eloquent models, use $casts to control serialization, implement toArray() to explicitly define what's exposed.
OWASP Top 10 (2021): Broken Access Control, Cryptographic Failures, Injection, Insecure Design, Security Misconfiguration, Vulnerable Components, Authentication Failures, Software Integrity Failures, Logging Failures, SSRF.
Code Example
<?php
// VULNERABLE — mass assignment
class User extends Model
{
protected $fillable = []; // empty — no fillable defined
protected $guarded = []; // empty guarded = no protection!
}
$user = User::create($request->all()); // attacker adds is_admin=1 to form!
// SAFE — fillable allowlist
class User extends Model
{
protected $fillable = ['name', 'email', 'password']; // ONLY these can be mass-assigned
// is_admin, balance, role, etc. are NOT fillable
protected $hidden = ['password', 'remember_token']; // hidden from ->toArray() / JSON
}
User::create($request->only(['name', 'email', 'password'])); // explicit too
User::create($request->validated()); // from validated Form Request
// IDOR fix — scope to authenticated user
// VULNERABLE
$order = Order::findOrFail($request->order_id); // any order, any user!
// SAFE — scoped to current user
$order = auth()->user()->orders()->findOrFail($request->order_id);
// or: Order::where('user_id', auth()->id())->findOrFail($request->order_id);
// Laravel gates and policies
class OrderPolicy
{
public function view(User $user, Order $order): bool
{
return $user->id === $order->user_id;
}
}
// In controller: $this->authorize('view', $order); // throws 403 if not authorized
// Sensitive data model — hide from serialization
class PaymentMethod extends Model
{
protected $hidden = ['card_number', 'cvv'];
// Only last 4 digits in JSON
protected $appends = ['masked_number'];
public function getMaskedNumberAttribute(): string
{
return '****' . substr($this->card_number, -4);
}
}