PHP Application Security

Security is a set of controls across input, output, identity, authorization, configuration, dependencies, and operations. No single function or firewall replaces secure application design.

Prevent SQL and Command Injection

Use prepared statements for values and strict whitelists for identifiers. Avoid shell execution when a native PHP function or library exists.

<?php
declare(strict_types=1);

$stmt = $pdo->prepare('SELECT id FROM users WHERE email = :email');
$stmt->execute(['email' => $email]);

// When shell execution is unavoidable, pass each argument safely.
$command = sprintf(
    'ping -c 1 -- %s',
    escapeshellarg($validatedIpAddress)
);

Escape for the Output Context

<p><?= htmlspecialchars($customerName, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?></p>

Do not place untrusted values directly into inline JavaScript, CSS, or raw HTML. Prefer data attributes encoded for HTML and parse them in external JavaScript.

Protect State-Changing Browser Requests

<?php
declare(strict_types=1);

$_SESSION['csrf_token'] ??= bin2hex(random_bytes(32));

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $submitted = $_POST['csrf_token'] ?? '';

    if (!hash_equals($_SESSION['csrf_token'], $submitted)) {
        http_response_code(419);
        exit('The form expired. Reload the page and try again.');
    }
}

Protect Authentication and Authorization

  • Use password_hash() and password_verify().
  • Regenerate the session ID after login.
  • Rate-limit authentication attempts.
  • Require re-authentication for high-risk changes.
  • Use role or permission checks at the server-side action boundary.
  • Record privileged actions in an audit log.

Treat File Uploads as Hostile

<?php
declare(strict_types=1);

$file = $_FILES['document'] ?? null;

if (!is_array($file) || $file['error'] !== UPLOAD_ERR_OK) {
    throw new RuntimeException('Upload failed.');
}

if ($file['size'] > 5 * 1024 * 1024) {
    throw new RuntimeException('File exceeds the 5 MB limit.');
}

$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($file['tmp_name']);
$allowed = ['application/pdf' => 'pdf', 'image/jpeg' => 'jpg'];

if (!isset($allowed[$mime])) {
    throw new RuntimeException('Unsupported file type.');
}

$filename = bin2hex(random_bytes(16)) . '.' . $allowed[$mime];
$destination = dirname(__DIR__, 2) . '/storage/uploads/' . $filename;

if (!move_uploaded_file($file['tmp_name'], $destination)) {
    throw new RuntimeException('Unable to store uploaded file.');
}

Configuration and Secrets

  • Keep secrets out of source control and the public document root.
  • Use separate credentials for development, staging, and production.
  • Give the database account only the privileges the application needs.
  • Rotate credentials after exposure and remove them from history.
  • Never put secrets in URLs, client-side JavaScript, or logs.

Useful Response Headers

<?php
header('X-Content-Type-Options: nosniff');
header('Referrer-Policy: strict-origin-when-cross-origin');
header('Permissions-Policy: camera=(), microphone=(), geolocation=()');
header("Content-Security-Policy: default-src 'self'; frame-ancestors 'none'; base-uri 'self'");

Build a Content Security Policy around the assets the application actually needs. Test it in report-only mode before enforcement on an existing site.

Log Security-Relevant Events

Record login success and failure, permission denials, password changes, user and role changes, token creation, data exports, and administrative actions. Include actor, target, time, request correlation ID, and outcome. Avoid passwords, session IDs, API keys, full payment data, and unnecessary personal information.