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
42: Basic Security: SQL Injection and XSS Prevention
Up until now, we've focused on making things work. But in the real world, making something work is only half the battle; the other half is making sure a stranger can't use your own code to tear your database apart or hijack your users' sessions. Today, we're tackling the two "big ones": SQL Injection and Cross-Site Scripting (XSS).
To show you how this works, we're going to build a tiny "Community Shoutbox"—just a page where people can post a quick message and view previous ones. It sounds simple, but it's a minefield if you aren't careful.
Building the Vulnerable Shoutbox
I'll start by writing the code the way I did when I first started out—and the way you should never do it. I want to get a message from a POST request and save it to the database.
// DON'T DO THIS
$message = $_POST['message'];
$user = $_POST['username'];
$sql = "INSERT INTO shoutbox (username, message) VALUES ('$user', '$message')";
$db->query($sql);
At first glance, this looks fine. It works. But I've just left the front door wide open. If a user enters ' OR 1=1 -- as their username, they aren't just providing a name; they're actually altering the SQL command itself. In a SELECT query, this could let them log in without a password or dump your entire user table. This is SQL Injection: letting user input "leak" into the command logic.
Fixing the Leak with Prepared Statements
The fix here isn't to try and "clean" the string by stripping out quotes—hackers are way smarter than our regex patterns. Instead, we use prepared statements. This tells the database: "Here is the template of my query, and here is the data. Keep them separate."
// The professional way
$message = $_POST['message'];
$user = $_POST['username'];
// We use placeholders (?) instead of variables
$stmt = $pdo->prepare("INSERT INTO shoutbox (username, message) VALUES (?, ?)");
$stmt->execute([$user, $message]);
By using prepare() and execute(), the database treats the input strictly as data, not as executable code. Even if the user types a malicious SQL command, it just gets saved as a very weird-looking username in the table. Problem solved.
The "Invisible" Threat: XSS
Now that the data is safely in the database, we need to show it on the page. I'll write a quick loop to display the shouts. Here is where I usually make a mistake when I'm rushing: I forget that the database is now holding "untrusted" data.
// My mistake: Trusting the database
foreach ($messages as $row) {
echo "" . $row['username'] . ": " . $row['message'] . "
";
}
I thought I was safe because I used prepared statements to save the data. But SQL Injection and XSS are different beasts. If a user posted the message <script>alert('Your session cookie is: ' + document.cookie)</script>, that script is now stored in my database. When other users visit the page, their browser will execute that JavaScript.
This is Cross-Site Scripting. A malicious user could use this to steal login cookies, redirect your users to a phishing site, or deface your page. The database didn't "break," but the user's browser did.
Neutralizing the Payload
The rule of thumb is: Filter on the way in, escape on the way out. We already filtered the SQL on the way in. Now, we must escape the HTML on the way out. PHP has a perfect tool for this: htmlspecialchars().
// The secure way to display data
foreach ($messages as $row) {
$safe_user = htmlspecialchars($row['username'], ENT_QUOTES, 'UTF-8');
$safe_msg = htmlspecialchars($row['message'], ENT_QUOTES, 'UTF-8');
echo "" . $safe_user . ": " . $safe_msg . "
";
}
Now, if someone tries to post a <script> tag, htmlspecialchars converts the < into <. The browser will simply print the text "<script>" on the screen instead of executing it. You've effectively neutralized the bomb.
📋 Practical Task
Exercise: Securing the User Profile Search Page
You've been handed a legacy search page that is dangerously insecure. Your task is to refactor the following code to prevent both SQL Injection and XSS.
The Vulnerable Code:
// Search form submits 'search_term' via GET
$term = $_GET['search_term'];
// VULNERABLE SQL
$query = "SELECT username, bio FROM users WHERE username LIKE '%$term%'";
$results = $pdo->query($query)->fetchAll();
echo "Search results for: " . $term . "
";
foreach ($results as $user) {
echo "User: " . $user['username'] . " - Bio: " . $user['bio'] . "
";
}
Requirements:
- Rewrite the database query using a PDO prepared statement to prevent SQL Injection.
- Wrap the
$termvariable in the<h1>tag withhtmlspecialchars()to prevent XSS. - Wrap the
$user['username']and$user['bio']outputs withhtmlspecialchars()to ensure no stored XSS can trigger when displaying the results.
There are no comments for now.