
PHP Security: SQL Injection, XSS, CSRF, Password Hashing and Safe Code
Security is one of the most important parts of PHP development. A PHP application may work correctly from a functional point of view, but if it does not handle user input, database queries, sessions, files, and passwords safely, it can become vulnerable to attacks.
PHP is commonly used to build login systems, dashboards, admin panels, APIs, blogs, e-commerce systems, and content management systems. These applications often deal with users, forms, databases, sessions, uploaded files, and sensitive information.
This article explains important PHP security topics including security basics, input validation, output escaping, SQL injection, XSS protection, CSRF protection, password hashing, session security, file upload security, error handling, and secure coding practices.
PHP Security Basics
PHP security starts with one simple rule: never trust user input. Any data that comes from the user, browser, URL, form, cookie, header, API request, uploaded file, or external service should be treated as untrusted until it is validated and handled safely.
User input can come from many places, including:
$_GET: data from the URL query string.
$_POST: data from submitted forms or AJAX requests.
$_COOKIE: data stored in the user's browser.
$_FILES: uploaded file information.
$_SERVER: request and server information.
JSON request body: data sent by APIs or JavaScript applications.
Secure PHP development usually depends on several practices working together. You validate input, escape output, use prepared statements for database queries, hash passwords, protect sessions, check permissions, handle errors carefully, and avoid exposing sensitive information.
Security should not be added only at the end of a project. It should be part of the development process from the beginning.
Input Validation
Input validation means checking whether user input is acceptable before using it. Validation helps protect the application from incorrect, incomplete, unexpected, or dangerous data.
For example, if a form asks for an email address, the application should check that the value is actually a valid email. If a form asks for an age, the application should check that the value is a number within an expected range.
<?php
$email = trim($_POST["email"] ?? "");
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
echo "Invalid email address.";
}
?>Validation can check many things:
Required fields.
Email format.
URL format.
Minimum and maximum length.
Numeric values.
Allowed values.
Date format.
File type and file size.
For example, a role field should not accept any random value. It should accept only specific allowed values.
<?php
$allowedRoles = ["user", "editor", "admin"];
$role = $_POST["role"] ?? "user";
if (!in_array($role, $allowedRoles, true)) {
echo "Invalid role.";
exit;
}
?>Validation improves both security and data quality. It prevents invalid data from entering the application and helps the system behave predictably.
Output Escaping
Output escaping means converting special characters before displaying user-generated content in HTML. This is important because user input may contain HTML or JavaScript code.
In PHP, htmlspecialchars() is commonly used to safely display text inside HTML.
<?php
$name = $_POST["name"] ?? "";
echo htmlspecialchars($name, ENT_QUOTES, "UTF-8");
?>Without escaping, a user could submit something like a script tag, and the browser might execute it as JavaScript. With escaping, the browser displays it as normal text instead of running it.
A small helper function can make safe output easier:
<?php
function e($value) {
return htmlspecialchars($value ?? "", ENT_QUOTES, "UTF-8");
}
echo e($_POST["name"] ?? "");
?>Output escaping should be applied when displaying user-generated data such as names, comments, article content, profile information, search keywords, and database values.
Validation and escaping are different. Validation checks if the input is acceptable. Escaping protects the output context where the data is displayed.
SQL Injection
SQL injection is one of the most dangerous vulnerabilities in database-driven applications. It happens when user input is inserted directly into an SQL query without proper protection.
For example, this code is unsafe:
<?php
$email = $_POST["email"];
$sql = "SELECT * FROM users WHERE email = '$email'";
?>If a malicious user sends special SQL code inside the email field, they may change the meaning of the query. This can lead to data leaks, unauthorized login, deleted records, or damaged databases.
The correct approach is to use prepared statements. Prepared statements separate SQL structure from user input.
<?php
$pdo = new PDO(
"mysql:host=localhost;dbname=app;charset=utf8mb4",
"root",
""
);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$email = $_POST["email"] ?? "";
$statement = $pdo->prepare("SELECT * FROM users WHERE email = :email");
$statement->execute([
"email" => $email
]);
$user = $statement->fetch(PDO::FETCH_ASSOC);
?>Prepared statements should be used for all queries that include user input, including SELECT, INSERT, UPDATE, and DELETE queries.
For example, an update query should also use prepared statements:
<?php
$id = $_POST["id"] ?? "";
$name = $_POST["name"] ?? "";
$statement = $pdo->prepare("
UPDATE users
SET name = :name
WHERE id = :id
");
$statement->execute([
"name" => $name,
"id" => $id
]);
?>SQL injection prevention is one of the most important security habits for every PHP developer.
XSS Protection
XSS stands for Cross-Site Scripting. It happens when an attacker manages to inject JavaScript into a page viewed by other users.
For example, a comment form may allow users to write comments. If the application displays comments without escaping, a malicious user could submit JavaScript code instead of normal text.
Unsafe output:
<?php
echo $_POST["comment"];
?>Safe output:
<?php
echo htmlspecialchars($_POST["comment"] ?? "", ENT_QUOTES, "UTF-8");
?>XSS can be used to steal session data, change page content, redirect users, or perform actions on behalf of another user. That is why output escaping is essential.
XSS protection usually includes:
Escaping user-generated output.
Validating input carefully.
Avoiding raw HTML from users unless it is strictly sanitized.
Using secure cookies for session protection.
Using Content Security Policy when possible.
If your application allows rich text, such as blog content or formatted comments, do not simply trust the HTML. Use a trusted HTML sanitizer or a framework feature designed for safe rich-text handling.
CSRF Protection
CSRF stands for Cross-Site Request Forgery. It happens when an attacker tricks a logged-in user into submitting an unwanted request to your application.
For example, if a user is logged into an admin panel, an attacker could try to make the user's browser submit a hidden form that changes account data or deletes content.
CSRF protection usually uses a token. The server generates a random token, stores it in the session, and includes it inside the form. When the form is submitted, the server checks whether the submitted token matches the session token.
<?php
session_start();
if (empty($_SESSION["csrf_token"])) {
$_SESSION["csrf_token"] = bin2hex(random_bytes(32));
}
?>
<form method="post" action="update-profile.php">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION["csrf_token"], ENT_QUOTES, "UTF-8"); ?>">
<input type="text" name="name">
<button type="submit">Update</button>
</form>When processing the request, check the token:
<?php
session_start();
$token = $_POST["csrf_token"] ?? "";
if (!hash_equals($_SESSION["csrf_token"] ?? "", $token)) {
http_response_code(403);
echo "Invalid CSRF token.";
exit;
}
echo "Request accepted.";
?>CSRF protection is especially important for forms that create, update, or delete data. Login, logout, profile update, password change, delete buttons, and admin actions should be protected.
Password Hashing
Passwords should never be stored as plain text. If a database is leaked and passwords are stored directly, all user accounts become exposed.
PHP provides password_hash() to securely hash passwords.
<?php
$password = $_POST["password"] ?? "";
$hash = password_hash($password, PASSWORD_DEFAULT);
echo $hash;
?>The generated hash should be stored in the database, not the original password.
When the user tries to log in, use password_verify() to compare the submitted password with the stored hash.
<?php
$submittedPassword = $_POST["password"] ?? "";
$storedHash = $user["password"];
if (password_verify($submittedPassword, $storedHash)) {
echo "Login successful.";
} else {
echo "Invalid credentials.";
}
?>Do not use weak hashing methods such as MD5 or SHA1 for passwords. Password hashing needs algorithms designed for password storage.
It is also a good practice to enforce password rules such as minimum length and to protect login forms from brute-force attacks using rate limiting or temporary lockouts.
Session Security
Sessions are commonly used for login systems and user dashboards. Because sessions can represent authenticated users, they must be handled carefully.
A basic login system may store the user ID in the session after successful login.
<?php
session_start();
session_regenerate_id(true);
$_SESSION["user_id"] = $user["id"];
$_SESSION["user_role"] = $user["role"];
?>The function session_regenerate_id(true) helps protect against session fixation by creating a new session ID after login.
To check whether a user is logged in:
<?php
session_start();
if (empty($_SESSION["user_id"])) {
header("Location: login.php");
exit;
}
?>For logout, clear the session data and destroy the session.
<?php
session_start();
$_SESSION = [];
session_destroy();
header("Location: login.php");
exit;
?>Important session security practices include:
Regenerate the session ID after login.
Do not store passwords in the session.
Use HTTPS in production.
Set secure cookie options when possible.
Destroy sessions during logout.
Check permissions on protected pages.
Cookie Security
Cookies are stored in the user's browser, so they should not contain sensitive information such as plain passwords, private tokens, or secret user data.
When setting cookies, PHP allows security-related options.
<?php
setcookie("theme", "dark", [
"expires" => time() + 3600,
"path" => "/",
"secure" => true,
"httponly" => true,
"samesite" => "Lax"
]);
?>The secure option means the cookie should only be sent over HTTPS. The httponly option prevents JavaScript from reading the cookie. The samesite option helps reduce certain cross-site request risks.
Cookies are useful for preferences and non-sensitive data, but authentication should be designed carefully using secure session and token practices.
File Upload Security
File uploads can be risky because users are sending files to your server. If uploads are not validated correctly, attackers may upload dangerous files or overwrite existing files.
A secure upload system should validate file size, extension, MIME type, upload errors, and file name. It should also store uploaded files in a controlled directory.
<?php
$allowedExtensions = ["jpg", "jpeg", "png", "pdf"];
$maxSize = 2 * 1024 * 1024;
if ($_FILES["file"]["error"] !== UPLOAD_ERR_OK) {
echo "Upload error.";
exit;
}
$fileName = $_FILES["file"]["name"];
$tmpName = $_FILES["file"]["tmp_name"];
$fileSize = $_FILES["file"]["size"];
$extension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
if (!in_array($extension, $allowedExtensions, true)) {
echo "File type is not allowed.";
exit;
}
if ($fileSize > $maxSize) {
echo "File is too large.";
exit;
}
$newName = bin2hex(random_bytes(16)) . "." . $extension;
move_uploaded_file($tmpName, __DIR__ . "/uploads/" . $newName);
echo "File uploaded safely.";
?>Do not trust the original file name. Generate a new safe name instead. This helps prevent overwriting files and reduces risks caused by special characters.
For better protection, uploaded files should not be placed in a directory where PHP scripts can be executed. If possible, store uploads outside the public web root and serve them through controlled download logic.
Authorization and User Roles
Authentication answers the question: who is the user? Authorization answers the question: what is the user allowed to do?
A user may be logged in, but that does not mean they should access every page or perform every action. For example, only admins should delete users, and only the owner of a post should edit that post unless the user has higher permissions.
<?php
session_start();
if (($_SESSION["user_role"] ?? "") !== "admin") {
http_response_code(403);
echo "Access denied.";
exit;
}
echo "Welcome to admin area.";
?>For ownership checks, compare the logged-in user ID with the owner ID of the resource.
<?php
if ($post["user_id"] !== $_SESSION["user_id"]) {
http_response_code(403);
echo "You cannot edit this post.";
exit;
}
?>Authorization should always be checked on the server side. Hiding a button in HTML or JavaScript is not enough because users can still send requests manually.
Error Handling and Debug Mode
Error messages are useful during development, but they can expose sensitive information in production. Database credentials, file paths, SQL queries, and stack traces should not be shown to public users.
During development, displaying errors can help debugging:
<?php
ini_set("display_errors", "1");
error_reporting(E_ALL);
?>In production, errors should usually be logged instead of displayed:
<?php
ini_set("display_errors", "0");
ini_set("log_errors", "1");
error_reporting(E_ALL);
?>When something fails, show a safe message to the user and log the technical details privately.
<?php
try {
// Database or application logic
} catch (Exception $exception) {
error_log($exception->getMessage());
echo "Something went wrong. Please try again later.";
}
?>This approach protects sensitive system details while still allowing developers to investigate errors through logs.
Secure Database Practices
Database security is not only about prepared statements. It also includes permissions, backups, error handling, and avoiding unnecessary access.
Important database security practices include:
Use prepared statements for all user input.
Use a database user with limited permissions.
Do not expose database credentials publicly.
Store credentials in configuration or environment files.
Do not show raw database errors to users.
Backup important databases regularly.
Validate data before saving it.
For example, the database user used by the website should not always need full administrative permissions. In production, it is safer to give only the permissions the application actually needs.
Secure PHP Configuration
Some security practices are related to server and PHP configuration. A secure application can still be weakened by unsafe configuration.
Important configuration practices include:
Keep PHP updated.
Disable displaying errors in production.
Use HTTPS for production websites.
Set correct file and directory permissions.
Do not commit secrets to Git repositories.
Protect environment files from public access.
Limit upload size according to application needs.
Remove unused files, test scripts, and old backups from public directories.
Security is not only code. It is also deployment, configuration, updates, permissions, and monitoring.
Security Checklist for PHP Projects
A practical PHP security checklist can help developers review their applications before deployment.
Validate all input from forms, URLs, cookies, APIs, and files.
Escape all user-generated output using the correct method for the output context.
Use prepared statements for database queries.
Hash passwords using password_hash().
Verify passwords using password_verify().
Regenerate session ID after login.
Protect sensitive forms with CSRF tokens.
Check user permissions before protected actions.
Validate uploaded files carefully.
Do not show technical errors in production.
Use HTTPS in production.
Keep PHP, packages, and server software updated.
Store secrets outside public files.
Use backups for important data.
This checklist does not cover every possible security topic, but it covers the most important foundations that every PHP beginner should understand.
How PHP Security Works in Real Projects
In real applications, security concepts are used together. For example, a login system validates input, uses prepared statements to find the user, verifies the password hash, regenerates the session ID, stores only the user ID in the session, and redirects the user to a protected dashboard.
<?php
session_start();
$pdo = new PDO(
"mysql:host=localhost;dbname=app;charset=utf8mb4",
"root",
""
);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$email = trim($_POST["email"] ?? "");
$password = $_POST["password"] ?? "";
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
echo "Invalid login details.";
exit;
}
$statement = $pdo->prepare("SELECT id, name, email, password FROM users WHERE email = :email LIMIT 1");
$statement->execute([
"email" => $email
]);
$user = $statement->fetch(PDO::FETCH_ASSOC);
if (!$user || !password_verify($password, $user["password"])) {
echo "Invalid login details.";
exit;
}
session_regenerate_id(true);
$_SESSION["user_id"] = $user["id"];
header("Location: dashboard.php");
exit;
?>This example combines validation, prepared statements, password verification, session security, and safe login behavior.
Security in Modern PHP Frameworks
Modern PHP frameworks such as Laravel and Symfony provide built-in tools for many security tasks. They include request validation, CSRF protection, password hashing, authentication systems, authorization policies, secure sessions, escaping in templates, and database query builders.
However, learning security in pure PHP is still important. It helps you understand what the framework is doing behind the scenes and allows you to make better decisions when building custom logic.
For example, Laravel can protect forms with CSRF tokens automatically, hash passwords using secure helpers, escape Blade output by default, and use Eloquent or query builder bindings to reduce SQL injection risks. But the developer still needs to validate input, design permissions, protect file uploads, and avoid unsafe coding practices.
Conclusion
PHP security is a core skill for every backend developer. Secure applications require careful handling of input, output, databases, sessions, passwords, files, errors, cookies, and permissions.
The most important practices include validating input, escaping output, using prepared statements, protecting against SQL injection, preventing XSS, using CSRF tokens, hashing passwords, securing sessions, validating file uploads, and hiding technical errors in production.
Security is not a single feature. It is a continuous habit that should be applied throughout the whole project. After understanding these PHP security basics, the next step is to practice by building a secure login system, a protected dashboard, a safe file upload form, and a CRUD application with validation and prepared statements.

