Security Introduction
Why Security Matters In simple terms: Every website that accepts input — a search box, a login form, a file upload — can be attacked. Attackers try to steal data, take over accounts, deface pages, or
Why Security Matters
In simple terms: Every website that accepts input — a search box, a login form, a file upload — can be attacked. Attackers try to steal data, take over accounts, deface pages, or use the server for their own purposes. Most successful attacks exploit a small number of well-known mistakes. Learning those mistakes and their fixes protects your users, your business, and your reputation.
Technically: Web application security is about controlling how untrusted data flows through an application and who may perform which actions. PHP itself is not inherently insecure; vulnerabilities come from how code handles input, output, authentication, sessions, files, configuration, and dependencies. The OWASP Top 10 (Open Worldwide Application Security Project) lists the most critical web application risks and is the standard reference for developers.
The Core Principle: Never Trust Input
Anything that comes from outside your code is untrusted — including data that seems harmless:
| Source | Examples |
|---|---|
| Request data | $_GET, $_POST, $_COOKIE, $_FILES, JSON bodies |
| Request headers | User-Agent, Referer, Host, X-Forwarded-For, custom headers |
| Data stored earlier | Product names, comments, profile fields entered by users |
| External systems | API responses, webhooks, imported CSV/XML files, emails |
The defense has two halves:
Input ──► validate (is it what we expect?) ──► application logic
Output ◄── encode/escape for the destination (HTML, SQL, URL, shell, JSON)
- Validate input when it arrives: type, format, length, range, allowed values (see PHP Form Validation, PHP Filters).
- Escape or parameterize output for the context where it is used: HTML escaping for pages, prepared statements for SQL,
escapeshellarg()for shell commands,json_encode()for JSON.
Validation alone is not enough (valid data can still contain < or '), and escaping alone is not enough (an "age" of -5 is not an injection, but it is still wrong).
A Vulnerable Example
The following page contains several classic vulnerabilities in just a few lines:
<?php
// profile.php?id=7 — DO NOT USE
ini_set('display_errors', '1');
$db = new mysqli('localhost', 'root', '', 'shop');
$result = $db->query("SELECT name, bio FROM users WHERE id = " . $_GET['id']);
$user = $result->fetch_assoc();
echo "<h1>Profile of " . $user['name'] . "</h1>";
echo "<div>" . $user['bio'] . "</div>";
echo "<a href='/search?q=" . $_GET['q'] . "'>Back to results</a>";
| Problem | Vulnerability | Consequence |
|---|---|---|
$_GET['id'] concatenated into SQL | SQL injection | ?id=0 UNION SELECT email, password_hash FROM users reads private data |
| Name and bio printed without escaping | Stored XSS | A bio containing <script> runs in every visitor's browser |
$_GET['q'] printed into an attribute | Reflected XSS | A crafted link executes JavaScript for whoever clicks it |
Connecting as root without a password | Excessive privileges | One injection compromises the whole database server |
display_errors = 1 | Information disclosure | Error messages reveal paths, queries, and structure |
The Fixed Version
<?php
// profile.php?id=7
declare(strict_types=1);
require __DIR__ . '/../src/pdo.php'; // least-privilege user, credentials from environment
function e(string $value): string
{
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
if (!$id) {
http_response_code(404);
exit('Profile not found.');
}
$stmt = pdo()->prepare('SELECT name, bio FROM users WHERE id = ?');
$stmt->execute([$id]);
$user = $stmt->fetch();
if ($user === false) {
http_response_code(404);
exit('Profile not found.');
}
$backUrl = '/search?' . http_build_query(['q' => (string) ($_GET['q'] ?? '')]);
?>
<h1>Profile of <?= e($user['name']) ?></h1>
<div><?= nl2br(e($user['bio'])) ?></div>
<a href="<?= e($backUrl) ?>">Back to results</a>
Every fix applies one of the principles: validate the ID, use a prepared statement, escape every output for HTML, build URLs with encoding functions, use a restricted database account, and handle errors without revealing details (with display_errors off in production).
Key Security Principles
| Principle | Meaning | Example |
|---|---|---|
| Defense in depth | Several independent layers of protection | Prepared statements and a least-privilege DB user and a web application firewall |
| Least privilege | Grant only the permissions needed | The app's DB user cannot DROP tables; upload folders are not executable |
| Secure defaults | The safe option is the default | New accounts have no admin rights; cookies are HttpOnly and Secure |
| Fail securely | Errors must not open doors | A failed permission check denies access; errors show generic messages |
| Keep it simple | Complex security logic hides bugs | Use proven libraries and framework features instead of custom crypto |
| Validate on the server | Client-side checks are for convenience only | Attackers send requests directly, bypassing JavaScript |
The Lessons in This Section
| Lesson | Protects Against |
|---|---|
| Password Hashing | Stolen databases revealing users' passwords |
| SQL Injection Prevention | Attackers reading or changing the database |
| XSS Prevention | Malicious scripts running in users' browsers |
| CSRF Protection | Other websites performing actions as your logged-in users |
| Session Security | Session hijacking and fixation |
| Secure Cookies | Cookie theft and manipulation |
| File Upload Security | Uploaded files executing code on the server |
| Filesystem Security | Path traversal and unauthorized file access |
| Error Reporting in Production | Information disclosure |
| Hiding PHP | Revealing technology details to attackers |
| Encryption with Sodium | Exposure of sensitive stored data |
Keeping the Platform Secure
Secure code can still be compromised through outdated software:
- Run a supported PHP version. Each PHP release receives two years of active support and then two years of security fixes. Check the current list at
https://www.php.net/supported-versions.php; unsupported versions receive no security patches. - Update dependencies.
composer auditreports Composer packages with known vulnerabilities; run it regularly and in your deployment pipeline (see Composer). - Use HTTPS everywhere. Without TLS, passwords and session cookies travel in plain text.
- Harden the server. Keep the operating system, web server, and database updated; restrict network access to databases and admin tools.
- Log and monitor. Record failed logins, permission errors, and unusual activity, and review alerts (see Error Logging).
Common Mistakes
Relying on Client-Side Validation
Effect: Attackers bypass it by sending requests directly.
Fix: Validate every input on the server.
Escaping Input Instead of Output
Effect: Data is stored in an escaped form that is wrong for other contexts, and escaping is forgotten elsewhere.
Fix: Store raw (validated) data; escape when outputting, for the specific context.
"Security Through Obscurity"
Effect: Hidden admin URLs or renamed files give false confidence.
Fix: Protect resources with real authentication and authorization.
Writing Custom Cryptography
Effect: Subtle, exploitable flaws.
Fix: Use password_hash(), random_bytes(), and Sodium.
Best Practices
- Treat all external data as untrusted; validate input and encode output by context.
- Use prepared statements, output escaping, CSRF tokens, and secure sessions consistently.
- Apply least privilege to database users, file permissions, and application roles.
- Keep PHP, dependencies, and servers updated; use HTTPS everywhere.
- Learn the OWASP Top 10 and review code with security in mind.
Official reference: https://www.php.net/manual/en/security.intro.php