Defense in Depth — How Every Layer in This Series Works Together

php dev.to

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 .env is 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=false and APP_ENV=production
  • .env in .gitignore before first commit
  • No secrets hardcoded anywhere in the codebase
  • All database queries use parameterized statements
  • All output encoded with {{ }} in Blade — {!! !!} reviewed explicitly
  • $fillable defined 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 with true as the third argument
  • json_encode()/json_decode() used instead of serialize()/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 audit running in CI/CD pipeline
  • composer install used in production — never composer update
  • composer.lock committed 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 .env is 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

Source: dev.to

arrow_back Back to Tutorials