PHP
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Object-Oriented PHP
-
Section 5: Working with Data
-
Section 6: Modern PHP (PHP 8)
-
Section 7: Working with Files and Networking
-
Section 8: Common PHP Frameworks Overview
-
Section 9: Tooling and Ecosystem
-
Section 10: Practical Projects
-
Section 11: Interview Practice
-
Section 12: More Standard Library
-
Section 13: Data Structures and Algorithms in PHP
-
Section 14: More Practice Exercises
-
Section 15: More Security and Best Practices
-
Section 16: More Testing and Tooling
-
Section 17: WordPress-Style CMS Concepts
-
Section 18: Advanced OOP Practice
-
Section 19: More Web Fundamentals
-
Section 20: Database Practice
-
Section 21: PHP Manual: Array Functions
-
Section 22: PHP Manual: Date and Calendar Functions
-
Section 23: PHP Manual: Filesystem and Directory Functions
-
Section 24: PHP Manual: Filter and Var Handling
-
Section 25: PHP Manual: Math Functions
-
Section 26: PHP Manual: JSON and XML
-
Section 27: PHP Manual: Network and Stream Functions
-
Section 28: PHP Manual: Error and Exception Handling
-
Section 29: PHP Manual: Output Control and Misc
-
Section 30: PHP Manual: FTP, Zip, and Mail
-
Section 31: Modern PHP Frameworks Deep Dive
-
Section 32: PHP Design Patterns
-
Section 33: More Practice Exercises
-
Section 34: PHP Performance and Deployment
-
Section 35: More Interview Practice
-
Section 36: More PHP Standard Library
-
Section 37: PHP Concurrency and Async
-
Section 38: More Web Development Practice
-
Section 39: PHP Testing Deep Dive
-
Section 40: Composer and Package Development
-
Section 41: PHP Security Deep Dive
-
Section 42: More Practical Projects
-
Section 43: Legacy PHP Maintenance
-
Section 44: More Algorithm Practice
-
Section 45: Final Practice and Review
-
Section 46: PHP for E-Commerce Patterns
-
Section 47: PHP API Design Deep Dive
-
Section 48: PHP Caching Strategies
-
Section 49: PHP Queue and Background Jobs
-
Section 50: PHP Multi-Tenancy Patterns
-
Section 51: PHP Real-Time Features
-
Section 52: PHP CMS and Content Modeling
-
Section 53: PHP Internationalization
-
Section 54: More Framework-Specific Practice
-
Section 55: PHP Legacy Code Refactoring
-
Section 56: More Practice Projects Round 2
-
Section 57: PHP Command-Line Applications
-
Section 58: PHP and Microservices
-
Section 59: More Interview and Review Round 2
161: Building CSRF-Protected Forms
Imagine you walk into your favorite local bank. You've already shown your ID at the door, so the staff knows exactly who you are. Now, imagine a prankster stands outside the window and manages to slide a piece of paper through the slot that says, "Transfer $500 to the prankster's account." Because you're already "verified" in the building, the teller might just see the request and execute it, thinking you're the one who asked.
That’s exactly how a Cross-Site Request Forgery (CSRF) attack works. The "bank" is your web server, and the "verified user" is someone with an active session cookie. The attacker doesn't need to steal the user's password; they just need to trick the user's browser into sending a request to your server while the user is still logged in. Since the browser automatically attaches the session cookie, your server thinks the request is legitimate.
To stop this, we need a "transaction slip"—a unique, secret token that the server generates and requires for every state-changing request. If the request doesn't come with the correct slip, the server rejects it, even if the user is logged in.
Generating the Secret Handshake
The first thing we need to do is create a token that is impossible for an attacker to guess. I usually recommend random_bytes() for this because it's cryptographically secure. We store this token in the user's session so we can check it later.
session_start();
// Generate a token if one doesn't already exist for this session
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
I put this logic in a helper function or a global initialization file. You only need to generate it once per session, or you can rotate it after every single request if you're building something ultra-secure, like a banking app. For most projects, once per session is plenty.
Embedding the Token in Your Forms
Now that the server knows the secret, we have to give it to the client. We do this by dropping it into a hidden input field in our HTML form. The user never sees it, but it gets sent back to us when they hit "Submit."
<form action="update_profile.php" method="POST">
<label for="email">New Email:</label>
<input type="email" name="email" id="email">
<!-- This is the magic piece -->
<input type="hidden" name="csrf_token" value="">
<button type="submit">Update Profile</button>
</form>
If an attacker tries to forge a request from a different website, they can't "reach into" your user's session to find this token. Their forged form will either be missing the token or contain a fake one, and that's where we catch them.
The Moment of Truth: Verification
When the POST request hits your server, you must verify the token before doing anything else. If the token is missing or doesn't match what's in the session, you stop the script immediately. I usually throw a 403 Forbidden error here because the request is fundamentally unauthorized.
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
// Something is fishy. Kill the request.
http_response_code(403);
die("CSRF token validation failed. Request denied.");
}
// If we got here, the request is legitimate.
// Now we can safely update the user's email.
$new_email = $_POST['email'];
// ... database logic here ...
}
One quick tip: always use the strict comparison operator (!==) when checking tokens. You don't want PHP's type juggling to accidentally let a "falsey" or numeric value pass through as a match.
📋 Practical Task
Securing a User Profile Email Update Form
You have been given a basic PHP profile update page that is currently vulnerable to CSRF attacks. Your task is to secure it.
Requirements:
- Implement
session_start()at the top of both the form page and the processing script. - Create a mechanism to generate a cryptographically secure CSRF token and store it in
$_SESSION['csrf_token']if it doesn't already exist. - Modify the HTML form to include a hidden input field containing this token.
- In the processing script, write a check that compares the
$_POST['csrf_token']against the$_SESSION['csrf_token']. - Ensure the script terminates with a
403 Forbiddenresponse if the tokens do not match or if the token is missing.
There are no comments for now.