PHP Security Checklist

Review PHP input, SQL, output, sessions, uploads, secrets, dependencies, and authorization with practical security checks.

Lesson 85 of 86Advanced10 min read

Security review works best when it follows the path of data through an application. A request crosses from an untrusted visitor into PHP, then may reach SQL, HTML, a file, or an external service. Each boundary needs its own rule. This reference turns those rules into a practical checklist for a small authenticated PHP application. It supplements the main security lesson with review questions and concrete tests. You will check authorization as carefully as validation, keep uploaded bytes away from execution, and verify production configuration. A checklist is a way to find gaps; it is not a certificate that every possible attack has been prevented.

Review input shape and business rules

Treat query strings, form fields, JSON, cookies, headers, and uploaded metadata as untrusted. Require the shapes your handler expects before calling string functions or converting values. A field can arrive as an array, a missing value, or an oversized string. Validate ranges, allowed statuses, maximum lengths, and structured formats according to the business rule. Keep normalization deliberate; deleting characters can silently change an identifier or password. A valid URL is not necessarily a permitted server destination. If PHP fetches external resources, define allowed hosts and prevent access to internal services under the deployment's network policy. Test boundary values and malformed shapes rather than only ordinary form submissions.

Verify authorization on every protected operation

Authentication identifies the account; authorization decides whether that account can read or change the particular resource. A valid numeric order ID is not proof of ownership. Use trusted session identity and tenant or ownership predicates in database operations where appropriate. Never accept a hidden user_id field as the authority for whose record to edit. Test account A requesting account B's data through direct IDs, search results, downloads, and update endpoints. Apply the same access rules to background jobs and APIs. Avoid filtering private data only in JavaScript because the response may already contain it. Denial messages should preserve the feature's privacy policy without hiding useful private audit evidence.

Keep SQL values and output data separate from syntax

Prepared statements prevent bound values from becoming SQL structure. Dynamic table, column, and sort choices require reviewed allowlists because placeholders represent values. Database constraints protect invariants under concurrent writers. For HTML output, escape untrusted text with htmlspecialchars() under the correct encoding and context. JavaScript, URL, CSS, and HTML-attribute contexts have different requirements, so avoid moving data through arbitrary inline code. In the browser, textContent is safer than innerHTML for plain text. A name read from your own database can still be hostile because it may have originated with a visitor. SQL preparation and output escaping solve different boundaries and neither replaces the other.

Protect authentication, sessions and state changes

Hash passwords with password_hash() and verify them with password_verify(); do not invent a reversible password scheme. Review rehash policy and supported algorithms under the actual PHP environment. Regenerate session identifiers at appropriate authentication transitions and set secure cookie attributes according to HTTPS and application behavior. Session-authenticated state changes need CSRF protection, with methods and tokens checked server-side. SameSite cookies can add protection but are not a universal substitute for a reviewed CSRF design. Rate-limit login, reset, and verification workflows under a privacy-aware policy. Reset tokens should be short-lived, single-purpose, and stored safely. Avoid revealing account existence inconsistently across normal and failure paths.

Constrain files, uploads and XML

Build filesystem paths from trusted roots and application-owned identifiers. Do not append arbitrary request names. Upload checks include error codes, size, server-detected type, generated names, private non-executable storage, and authorized downloads. Fileinfo is a classification layer rather than a complete content-safety guarantee. Image processing needs resource and dimension limits. XML input should not enable unnecessary entity expansion or external-resource loading; reject unsupported DTDs and bound size and depth. CSV imports need shape and value checks, while spreadsheet exports need a formula policy. These features can fail through resource exhaustion as well as direct injection, so enforce counts, sizes, and time budgets at server and infrastructure layers.

Check secrets, dependencies and deployment

Keep database, SMTP, API, and signing secrets in protected runtime configuration. A .env file must not be served publicly or committed with real values. Rotate exposed credentials rather than only deleting the current file. Install reviewed locked dependencies, monitor advisories, and verify the target PHP and extension requirements. Production should log diagnostics privately and avoid public stack traces, SQL errors, phpinfo, or OPcache control endpoints. Use least-privilege database and filesystem accounts. Confirm HTTPS, trusted proxy configuration, and response caching appropriate to private data. Test backup restoration and release rollback for availability. Record review findings with a concrete affected operation and verification result, not merely a checked box.

Practice task and worked solution

Review an authenticated receipt-upload feature from request to download. The solution verifies authentication and CSRF, checks scalar metadata and upload status, enforces a narrow size/type policy, generates a storage name, and saves private metadata through prepared SQL. The download path accepts an internal file ID and checks ownership before streaming. Test another account's ID, a filename with path traversal, an unsupported type, a huge image dimension, and a note containing HTML markup. Review logs for tokens and credentials. Record each test's expected denial or safe rendering outcome. Finish by checking the real web root cannot expose .env, backups, or upload directories.

Continue learning

Use the main security lesson, authentication, and REST APIs for full implementation context.

The PHP security manual describes runtime and application security concerns.

PHP Security Checklist Examples

Each example shows the code, the output it produces and what each part does.

Example 1

Authorize a row and escape its HTML text

Assume an authenticated user ID from trusted session state and a configured PDO connection. This read uses both order ID and owner ID, then escapes a returned text field.

An inaccessible or missing order produces a 404-style unavailable response. An accessible note containing <script> is represented as escaped text rather than executable HTML. Actual row content depends on the database and authenticated account.

PHP
<?php
$orderId = 12; // Already validated request identifier.
$userId = 7;   // Trusted authenticated identity.
$stmt = $pdo->prepare('SELECT note FROM orders WHERE id = ? AND user_id = ?');
$stmt->execute([$orderId, $userId]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($row === false) {
    http_response_code(404);
    echo 'Order unavailable.';
    exit;
}
echo htmlspecialchars($row['note'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
Output
Unavailable row: Order unavailable.
Accessible hostile markup: displayed as escaped text, not executed.

PHP Security Checklist FAQ

Does validation prove permission?

No. Valid input can target another user's record: an order ID can be a perfectly valid integer that belongs to someone else. Check authorization on every request, for example by including the current user's ID in the query's WHERE clause.

Does a prepared query prevent XSS?

No. Prepared statements protect the SQL query. A comment containing <script> is stored and returned exactly as typed, so escape at the output boundary with htmlspecialchars() or your template engine's automatic escaping.

Does a checklist guarantee security?

No. It provides review coverage, making sure common areas such as input, output, sessions and secrets aren't forgotten, with limits that need continuing assessment. Add dependency updates, code review, security testing and monitoring as the application changes.

Is deleting an exposed secret enough?

No. Rotate it and review where it was disclosed. A key that was committed to Git, logged or shown in an error page may already have been copied, so generate a new one, revoke the old one and check logs for misuse.

PHP Security Checklist Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Multiple choicemedium

A valid order ID belongs to another user. What should the endpoint do?

PHP Security Checklist Quiz

2 questions to check what you learned.

  1. What distinguishes authorization from authentication?

    • They always mean the same thing
    • Authorization only validates email syntax
    • Authentication escapes HTML
    • Authorization checks permission for the particular operation or resource
  2. Why escape data read from your database?

    • Stored values may originate from untrusted input
    • Every database value is automatically executable
    • Prepared SQL disables all escaping
    • Escaping grants permissions