Security checklist
The handful of mistakes behind most hacked PHP sites, and CSRF tokens.
Most PHP sites that get hacked fall to the same few mistakes. Check each one before you go live.
| Risk | Defence |
|---|---|
| SQL injection | Prepared statements with PDO, for every query |
| Cross-site scripting (XSS) | htmlspecialchars on everything you print that came from a user |
| Cross-site request forgery (CSRF) | A secret token in every form that changes data |
| Stolen session | session_regenerate_id at sign-in, and secure, httponly cookies |
| Stored passwords | password_hash and password_verify only |
| Error messages | display_errors off in production; log errors instead |
| Uploads | Check the type, rename the file, keep it out of the web folder |
| Old software | Keep PHP, WordPress and plugins updated |
CSRF tokens
PHP
<?php
session_start();
$_SESSION["csrf"] ??= bin2hex(random_bytes(32));
if ($_SERVER["REQUEST_METHOD"] === "POST") {
if (!hash_equals($_SESSION["csrf"], $_POST["csrf"] ?? "")) {
http_response_code(403);
exit("This form has expired. Please reload the page.");
}
// Safe to change data here.
}
?>
<form method="post">
<input type="hidden" name="csrf" value="<?= htmlspecialchars($_SESSION["csrf"]) ?>">
<button>Delete my account</button>
</form>Another site can make a signed-in visitor's browser submit a form to yours. That site cannot read the token, so the forged request fails the check. hash_equals compares in constant time, so the token cannot be guessed from timing.
Session cookie settings
PHP
<?php
session_set_cookie_params([
"secure" => true, // only over HTTPS
"httponly" => true, // JavaScript cannot read it
"samesite" => "Lax", // not sent with most cross-site requests
]);
session_start();