You’ve just deployed your Laravel app. Migrations are run, caches are cleared, and APP_DEBUG is set to false. You feel safe. But here’s the uncomfortable truth: the framework protects you only as long as you don’t open the door yourself. The juiciest exploits don’t live in the core — they live in the business logic you wrote last Friday under a deadline.
Let’s walk through ten real-world weak spots in Laravel applications, not as a boring checklist, but the way an attacker sees them — with mechanics, examples, and a hacker’s eye for elegance.
1. Mass Assignment: When Your Model Trusts Everything
The problem
You use User::create($request->all()) and an attacker slips is_admin: true into the request payload. Eloquent obediently saves everything.
Attack flow
Fix
Always define $fillable or $guarded. Never pass unfiltered inputs:
protected $fillable = ['name', 'email'];
// or
protected $guarded = ['is_admin', 'role', 'balance'];
$user->fill($request->only(['name', 'email']));2. Information Leakage via Debug Mode
The problem
You forgot APP_DEBUG=true on production. A 500 error spills stack traces, file paths, package versions, and environment variables. For an attacker, it’s like finding the blueprint of your house.
Visual
Fix
APP_DEBUG=false
APP_ENV=productionPipe errors to Sentry, Flare, or ELK. Show users nothing but a friendly error page.
3. SQL Injection: When Raw Queries Bite
The problem
Concatenating user input into SQL, or misusing DB::raw(), instantly undoes all of Laravel’s protections.
Vulnerable code
$user = DB::select("SELECT * FROM users WHERE email = '$email'");An input of ' OR 1=1 -- returns the entire table.
Safe approach
DB::table('users')->where('email', $email)->first();
// or parameterized
DB::select('SELECT * FROM users WHERE email = ?', [$email]);Even ->orderByRaw() must use placeholders, never interpolation.
4. Insecure Deserialization: Zombie Objects
The problem
PHP’s unserialize() on untrusted data can lead to object injection, chaining gadgets into remote code execution. In Laravel, this lurks in queues (if serialized with PHP), custom cache drivers, and session handlers.
Attack chain concept
Hardening
- Never
unserialize()data you didn’t generate. - Switch queues and cache to JSON serialization.
- Restrict allowed classes in job payloads.
- Avoid
'serialize'as the session driver.
5. CSRF & SPA: When Protection Gets Disabled “For Convenience”
The problem
Removing VerifyCsrfToken globally or misconfiguring Sanctum for SPAs leaves state-changing endpoints open to cross-site request forgery.
Fix
- Never globally disable CSRF middleware; exclude only specific webhook routes.
- For APIs consumed by mobile/native apps, use token-based auth (Sanctum API tokens, Passport).
- For SPAs on the same domain, use Sanctum’s cookie mode and keep
same_site: lax.
6. Session Hijacking & Brute-Force: The Login Sieve
The problem
Cookies without HttpOnly and Secure flags get stolen via XSS or MitM. Without rate limiting, attackers brute-force passwords freely.
Defense in depth
SESSION_SECURE_COOKIE=true
SESSION_HTTPONLY=true
SESSION_SAME_SITE=laxAdd a login rate limiter:
RateLimiter::for('login', fn (Request $request) =>
Limit::perMinute(5)->by($request->input('email').$request->ip())
);Always hash passwords with Hash::make() (bcrypt/argon2) and enable two-factor authentication (Laravel Fortify).
7. XSS: When Blade Can’t Save You
The problem{!! $comment !!} outputs raw HTML. An attacker injects <script>steal(document.cookie)</script>.
Safe output
{{ $comment }} <!-- auto-escaped -->If you must render HTML, sanitize with HTMLPurifier and enforce a strict Content Security Policy:
Content-Security-Policy: default-src 'self'8. IDOR: Accessing Other People’s Orders
The problem
Direct object references without ownership checks let anyone browse resources by changing an ID in the URL.
Flow
Fix
Always scope queries to the authenticated user:
$order = Order::where('user_id', Auth::id())->findOrFail($id);
// plus Policy
$this->authorize('view', $order);9. Open Redirects: “Forward Me Anywhere”
The problem
A controller redirects to a user-supplied URL, enabling phishing and redirect chains.
Dangerous
return redirect()->away($request->input('to'));Safe
return redirect()->to('/dashboard');If dynamic redirects are needed, validate against a whitelist of allowed domains. Use intended() with proper filtering.
10. Zombie Dependencies: A Ticking Time Bomb
The problem
You pull in a package for a tiny feature, forget about it, and a year later it has a critical CVE — and your composer.lock still pins the vulnerable version.
Maintenance cadence
- Run
composer auditin CI. - Enable GitHub Dependabot or Snyk.
- Remove unused packages; every extra dependency is a potential door.
Practical Audit Checklist
Before every release, verify:
- Environment:
APP_DEBUG=false,APP_KEYset, no test secrets in code. - Models: correct
$fillable/$guarded. - Database: zero raw SQL with string interpolation.
- Auth: rate limiting, 2FA, secure cookie flags.
- Authorization: Policies/Gates everywhere.
- Frontend: Blade escaping, CSP headers, HTML filtering.
- Errors: no stack traces in responses.
- Redirects: only safe internal paths.
- Dependencies:
composer auditclean, packages updated. - Automation: PHPStan/Larastan on CI, periodic SAST/DAST scans, basic pentests.
Final Word
Laravel security isn’t a magic flag in your .env — it’s a discipline. The most spectacular breaches don’t come from 0-days in the core; they come from sloppy business logic. Study the mechanics, run an internal audit with the mindset “what if I’m the attacker?”, and you’ll keep the real bad guys out.
Комментарии (0)
Пока нет комментариев — будьте первым.