पाठ 15 / 25

Security: XSS, CSRF, SQL Injection and Passwords

Defend PHP applications against the most common web vulnerabilities.

The essential defences

Most PHP security incidents come from a handful of mistakes. Cross-site scripting (XSS): user data printed into HTML without escaping can run attackers' JavaScript; escape output with htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') or use a templating engine that escapes automatically (Twig, Blade), and add a Content-Security-Policy header. SQL injection: never concatenate input into SQL; use prepared statements with bound parameters. Cross-site request forgery (CSRF): state-changing forms need an unpredictable per-session token checked on submission (frameworks do this), plus SameSite cookies. Passwords: store only hashes made with password_hash() (bcrypt by default, or PASSWORD_ARGON2ID) and check with password_verify(); never use md5 or sha1 for passwords. Other essentials: validate uploaded files by content type and size and store them outside the web root; avoid eval, unserialize on untrusted data and shell commands built from input (escapeshellarg if unavoidable); generate secrets with random_bytes(); keep PHP and dependencies updated (composer audit).

Escaping, CSRF tokens and password hashing

Each defence is a small, consistent habit.

<?php
declare(strict_types=1);

function e(string $value): string
{
    return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

// XSS: escape every value printed into HTML
echo '<p>Welcome, ' . e($user['name']) . '</p>';

// CSRF: issue a token per session and verify it on POST
$_SESSION['csrf'] ??= bin2hex(random_bytes(32));
echo '<input type="hidden" name="csrf" value="' . e($_SESSION['csrf']) . '">';

if ($_SERVER['REQUEST_METHOD'] === 'POST'
    && !hash_equals($_SESSION['csrf'] ?? '', (string) ($_POST['csrf'] ?? ''))) {
    http_response_code(419);
    exit('Invalid form token');
}

// passwords: hash on sign-up, verify on login, rehash when the algorithm changes
$hash = password_hash($plainPassword, PASSWORD_DEFAULT);
if (password_verify($attempt, $hash)) {
    if (password_needs_rehash($hash, PASSWORD_DEFAULT)) {
        $hash = password_hash($attempt, PASSWORD_DEFAULT);   // store the upgraded hash
    }
}

Compare secrets with hash_equals

Comparing tokens with === can leak information through timing differences. hash_equals() compares in constant time, which is the right tool for CSRF tokens, API signatures and similar secrets.

त्वरित जाँच: How should passwords be stored in a PHP application?

  • Plain text in the database
  • As md5 hashes
  • As hashes from password_hash(), checked with password_verify()
  • Encrypted with a key stored in the same database
Answer

As hashes from password_hash(), checked with password_verify() — password_hash uses slow, salted algorithms designed for passwords.