| title | PHP Security Guidelines |
|---|---|
| scope | php |
- Always use PDO prepared statements or the query builder's parameter binding
- Never concatenate user input into SQL queries
- Use named parameters for clarity:
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute(['email' => $email]);- Escape all output with
htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') - Use template engine auto-escaping (Twig, Blade)
- Set
Content-Typeheaders correctly - Use Content Security Policy headers
- Include CSRF tokens in all state-changing forms
- Validate tokens on the server side for every POST/PUT/DELETE request
- Use
SameSite=LaxorSameSite=Strictcookie attribute
- Use
password_hash()withPASSWORD_DEFAULT(currently bcrypt, auto-upgrades) - Use
password_verify()for checking — never compare hashes directly - Use
password_needs_rehash()to upgrade hashes on login - Implement rate limiting on login endpoints
- Use constant-time comparison (
hash_equals()) for tokens
- Regenerate session ID after login:
session_regenerate_id(true) - Set secure cookie flags:
Secure,HttpOnly,SameSite - Set appropriate session lifetime
- Store sessions server-side (files, Redis, database)
- Validate MIME type server-side (do not trust client headers)
- Restrict allowed extensions with an allowlist
- Store uploads outside the web root
- Generate random filenames — never use the original filename
- Set file size limits
- Scan for malware if possible
- Never use
unserialize()on untrusted data - Use
json_decode()for data interchange - If
unserialize()is required, use theallowed_classesoption:
unserialize($data, ['allowed_classes' => [AllowedClass::class]]);Avoid or strictly control:
eval(),assert()with string argumentsexec(),shell_exec(),system(),passthru(),proc_open()preg_replace()with theemodifier (removed in PHP 8.0)extract()on user input$$variable(variable variables) with user input
If shell commands are necessary, use escapeshellarg() and escapeshellcmd().
Set these headers in your application or web server:
Strict-Transport-Security: max-age=31536000; includeSubDomainsContent-Security-Policy: default-src 'self'X-Content-Type-Options: nosniffX-Frame-Options: DENYReferrer-Policy: strict-origin-when-cross-origin
- Run
composer auditregularly to check for known vulnerabilities - Keep dependencies updated
- Pin exact versions in
composer.lock - Review new dependencies before adding them