PHP Security — Complete Guide to Securing PHP Applications
In this tutorial, you will learn about PHP Security. We cover key concepts, practical examples, and best practices to help you master this topic.
PHP security encompasses the practices and coding techniques that protect web applications from common attacks including SQL injection, cross-site scripting (XSS), cross-site request forgery (CSRF), session hijacking, and password cracking. This tutorial covers each vulnerability in depth with PHP 8 code examples, explains how attacks work from the attacker's perspective, and shows you how to implement defenses using prepared statements, output escaping, CSRF tokens, secure sessions, and password hashing.
What You'll Learn
- Preventing SQL injection with PDO prepared statements and type hints
- Defending against XSS attacks with output escaping strategies
- Implementing CSRF protection with token generation and validation
- Securing password storage with Argon2id and password_hash
- Protecting sessions from fixation and hijacking
- Validating and sanitizing user input before processing
- Setting secure HTTP headers for additional protection
Why It Matters
Security is not optional. A single SQL injection vulnerability can expose your entire user database. An XSS flaw can hijack admin sessions. According to OWASP, injection and broken authentication consistently rank among the top web application risks. PHP's flexibility makes it easy to write insecure code accidentally. Understanding these vulnerabilities and their fixes is essential for any PHP developer building public-facing applications.
Real-World Use
Durga Antivirus Pro's web dashboard processes sensitive user data including license keys, scan histories, and personal information. The application uses prepared statements for every database query, Argon2id for password storage, CSRF tokens on every form, and Content-Security-Policy headers that block inline scripts. These measures have prevented multiple attempted attacks since launch.
Learning Path
flowchart LR A[Symfony Basics] --> B[Security\nYou are here] B --> C[CLI Scripts] style B fill:#f90,color:#fff
SQL Injection Prevention
SQL injection occurs when user input is included in SQL queries without proper sanitization.
Vulnerable code:
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$username = $_POST['username'];
$stmt = $pdo->query("SELECT * FROM users WHERE username = '$username'");
An attacker submits ' OR '1'='1 as the username. The query becomes:
SELECT * FROM users WHERE username = '' OR '1'='1'
Secure version using prepared statements:
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
]);
$username = $_POST['username'];
$stmt = $pdo->prepare('SELECT * FROM users WHERE username = :username');
$stmt->execute(['username' => $username]);
$user = $stmt->fetch();
if ($user) {
echo "Welcome, " . htmlspecialchars($user['username'], ENT_QUOTES, 'UTF-8');
} else {
echo "User not found";
}
Output (for valid user):
Welcome, alice
XSS Prevention
Cross-site scripting (XSS) lets attackers inject JavaScript into pages viewed by other users. There are three types: reflected, stored, and DOM-based.
Vulnerable code that echoes user input directly:
<?php
$search = $_GET['q'];
echo "You searched for: $search";
An attacker crafts a URL like http://example.com/search?q=<script>alert('xss')</script>. The script executes in every Visitor's browser.
Secure version:
<?php
$search = $_GET['q'] ?? '';
$clean = htmlspecialchars($search, ENT_QUOTES | ENT_HTML5, 'UTF-8');
echo "You searched for: $clean";
Output (the script tag is rendered as text, not executed):
You searched for: <script>alert('xss')</script>
For contexts outside HTML (JavaScript, CSS, URLs), use context-specific escaping or contextual auto-escaping templates like Twig or Blade.
CSRF Protection
CSRF tricks an authenticated user into performing actions they did not intend. A malicious site submits a form to your site using the victim's session cookie.
Generate a CSRF token and include it in forms:
<?php
session_start();
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
function csrf_token(): string
{
return $_SESSION['csrf_token'];
}
function verify_csrf(string $token): bool
{
return hash_equals($_SESSION['csrf_token'], $token);
}
Form:
<form method="POST" action="/update-email">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(csrf_token(), ENT_QUOTES, 'UTF-8') ?>">
<input type="email" name="email" required>
<button type="submit">Update Email</button>
</form>
Processing the form:
<?php
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!isset($_POST['csrf_token']) || !verify_csrf($_POST['csrf_token'])) {
http_response_code(403);
die('Invalid CSRF token');
}
$email = filter_var($_POST['email'], FILTER_VALIDATE_EMAIL);
if ($email === false) {
die('Invalid email format');
}
// Update email in database
echo "Email updated successfully";
}
Password Hashing
Never store passwords in plain text. PHP provides password_hash and password_verify with automatic salt generation and algorithm selection:
<?php
$password = 'user-secure-password-123';
$hash = password_hash($password, PASSWORD_ARGON2ID, [
'memory_cost' => 65536,
'time_cost' => 4,
'threads' => 3,
]);
echo "Hash: $hash\n";
if (password_verify($password, $hash)) {
echo "Password is valid";
} else {
echo "Invalid password";
}
Output:
Hash: $argon2id$v=19$m=65536,t=4,p=3$...
Password is valid
Session Security
Protect sessions from fixation and hijacking:
<?php
ini_set('session.use_strict_mode', '1');
ini_set('session.use_only_cookies', '1');
ini_set('session.cookie_httponly', '1');
ini_set('session.cookie_secure', '1'); // HTTPS only
ini_set('session.cookie_samesite', 'Lax');
ini_set('session.gc_maxlifetime', '3600'); // 1 hour
session_start();
// Regenerate session ID after login
if ($loginSuccessful) {
session_regenerate_id(true);
}
Input Validation and Sanitization
Use PHP's filter functions for common input types:
<?php
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
$url = filter_input(INPUT_POST, 'website', FILTER_VALIDATE_URL);
$age = filter_input(INPUT_POST, 'age', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1, 'max_range' => 120]]);
$ip = filter_input(INPUT_SERVER, 'REMOTE_ADDR', FILTER_VALIDATE_IP);
if ($email === false || $url === false || $age === false) {
http_response_code(400);
die('Invalid input data');
}
echo "Valid email: $email, Age: $age";
Security Headers
Set HTTP security headers to enable browser-level protections:
<?php
header('Content-Security-Policy: default-src \'self\'; script-src \'self\'');
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');
header('X-XSS-Protection: 0');
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
header('Referrer-Policy: strict-origin-when-cross-origin');
Common Mistakes
- Using mysql_ functions*: These are removed in PHP 7 and offer no protection against SQL injection. Use PDO or mysqli with prepared statements.
- Storing passwords with MD5 or SHA1: These hashes are trivially reversed with rainbow tables. Use Argon2id or bcrypt via password_hash.
- Trusting $_FILES data: File uploads can contain malicious payloads. Validate MIME type, size, and extension server-side. Never trust the MIME type from the client.
- Displaying raw error messages to users: PHP error messages reveal database schema, file paths, and configuration. Log errors and show a generic message to users.
- Disabling security features for convenience: Disabling CSP, CSRF, or HTTPS during development is fine, but forgetting to enable them in production leaves the application exposed.
Practice Questions
What is the difference between SQLI and XSS?
- SQL injection targets the database by injecting SQL commands. XSS targets the browser by injecting JavaScript. Both exploit insufficient input validation.
Why should you use PDO prepared statements instead of escaping functions?
- Prepared statements separate SQL logic from data. The database driver handles escaping, eliminating the possibility of injection even with malicious input.
What does the
htmlspecialcharsfunction do?- It converts special HTML characters to entities:
<becomes<,>becomes>,"becomes", and&becomes&.
- It converts special HTML characters to entities:
How does Argon2id compare to bcrypt?
- Argon2id is the winner of the Password Hashing Competition and is resistant to GPU cracking attacks. It is the recommended algorithm as of PHP 8.
Challenge: Build a secure user registration and login system that implements account lockout after 5 failed attempts, uses Argon2id hashing, CSRF protection on all forms, and session regeneration after login.
Mini Project
Create a secure file-sharing application:
- Users register and log in with Argon2id-hashed passwords.
- Authenticated users can upload files. Validate MIME type, scan for executable content, and limit file size to 10MB.
- Generate a random 32-character token for each file as the download link.
- Store files outside the web root and serve them through a PHP download script that checks permissions.
- Log all upload and download attempts for audit.
- Add Rate Limiting to prevent abuse.
FAQ
What's Next
Now that your applications are secure, learn to build command-line tools with CLI Scripts.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro