PHP powers a huge portion of the web, which makes it a top target for attackers. The OWASP Top 10 — the definitive list of critical web application security risks — maps directly onto mistakes PHP developers make regularly. This post covers the essential defences every PHP application should implement in 2022.
SQL Injection: Parameterised Queries Are the Only Defence
Never interpolate user input into SQL strings. Always use PDO prepared statements:
// VULNERABLE — never do this
$result = $db->query("SELECT * FROM users WHERE email = '{$_POST['email']}'");
// SAFE — parameterised query
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?");
$stmt->execute([$_POST['email']]);
$user = $stmt->fetch();
Cross-Site Scripting (XSS)
Escape all output that includes user-controlled data. htmlspecialchars() with ENT_QUOTES and UTF-8 is the standard:
// Safe output
echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
// In WordPress
echo esc_html($value);
echo esc_attr($value); // for HTML attributes
echo esc_url($url); // for URLs
CSRF Tokens
Every state-changing form (login, settings, delete) must include a CSRF token. Generate a cryptographically random token, store it in the session, verify it on submission:
// Generate
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
// In form HTML
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
// Verify
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
http_response_code(403);
die('CSRF validation failed');
}
Password Hashing
Use password_hash() with PASSWORD_BCRYPT or PASSWORD_ARGON2ID. Never use MD5, SHA1, or unsalted hashes:
// Hash on registration
$hash = password_hash($plaintext, PASSWORD_ARGON2ID);
// Verify on login
if (password_verify($plaintext, $storedHash)) {
// authenticated
}
Security Headers
header('X-Frame-Options: DENY');
header('X-Content-Type-Options: nosniff');
header('Content-Security-Policy: default-src 'self'; script-src 'self'');
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
Input Validation vs Sanitisation
Validate that input meets your expectations (is it an email? is it within the allowed range?). Sanitise when you must store or display data that may contain special characters. Never rely solely on client-side validation — always validate server-side.
Security is not a one-time checklist — it is an ongoing discipline. Run your code through static analysis tools like PHPStan and Psalm, keep dependencies updated with composer audit, and conduct regular security reviews of authentication and data-handling code.