PHP Security Vulnerabilities: 10 Flaws Attackers Actually Exploit (and How to Fix Each One)
The most common PHP security vulnerabilities — SQL injection, XSS, CSRF, broken access control, unsafe file uploads and more — explained with the fix for each, mapped to the OWASP Top 10 2025.
By Rehan Baqir
PHP runs a huge share of the web — WordPress, Laravel, Symfony, and millions of custom applications. That popularity makes it a constant target. The good news: the flaws attackers exploit are not mysterious, and the fixes are well understood. This guide covers the ten that matter most, what each one looks like in PHP, and the fix.
1. SQL injection
What it is: User input is concatenated into a database query, so an attacker can change the query's meaning — dumping tables, bypassing logins, or deleting data.
Vulnerable pattern:
$email = $_POST['email'];
$query = "SELECT * FROM users WHERE email = '$email'";
The fix — prepared statements with PDO:
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute(['email' => $email]);
$user = $stmt->fetch();
Prepared statements separate the SQL structure from the data, so input can never become SQL syntax. Use least-privilege database users in production, and be careful with raw queries even inside frameworks — query builders are only safe when you actually use their parameter binding.
2. Cross-site scripting (XSS)
What it is: Attacker-controlled data is rendered in a page without escaping, so a victim's browser executes it — stealing sessions, defacing pages, or submitting actions as the victim.
The fix — escape output for its context:
echo htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
Input filtering alone is not enough: validate input on the way in, but escape output for where it is displayed — HTML body, attribute, JavaScript string, and URL contexts all need different handling. Template engines with auto-escaping help, and a Content Security Policy limits the damage if something slips through. Never render raw user HTML unless it passes through a trusted sanitizer.
3. Cross-site request forgery (CSRF)
What it is: A malicious page tricks a logged-in user's browser into submitting a request to your site — changing an email, password, or making a purchase — because the browser helpfully attaches the session cookie.
The fix: Every state-changing form (POST, PUT, DELETE) must carry a per-session, unpredictable token that the server verifies:
// On form render
$_SESSION['csrf'] = bin2hex(random_bytes(32));
// On submission
if (!hash_equals($_SESSION['csrf'], $_POST['csrf'] ?? '')) {
http_response_code(403);
exit('Invalid request');
}
Also set cookies with SameSite=Lax or Strict and verify the Origin header where practical.
4. Broken access control
What it is: The #1 category in the OWASP Top 10 2025. A user accesses something they should not — another user's order, an admin endpoint, a record by guessing an ID — because the server never checked permission.
The fix: Authorize on the server, on every request, for every object — never trust the UI to hide things:
$order = getOrder($_GET['id']);
if ($order->user_id !== $_SESSION['user_id'] && !isAdmin()) {
http_response_code(403);
exit('Forbidden');
}
Hiding a button is not access control. Neither is an unguessable URL.
5. Unsafe file uploads
What it is: An upload form accepts any file, and the attacker uploads a PHP script, then visits its URL and gains code execution.
The fix — all of these, together:
- Allowlist extensions and MIME types (never a blocklist).
- Rename uploads to random names; never trust the original filename.
- Store uploads outside the web root, or serve them through a script that sets a safe content type.
- Reject double extensions (
image.php.jpg) and executable permissions.
6. File inclusion and path traversal
What it is: User input reaches include, require, or a file path, so ?page=../../etc/passwd or a remote URL becomes readable or executable code. Wordfence's Q2 2026 data counted 101 PHP remote file inclusion flaws in one quarter.
The fix: Never build an include path from raw input. Map input to an allowlist:
$allowed = ['home', 'about', 'contact'];
$page = $_GET['page'] ?? 'home';
if (!in_array($page, $allowed, true)) {
$page = 'home';
}
include __DIR__ . '/pages/' . $page . '.php';
Disable allow_url_include in production, always.
7. Session hijacking and fixation
What it is: Session IDs leaked over HTTP, in URLs, or through XSS let an attacker ride a victim's session. Fixation attacks set a known session ID before login.
The fix:
- Force HTTPS and set session cookies
Secure,HttpOnly, andSameSite=Lax. - Regenerate the session ID on login and privilege change:
session_regenerate_id(true). - Never put session IDs in URLs.
8. Weak password storage
What it is: Passwords stored in plain text, MD5, or SHA-1 fall instantly when a database leaks. The fix has been settled for a decade:
$hash = password_hash($password, PASSWORD_DEFAULT);
// Later:
if (password_verify($password, $hash)) { /* logged in */ }
password_hash() uses bcrypt by default and handles salting. If your codebase still contains md5($password), that is a finding, not a legacy quirk.
9. Insecure deserialization
What it is: unserialize() on user-controlled data lets an attacker instantiate arbitrary classes and trigger magic methods — often leading to file deletion or code execution. Q2 2026 saw 103 deserialization disclosures.
The fix: Do not unserialize untrusted data, period. Use json_encode / json_decode for data exchange. If you must handle serialized data, sign it (HMAC) and verify the signature before touching it.
10. Security misconfiguration and outdated components
What it is: Debug mode left on in production, directory listing enabled, default credentials, error messages that leak paths and versions, and — most commonly — old software. The OWASP Top 10 2025 folds vulnerable components into supply-chain failures for a reason: your dependencies are part of your attack surface.
The fix:
display_errors = Offin production; log errors privately.- Remove install scripts, test files, and
.gitfrom the web root. - Run
composer auditregularly and upgrade on a schedule. - Track PHP itself: recent releases patched a critical SOAP use-after-free (CVE-2026-6722, CVSS 9.8), a PDO Firebird SQL injection (CVE-2025-14179), and a PHP-FPM status-page XSS (CVE-2026-6735). If your PHP is past security support, no code fix covers that gap.
The hardening checklist
Work through this in order and you cover the vast majority of real PHP risk:
- Upgrade PHP to a supported version and run
composer audit. - Convert every raw query to a prepared statement.
- Escape every echo of user data with
htmlspecialchars(or your template engine's auto-escaping). - Add CSRF tokens to every state-changing form.
- Add server-side authorization checks to every object access.
- Harden uploads: allowlist, random names, outside the web root.
- Turn off
display_errorsandallow_url_includein production. - Set secure session cookies and regenerate IDs on login.
- Replace any MD5/SHA-1 password storage with
password_hash(). - Put a WAF in front as a second layer — not a substitute.
Security is not a feature you ship once. It is a habit: patch on a schedule, review the ten classes above whenever you add a feature, and re-test after every fix.
Frequently Asked Questions
Common questions answered to help you get the most from this tool.