PHP Security
Security Mindset
PHP powers a large share of the web, making it a frequent target. Security must be built in from the start, not added later.
SQL Injection Prevention
Never concatenate user input into SQL:
// DANGEROUS
$query = "SELECT * FROM users WHERE id = " . $_GET['id'];
Use prepared statements with PDO or mysqli:
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => $_GET['id']]);
$user = $stmt->fetch();
Cross-Site Scripting (XSS)
Escape all output in HTML context:
echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
In templates (Blade/Twig), use built-in escaping: {{ $variable }} auto-escapes in Blade.
Cross-Site Request Forgery (CSRF)
Use CSRF tokens for state-changing requests:
// Generate token (store in session)
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
// In form
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
// Validate on submit
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
die('Invalid CSRF token');
}
Frameworks like Laravel and Symfony handle CSRF automatically.
Password Hashing
// Hash on registration
$hash = password_hash($password, PASSWORD_ARGON2ID);
// Verify on login
if (password_verify($password, $hash)) {
// authenticated
}
Never use md5() or sha1() for passwords.
Session Security
session_start([
'cookie_httponly' => true,
'cookie_secure' => true, // HTTPS only
'cookie_samesite' => 'Strict',
]);
session_regenerate_id(true); // After login
File Upload Safety
- Validate MIME type and extension against a whitelist
- Store uploads outside the web root
- Rename files — never use the original filename
- Limit file size
Environment Secrets
Never commit credentials. Use .env files (loaded by frameworks) and keep them out of version control:
DB_PASSWORD=secret_value
APP_KEY=base64:...
Security Checklist
- Prepared statements for all database queries
- Output escaping for all user-generated content
- CSRF protection on forms
-
password_hash()/password_verify() - HTTPS everywhere in production
- Keep PHP and dependencies updated
- Disable
display_errorsin production - Validate and sanitize all input
Content Security Policy (CSP)
Add headers to restrict script and style sources:
header("Content-Security-Policy: default-src 'self'; script-src 'self'");
header("X-Content-Type-Options: nosniff");
header("X-Frame-Options: DENY");
Frameworks like Laravel apply these via middleware packages.
Rate Limiting Login Attempts
$attempts = $_SESSION['login_attempts'] ?? 0;
if ($attempts >= 5) {
http_response_code(429);
exit('Too many attempts. Try again later.');
}
if (!password_verify($password, $hash)) {
$_SESSION['login_attempts'] = $attempts + 1;
exit('Invalid credentials');
}
unset($_SESSION['login_attempts']);
Secure Deserialization
Never unserialize() untrusted input — it can trigger object injection attacks. Use JSON instead:
$data = json_decode($input, true, 512, JSON_THROW_ON_ERROR);
Dependency Auditing
composer audit
Integrate composer audit or tools like Roave Security Advisories into CI pipelines.
Principle of Least Privilege
- Database users should have only required permissions (no
DROPin app accounts). - Run PHP-FPM workers as a non-root user.
- Disable dangerous functions in
php.ini:exec,shell_exec,passthruwhen not needed.
Security Incident Response
- Rotate all secrets (DB passwords, API keys,
APP_KEY). - Review access logs for suspicious patterns.
- Patch the vulnerability and deploy.
- Notify affected users if data was exposed.