PHP Security: XSS and SQL Injection

Three of the most common ways PHP apps get compromised are Cross-Site Scripting (XSS), SQL Injection, and storing passwords in plain text. This page explains each one and shows the real PHP functions that prevent them, with genuinely-executed live demos.

1) Cross-Site Scripting (XSS)

XSS happens when user-supplied text is echoed into HTML without escaping, so a malicious visitor can submit something like <script>alert(1)</script> and have it execute in other users' browsers (in a comment box, profile name, search results page, etc.).

<?php
  // DANGEROUS: never do this with user input
  echo "Hello, " . $_GET['name'];
?>
The fix: htmlspecialchars() on all user-generated output
<?php
  echo "Hello, " . htmlspecialchars($_GET['name'], ENT_QUOTES, 'UTF-8');
?>

ENT_QUOTES escapes both single and double quotes too, so the output is safe inside value="..." attributes as well as regular text.

2) Live Demo: Escaped vs Unsafe Output

For safety, this real site will never print your raw, unescaped input into its own HTML — that would create a genuine XSS hole on our own page. Both boxes below use htmlspecialchars() so anything you type is shown as harmless text either way. The first box is labeled to show you exactly what an unescaped version would have let happen; the second box is the real, correctly-escaped output you should always use in production.

3) SQL Injection

SQL Injection happens when user input is concatenated directly into a SQL query string, letting an attacker change the query's meaning.

<?php
  // DANGEROUS: string concatenation
  $sql = "SELECT * FROM users WHERE email = '" . $_POST['email'] . "'";
?>
The fix: prepared statements
<?php
  $stmt = $pdo->prepare('SELECT * FROM users WHERE email = ?');
  $stmt->execute([$_POST['email']]);
  $user = $stmt->fetch();
?>
Prepared statements send the SQL and the data separately, so the database never confuses user input with SQL syntax. See php-mysql.php for a fuller walkthrough of connecting with PDO and running prepared queries.

4) Password Hashing

Never store plain-text passwords — if your database ever leaks, every user's real password leaks with it. Instead, hash the password with password_hash() and only ever compare hashes with password_verify().

<?php
  // When the user registers:
  $hash = password_hash($plainPassword, PASSWORD_DEFAULT);
  // store $hash in the database, never $plainPassword

  // When the user logs in:
  if (password_verify($plainPassword, $hash)) {
    echo 'Login OK';
  }
?>

5) Live Demo: password_hash() and password_verify()

6) A Word on CSRF

Cross-Site Request Forgery (CSRF) tricks a logged-in user's browser into submitting a request they didn't intend. The Forms & User Input page already implements a real CSRF token pattern (session token + hidden input + hash_equals()) — the same pattern protects both demo forms on this page.

Golden rules: escape all output with htmlspecialchars(), never build SQL with string concatenation, always hash passwords with password_hash(), and always include a CSRF token on state-changing forms.