Tutorials Logic, IN info@tutorialslogic.com

PHP Security Basics: Validation, Escaping, Passwords, CSRF, and Secrets

PHP Security Boundaries

PHP application security begins at trust and privilege boundaries: validate untrusted input, encode for the exact output context, parameterize SQL values, protect identity and sessions, constrain files and networks, and keep secrets out of code and logs.

No single sanitizer secures an application. Defense comes from least privilege, current runtimes, explicit authorization, bounded resources, secure deployment headers, redacted observability, and adversarial tests for each feature.

Validate and Escape

Operation Question
Validation Is this value acceptable for the domain?
Normalization What canonical form should valid data use?
SQL parameter binding How is data kept separate from SQL syntax?
HTML escaping How is text kept separate from HTML markup?

Validate Email and Escape Display

Validate Email and Escape Display
<?php
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);

if ($email === false || $email === null) {
    exit('Invalid email address.');
}

echo htmlspecialchars($email, ENT_QUOTES, 'UTF-8');

Password Storage

Store password hashes, never plaintext passwords. Let PASSWORD_DEFAULT choose the current recommended algorithm and allow the database column enough space for hashes to grow.

Hash and Verify

Hash and Verify
<?php
$hash = password_hash('correct horse battery staple', PASSWORD_DEFAULT);
$isValid = password_verify('correct horse battery staple', $hash);

echo $isValid ? 'Authenticated' : 'Denied';
Output
Authenticated
  • The generated hash is intentionally different across calls because it contains a random salt.

CSRF Tokens

A state-changing browser form should include an unpredictable token stored in the user session. Verify the submitted token with hash_equals() before applying the change. SameSite cookies help but do not replace an application CSRF design.

Create and Verify a Token

Create and Verify a Token
<?php
session_start();

$_SESSION['csrf'] ??= bin2hex(random_bytes(32));
$submitted = (string) ($_POST['csrf'] ?? '');

if (!hash_equals($_SESSION['csrf'], $submitted)) {
    http_response_code(403);
    exit('Invalid request token.');
}

Secrets and Database Input

  • Load database passwords and API keys from protected environment configuration.
  • Never commit real secrets or print them in diagnostics.
  • Use PDO prepared statements for every variable SQL value.
  • Restrict upload type, size, path, and execution permissions.
  • Keep dependencies and the PHP runtime supported and patched.

Trust Boundaries and Threats

Treat request fields, headers, cookies, uploaded files, database text, cached values, queue messages, environment configuration, and third-party responses as untrusted until validated for their use. Trust depends on provenance and current authorization, not on whether PHP decoded the value successfully.

Begin each feature by naming assets, actors, entry points, privileges, and failure impact. A public search endpoint, account update, administrator export, and background webhook have different threats. Security controls should follow the data flow and ownership boundaries rather than one generic sanitization function.

Use least privilege for database accounts, filesystem permissions, service credentials, and process users. Separate read-only from mutation capabilities where practical. A compromised endpoint should not automatically gain migration, shell, or unrestricted storage access.

Keep PHP, the web server, operating system, and enabled extensions on supported security releases. Disable unused extensions and services, inventory exposed entry points, and rehearse key rotation and emergency rollback before an incident.

  • Classify every external and persisted data source.
  • Model threats per feature and operation.
  • Grant processes and credentials least privilege.
  • Maintain supported runtimes and an incident procedure.

Validation and Output Encoding

Validation decides whether input belongs to the expected domain: type, length, range, format, allowed values, relationships, and authorization context. Normalize only after deciding what variants are accepted. Reject unexpected arrays when a scalar is required and cap collection sizes before expensive work.

Output encoding is context-specific. For HTML text and attribute values, use `htmlspecialchars` with an explicit character set and appropriate flags. JavaScript, CSS, URLs, HTTP headers, CSV, and shell arguments have different rules. Avoid inserting data into executable contexts when a structured API is available.

Escaping does not make a value safe for every destination, and validation does not replace encoding. An email address can be valid input and still need HTML encoding when displayed. Keep the raw validated domain value and encode at the final output boundary.

Rich HTML requires an allowlist sanitizer designed for HTML parsing; stripping tags is not a complete policy. Prefer plain text for user content unless rich formatting is an explicit maintained feature.

  • Validate type, shape, length, range, and relationships.
  • Encode for the exact output context.
  • Keep validation and output encoding as separate controls.
  • Sanitize rich HTML with an explicit allowlist policy.

Database Query Safety

Use PDO prepared statements and bind untrusted values as parameters. Parameters represent data, not SQL syntax, so table names, column names, directions, and operators need an allowlisted mapping chosen by application code. Never concatenate request text into a query structure.

