# Security: XSS, CSRF, SQL Injection and Passwords — PHP

Source: https://www.skillbyai.com/en/php/w-security

> 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
<?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.

**Quiz:** How should passwords be stored in a PHP application?

- [ ] Plain text in the database
- [ ] As md5 hashes
- [x] 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.
