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_errors in 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 DROP in app accounts).
  • Run PHP-FPM workers as a non-root user.
  • Disable dangerous functions in php.ini: exec, shell_exec, passthru when not needed.

Security Incident Response

  1. Rotate all secrets (DB passwords, API keys, APP_KEY).
  2. Review access logs for suspicious patterns.
  3. Patch the vulnerability and deploy.
  4. Notify affected users if data was exposed.