This is the twentieth and final article in the PHP and Laravel security series.
Over the past nineteen articles we covered:
- Article 1: What your PHP logs actually look like during a SQL injection attack
- Article 2: Why URL encoding can break PHP security checks
- Article 3: The decode bomb problem — why unlimited URL decoding can be its own vulnerability
- Article 4: Parameterized queries — the only real fix for SQL injection
-
Article 5: XSS prevention in Laravel and why
{!! !!}is the line between safe and hacked - Article 6: How attackers enumerate your Laravel app before exploiting it
- Article 7: File upload security — the file that isn't what it claims to be
-
Article 8: Path traversal in PHP — how
../escapes your application -
Article 9: Command injection in PHP — when
exec()becomes an attack surface - Article 10: Broken access control in Laravel — why being logged in is not enough
-
Article 11: Secrets in Laravel — why
.envis only the beginning - Article 12: Session security in PHP — what most developers get wrong
- Article 13: Rate limiting in Laravel and PHP — how to stop brute force before it starts
- Article 14: Security headers in PHP and Laravel — the lines that harden every response
- Article 15: IDOR in PHP and Laravel — when changing one number exposes someone else's data
- Article 16: Mass assignment in PHP and Laravel — when user input becomes more than it should
- Article 17: Open redirect vulnerabilities in PHP and Laravel
- Article 18: PHP type juggling — when 0 equals admin
- Article 19: Insecure deserialization in PHP — the vulnerability hiding in your cache
Each article taught one attack. One defense. One layer.
This article answers the question that matters after reading all nineteen: how do you put it all together into one application that actually holds?
Why One Layer Is Never Enough
Every security control in this series can be bypassed in isolation.
Parameterized queries stop SQL injection but they do not stop path traversal. Security headers reduce XSS impact but they do not replace output encoding. Rate limiting slows brute force but it does not catch credential stuffing distributed across thousands of IP addresses.
Security works because controls overlap. Each layer catches what the others miss. When one control fails another is already in place.
The attacker needs to find one gap. You need to close all of them.
That is defense in depth. Not a product. Not a checklist you complete once. A set of overlapping layers that together create a posture significantly harder to breach than any single control alone.
The Attack Journey — What Each Layer Stops
To understand why every layer matters follow a real attack from reconnaissance to breach and see exactly which control stops it at each stage.
Stage 1 — Reconnaissance
Before an attacker exploits your application they study it. They look for debug mode left on, .env files accessible via browser, .git directories exposed, default error pages revealing framework versions, admin panels at predictable URLs, and developer tools accessible without authentication.
Article 6 and Article 11 address this. An application that leaks no information during reconnaissance forces the attacker to work blind. They cannot target known vulnerabilities in your framework version. They cannot enumerate routes. They cannot find admin panels to brute force.
Stage 2 — Input Testing
The attacker sends malicious input to every parameter they can find — SQL injection payloads, XSS scripts, path traversal sequences, command separators, type juggling values.
Articles 4, 5, 8, 9, and 18 address this. Parameterized queries mean SQL injection payloads hit prepared statements and return no result. Output encoding means XSS payloads render as text not code. realpath() validation means traversal sequences resolve to blocked paths. Shell function avoidance and escapeshellarg() reduce command injection risk. Strict comparison means type juggling payloads fail at the comparison layer.
Stage 3 — Authentication Attack
The attacker cannot get in through injection so they target authentication — brute force, credential stuffing, session attacks.
Article 12 and Article 13 address this. Rate limiting makes brute force significantly slower — 5 attempts per minute means 10,000 passwords takes hours instead of seconds. Session regeneration after login prevents fixation. Cookie security attributes HttpOnly and Secure help prevent session theft through certain vectors. Strong password hashing and password_verify() prevent type juggling bypasses on login.
Stage 4 — Privilege Escalation
The attacker gets in as a regular user. Now they try to reach higher privileges.
Articles 10, 15, 16, and 19 address this. $fillable and Form Requests mean extra fields in requests are silently ignored. Scoped queries and policies mean resource ID changes return 404 for resources that do not belong to the user. Middleware and authorization checks mean privileged endpoints reject unauthorized requests.
Stage 5 — Data Exfiltration and Persistence
The attacker has access. Now they try to steal data or leave a backdoor.
Articles 7, 9, 11, 17, and 19 address this. Files stored outside the web root with random names cannot be executed via URL. Secret management practices mean credentials are not accessible through misconfigured endpoints. Whitelist-based redirects mean your domain cannot be weaponized for phishing. JSON serialization means web shell injection through deserialization has no attack surface.
The Complete Layer Map
| Attack Stage | Topic | Article |
|---|---|---|
| Reconnaissance | Enumeration and information leakage | 6 |
| Reconnaissance | Secret and environment exposure | 11 |
| Input injection | SQL injection detection and prevention | 1, 2, 3, 4 |
| Input injection | XSS prevention | 5 |
| Input injection | Path traversal | 8 |
| Input injection | Command injection | 9 |
| Input injection | Type juggling | 18 |
| Input file handling | File upload security | 7 |
| Authentication | Brute force and credential stuffing | 13 |
| Authentication | Session security | 12 |
| Privilege escalation | Mass assignment | 16 |
| Privilege escalation | IDOR | 15 |
| Privilege escalation | Broken access control | 10 |
| Privilege escalation | Deserialization | 19 |
| Data handling | Secrets management | 11 |
| Navigation | Open redirect | 17 |
| Browser layer | Security headers | 14 |
| All stages | App security and Behavioral monitoring and AI detection | Kriosa |
No single article covers every stage. Every stage requires multiple articles. That is the point.
The Checklist — Everything in One Place
Before first deployment:
-
APP_DEBUG=falseandAPP_ENV=production -
.envin.gitignorebefore first commit - No secrets hardcoded anywhere in the codebase
- All database queries use parameterized statements
- All output encoded with
{{ }}in Blade —{!! !!}reviewed explicitly -
$fillabledefined on every Eloquent model that accepts user input - All file uploads validated for content, stored with random names outside the web root
- PHP execution disabled in any directory that stores user uploads
- Security headers middleware registered globally
- HTTPS configured before enabling HSTS
Authentication and sessions:
-
session_regenerate_id(true)called after every login - Session cookie attributes:
HttpOnly,Secure,SameSite=Lax -
password_verify()used for all password comparisons -
hash_equals()used for all token comparisons - Rate limiting applied to login, password reset, registration, and OTP endpoints
- Three-step logout:
Auth::logout(),invalidate(),regenerateToken()
Authorization:
- Every resource endpoint has an ownership check — not just authentication
- Scoped queries used as the default pattern
- Policies implemented for complex authorization logic
- API endpoints have the same authorization checks as web endpoints
- Sensitive fields absent from
$fillable:role,is_admin,balance,email_verified_at
Input handling:
-
===used for all equality comparisons in security contexts -
in_array()always called withtrueas the third argument -
json_encode()/json_decode()used instead ofserialize()/unserialize()where possible -
unserialize()called with['allowed_classes' => false]when unavoidable - Shell functions avoided where PHP native alternatives exist
-
realpath()used to validate file paths before filesystem operations - All redirect destinations validated against a whitelist or restricted to relative URLs
Server and infrastructure:
- Directory listing disabled
- Technology headers removed
-
.git,.env, and backup files blocked at web server level - Developer tools behind authentication or IP restriction
- Redis and queue storage on private networks
-
composer auditrunning in CI/CD pipeline -
composer installused in production — nevercomposer update -
composer.lockcommitted to version control
Ongoing:
- Security headers scanner run after every deployment
- Logs reviewed for rate limit violations, 403 patterns, and sequential ID requests
- Automated vulnerability alerts enabled for dependencies
- Secret rotation plan tested for every secret in the application
What This Series Covers
Nineteen articles. Nineteen attack vectors. Nineteen defensive layers.
A developer who has read and implemented every article in this series has addressed many of the most common vulnerability classes found in PHP and Laravel applications today — including multiple categories from the OWASP Top 10.
That is not a complete security posture. Security is never complete. New vulnerabilities are discovered. Dependencies introduce risks. Configurations drift. Developer mistakes happen.
But it is a foundation that makes your application meaningfully harder to breach than the average PHP application running in production today.
The Layer This Series Cannot Replace
Every control in this series is preventive. It stops attacks from succeeding at the application layer.
But prevention alone has a gap — it cannot tell you when someone is trying.
A developer who has implemented every article in this series has no visibility into which endpoints are being probed right now, whether resource ID enumeration is happening, whether credential stuffing is running below rate limit thresholds, or whether reconnaissance is underway.
That detection and visibility is what Kriosa is built to provide.
Kriosa sits in front of your PHP application and inspects incoming requests for attack patterns and behavioral signals before they reach your code. When something is flagged it surfaces an explanation in the dashboard — what was detected, why it was flagged, and what the request looked like.
It is not a replacement for any article in this series. Parameterized queries, session regeneration, $fillable, and hash_equals() are application-layer controls that Kriosa cannot replicate. Those come first.
Kriosa is the detection layer on top of them.
Try it free: kriosa.com
Install it: composer require kriosa-ai/kriosa-php
What Comes Next
This series is twenty articles. Twenty attack vectors. Twenty defensive layers.
The next step is not more reading.
It is applying what you have learned to a real PHP application running in production right now.
Start with the checklist in this article. Run the audit commands from articles 4, 8, 9, 15, and 16 against your own codebase. Check your session configuration against article 12. Verify your rate limiting against article 13. Run composer audit against your dependencies.
One hour of auditing a real codebase teaches more than ten more articles.
Then install Kriosa — one composer command — and see what is actually hitting your application. Not what you think is hitting it. What is actually there.
The series taught the mindset. Now use it.
A Final Word
This series started with one question — what does a SQL injection attack actually look like in your PHP logs?
It ended nineteen articles later with defense in depth.
In between we covered every major attack vector hitting PHP applications today. Every article followed the same principle: understand the attack before you try to stop it.
That principle does not change after the series ends. New vulnerabilities will be discovered. New attack techniques will emerge. The mindset — understand first, then defend — is what makes security knowledge durable.
The series taught the mindset. Kriosa watches the traffic.
Built by a developer from Cameroon, for developers who want to understand their security — not just outsource it.
The Complete Series
- Article 1: What your PHP logs actually look like during a SQL injection attack
- Article 2: Why URL encoding can break PHP security checks
- Article 3: The decode bomb problem — why unlimited URL decoding can be its own vulnerability
- Article 4: Parameterized queries — the only real fix for SQL injection
-
Article 5: XSS prevention in Laravel and why
{!! !!}is the line between safe and hacked - Article 6: How attackers enumerate your Laravel app and what to hide
- Article 7: File upload security in PHP and Laravel
-
Article 8: Path traversal in PHP — how
../escapes your application -
Article 9: Command injection in PHP — when
exec()becomes an attack surface - Article 10: Broken access control in Laravel — why being logged in is not enough
-
Article 11: Secrets in Laravel — why
.envis only the beginning - Article 12: Session security in PHP — what most developers get wrong
- Article 13: Rate limiting in Laravel and PHP — how to stop brute force before it starts
- Article 14: Security headers in PHP and Laravel — the lines that harden every response
- Article 15: IDOR in PHP and Laravel — when changing one number exposes someone else's data
- Article 16: Mass assignment in PHP and Laravel — when user input becomes more than it should
- Article 17: Open redirect vulnerabilities in PHP and Laravel
- Article 18: PHP type juggling — when 0 equals admin
- Article 19: Insecure deserialization in PHP — the vulnerability hiding in your cache
- Article 20: This article — defense in depth and how every layer works together