Guide

PHP Security Basics: Validation, Prepared Statements and More

PHP security basics for beginners: input validation, prepared statements, password hashing, XSS and CSRF concepts, sessions and safe configuration.

Key takeaways

  • Treat everything from outside your code as untrusted: form fields, parameters, cookies, headers and uploaded files.
  • Use prepared statements for every query with outside values; the PHP manual calls this the recommended way to avoid SQL injection.
  • Store passwords with the built-in password_hash function, which generates a random salt by default, and never as plain text.
  • Escape output to stop XSS, and use a secret token in forms to stop CSRF.
  • Hide detailed errors on live sites, log them instead, keep PHP and extensions updated and grant minimum privileges.

Security is a habit, not a feature

A website that works is not necessarily a website that is safe. Attackers look for common mistakes, and most successful attacks on small sites use well-known weaknesses. The good news is that a handful of habits protect you from the majority of them. This guide explains the main ideas in prose so that you can recognise them and apply them in any PHP project.

Why should you never trust input?

Everything that comes from outside your code is untrusted. This includes form fields, address parameters, cookies, headers, uploaded files and even data from other services. Treat it as hostile until you have checked it. Decide what valid data looks like, and reject anything else. Prefer allow lists, which define what is accepted, over block lists, which try to guess what is dangerous.

Input validation

Validation checks that data has the right type, length, range and format. A quantity should be a whole number within a sensible range. A choice from a drop-down should match one of the values you offered. An email should have a valid shape. Perform validation on the server. Where the check fails, stop processing and return a clear message, but avoid telling an attacker more than necessary.

How do prepared statements stop SQL injection?

SQL injection happens when user input becomes part of a database command. If a program builds a query by joining text, a visitor can type characters that change the meaning of the command and may read, change or delete data. The reliable defence is the prepared statement. The query is written with placeholders, and values are supplied separately, so the database never mixes data with commands. Use prepared statements for every query that involves outside values, whether you work with PDO or MySQLi. Escaping functions are a weaker substitute and should not be your main defence.

How should passwords be stored?

Never store passwords as plain text, and do not invent your own scheme. PHP provides built-in password functions that create a salted hash using a strong, modern algorithm and that can verify a password later. A hash is a one-way transformation, so even the site owner cannot read the original password. When the recommended algorithm changes over time, the functions can tell you that a stored hash needs to be refreshed, and you can update it at the next successful login. Avoid fast general-purpose hashes such as the old MD5 or SHA-1 for passwords, because they are too quick to guess against.

Also consider the wider login flow: limit repeated failed attempts, give the same message whether the user name or the password was wrong, and offer a safe password reset by emailing a single-use, time-limited link.

What is cross-site scripting (XSS)?

XSS occurs when a site shows user-supplied text in a page without escaping it, which allows an attacker to run script in other visitors' browsers. The script could steal session information or change what the page shows. The defence is to escape output for the context where it appears. For HTML text, convert special characters into their harmless display forms before printing. Remember that escaping depends on the place: text inside an HTML attribute, inside a script block or inside a web address each needs the correct treatment. Template engines used by frameworks escape output automatically by default, which is one more reason to use them.

What is cross-site request forgery (CSRF)?

CSRF tricks a logged-in user's browser into sending an unwanted request to your site, for example to change an email address, because the browser attaches the user's cookies automatically. The common defence is a secret, unpredictable token placed in each form and checked when the form is submitted. A request that lacks the right token is refused. Set cookies with the SameSite attribute, use POST for actions that change data, and keep a rule that state-changing actions never happen on simple page visits.

How do you keep sessions safe?

Sessions identify logged-in users, so protect them. Regenerate the session identifier after login to prevent fixation. Mark session cookies as secure so they travel only over encrypted connections, and as HttpOnly so scripts cannot read them. End sessions properly on logout and after a period of inactivity. Do not put identifiers in web addresses.

Use HTTPS everywhere

Encrypted connections protect data in transit, including passwords and session cookies. Most hosting providers offer free certificates, so there is little reason to run a site without HTTPS. Redirect all plain requests to the encrypted address.

File uploads and file access

Treat uploaded files as dangerous. Check size and real type, rename the stored file, and keep it outside any folder where scripts are run. When your code opens files based on user input, guard against path tricks that climb to other folders. Where possible, avoid building file paths from visitor data at all.

How should a live site be configured?

  • Turn off detailed error display on live sites and write errors to logs.
  • Keep secrets such as database passwords and API keys out of public folders and out of version control.
  • Remove unused scripts, sample files and old backups from the server.
  • Set sensible permissions so the web server can write only where it must.

Why keep software updated?

Many compromises exploit known flaws in outdated PHP versions, frameworks, plugins and themes. Use a supported PHP version, apply updates promptly, remove plugins you do not use, and obtain extensions only from trusted sources. For sites built on a content management system, updates are one of the most effective protections you have.

Principle of least privilege

Give each part of your system only the access it needs. The database account used by the website should not have rights to manage the whole server. An editor account should not carry administrator powers. If something is breached, limited privileges limit the damage.

Threats and the matching defence

ThreatMain defence
SQL injectionPrepared statements with placeholders; check any dynamic query part against allowed values
Cross-site scripting (XSS)Escape output for its context, for example with htmlspecialchars for HTML text
Cross-site request forgery (CSRF)A secret token in each form, SameSite cookies and POST for state-changing actions
Stolen or guessed passwordsStore hashes made with password_hash and verify them with password_verify
Information leaksTurn off error display on live sites and write errors to a log

Summary checklist

  • Validate input on the server and use allow lists.
  • Use prepared statements for all database queries.
  • Hash passwords with the built-in password functions.
  • Escape output to stop XSS and use tokens to stop CSRF.
  • Secure sessions, use HTTPS and hide error details.
  • Keep everything updated and grant minimum privileges.

Security improves with practice. Review your own code against this list, and continue to read trusted resources such as the OWASP project and the official PHP manual.

Sources and further reading

  • PHP Manual: SQL Injection: recommends binding all data with prepared statements, notes that binding covers data only, and advises connecting with minimal-privilege database users.
  • PHP Manual: PDO prepared statements and stored procedures: explains named and question-mark placeholders, repeated execution and that exclusive use of prepared statements prevents SQL injection through the bound values.
  • PHP Manual: password_hash: documents the default bcrypt algorithm, automatic salt generation and the advice to use a column that can grow beyond 60 bytes.
  • PHP Manual: htmlspecialchars: lists the characters converted to HTML entities and the default flags since PHP 8.1.
  • PHP Manual: Error Reporting: advises hiding errors from display in production and logging them instead.
  • PHP: Supported Versions: explains that each branch gets two years of active support followed by two years of security-only fixes, and lists the current status of each branch.

Frequently asked questions

What is the best protection against SQL injection in PHP?

Use prepared statements with placeholders so that user values are always treated as data and never as part of the command.

How should passwords be stored in PHP?

Use the built-in password hashing functions, which create salted hashes and can verify passwords later. Never store plain text.

What is the difference between XSS and CSRF?

XSS runs attacker script in a visitor's browser through unescaped output, while CSRF tricks a logged-in browser into sending an unwanted request.

Why should error messages be hidden on live sites?

Detailed errors can reveal paths and internal details, so show a friendly message to visitors and write the details to a log.

All guides