Configure error handling so database failures become controlled exceptions at the repository boundary. Do not reveal SQL, credentials, schema paths, or driver messages to clients. Log a redacted operation and correlation ID while preserving the original Throwable internally.

A prepared statement does not authorize a row. Every query must scope records to the current user, tenant, or permission as required. Recheck authorization inside the transaction when concurrent changes can invalidate an earlier decision.

Limit result size, pagination bounds, statement time, and transaction duration. Injection prevention is only one database security property; excessive queries, unbounded exports, and long locks can also become denial-of-service paths.

  • Bind values and allowlist every dynamic SQL identifier.
  • Keep database diagnostics out of client responses.
  • Scope queries by current authorization context.
  • Bound result size, execution time, and transactions.

Identity, Sessions, and CSRF

Store passwords with `password_hash` using an appropriate supported algorithm and verify them with `password_verify`. Let PHP manage salts, and use `password_needs_rehash` after successful login when policy changes. Rate-limit authentication and avoid revealing whether an account exists.

Regenerate the session identifier after authentication and privilege changes, invalidate sessions at logout or security events, and use secure cookie attributes. Session data on the server still needs authorization checks; possession of a session does not grant access to every record.

Protect state-changing cookie-authenticated requests with a CSRF token bound to the user session and verified with a timing-safe comparison where appropriate. SameSite cookies add defense but do not replace a token for every application and integration model.

Authorization belongs on the server for every action and object. Hide or disable UI controls for usability, then repeat the permission decision using trusted identity and current resource state. Test horizontal and vertical privilege changes by modifying identifiers and roles.

  • Hash and verify passwords through PHP password APIs.
  • Rotate session IDs at identity transitions.
  • Verify CSRF protection on state-changing requests.
  • Authorize every action and resource server-side.

Files, Commands, and Remote Access

For uploads, enforce server-side size limits, upload status, expected media type from file content, generated storage names, and an allowlisted extension when it affects delivery. Store uploads outside executable public paths and serve them through an authorization-aware handler.

Do not build shell commands from request text. Prefer native PHP or library APIs. When process execution is unavoidable, use an API that separates executable and arguments where available, pass only allowlisted operations, use a restricted account, set time and output limits, and never rely on one escaping function as authorization.

Server-side requests can enable SSRF. Allowlist destinations or service identifiers, resolve and validate addresses under the network policy, restrict redirects and protocols, protect cloud metadata and internal networks, and bound response size and time. URL syntax validation alone is insufficient.

Canonicalize filesystem operations under an owned root and verify the resolved target stays there. Do not concatenate user paths into include, require, archive extraction, or deletion operations. Symlinks and race conditions need an ownership-aware design.

  • Store verified uploads outside executable public roots.
  • Avoid shell construction and restrict unavoidable processes.
  • Apply network policy to every server-side destination.
  • Keep resolved filesystem targets under owned roots.

Secrets, Headers, and Verification

Load secrets from deployment-managed configuration with narrow access, not source files, logs, error pages, or browser-delivered code. Rotate keys, support overlap where protocols require it, and know which data must be re-encrypted or sessions revoked after compromise.

Use HTTPS and configure secure transport at the server or proxy. Set response headers from the application architecture: a restrictive Content Security Policy, frame protection, content-type sniffing protection, referrer policy, and caching rules for sensitive pages. Test the final headers after every proxy layer.

Security logs should record identity, operation, resource, decision, correlation ID, release, and safe failure category without passwords, tokens, session IDs, or sensitive payloads. Protect log access and retention because logs become another data store.

Test injection, output contexts, CSRF, identifier tampering, upload disguise, path traversal, SSRF, rate limits, session fixation, logout invalidation, and error redaction. Combine automated tests with dependency, configuration, and threat-model review before high-risk releases.

  • Keep secrets in deployment-managed protected configuration.
  • Verify security headers at the final public response.
  • Record audit decisions without secret material.
  • Test abuse cases across every trust boundary.
Before you move on

Mastery Check

5 checks
  • Validate untrusted values for their domain use.
  • Encode only at the final output context.
  • Bind SQL values and allowlist query structure.
  • Protect sessions, CSRF tokens, and every authorization decision.
  • Test uploads, paths, remote requests, secrets, and error redaction.

Security Boundary Check

0 of 2 checked

Q1. Does HTML escaping prevent SQL injection?

Q2. What should be stored for a password?

Security Control Boundary

  • One defense used everywhere

    Escaping, validation, parameterization, authentication, and authorization solve different problems. Apply each control at its own input, query, and output boundary.

Try this next

Threat-Model One Form

0 of 2 completed

  1. For a profile form, identify validation, SQL binding, HTML escaping, session, and CSRF responsibilities.
  2. Hash a password, verify the correct candidate, and prove that an incorrect candidate fails.
Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.