Skip to Content
Course content

42: Basic Security: SQL Injection and XSS Prevention

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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 &lt;. 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 $term variable in the <h1> tag with htmlspecialchars() to prevent XSS.
  • Wrap the $user['username'] and $user['bio'] outputs with htmlspecialchars() to ensure no stored XSS can trigger when displaying the results.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.