Skip to content

PHP Security — Complete Guide to Securing PHP Applications

DodaTech Updated 2026-06-28 7 min read

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: &lt;script&gt;alert('xss')&lt;/script&gt;

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

  1. Using mysql_ functions*: These are removed in PHP 7 and offer no protection against SQL injection. Use PDO or mysqli with prepared statements.
  2. Storing passwords with MD5 or SHA1: These hashes are trivially reversed with rainbow tables. Use Argon2id or bcrypt via password_hash.
  3. 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.
  4. 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.
  5. 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

  1. 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.
  2. 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.
  3. What does the htmlspecialchars function do?

    • It converts special HTML characters to entities: < becomes &lt;, > becomes &gt;, " becomes &quot;, and & becomes &amp;.
  4. 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.
  5. 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 is the most common PHP vulnerability?

SQL injection remains the most common critical vulnerability. It is also the easiest to prevent by using prepared statements with PDO or mysqli.

How do I prevent XSS in PHP?

Use htmlspecialchars with ENT_QUOTES and UTF-8 encoding when outputting user-controlled data in HTML context. For other contexts, use context-specific escaping.

What is the best password hashing algorithm in PHP?

Argon2id is the current recommendation. Use password_hash($password, PASSWORD_ARGON2ID) with default cost parameters.

What are CSRF tokens and how do they work?

CSRF tokens are random values generated per session and embedded in forms. When the form is submitted, the server validates the token. An attacker cannot guess the token.

Is it safe to use $_GET and $_POST directly?

No. Always validate and sanitize superglobal input. Use filter_input or custom validation functions before processing user-supplied data.

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