Top 10 PHP Security Vulnerabilities Every Developer Should Know
Explore 10 important PHP security vulnerabilities, from SQL injection and XSS to broken access control and insecure sessions, with practical examples, defensive code, and prevention steps.

Table of Content
Table of Content
A PHP application can look polished, load quickly, and pass every functional test while still exposing private customer records. Sometimes the problem is a database query built from user input. Sometimes it is a file upload folder that allows scripts to execute. And sometimes it is something less obvious: an invoice download that checks whether someone is logged in but never checks whether the invoice belongs to them.
These mistakes are easy to overlook because the application usually works exactly as expected for legitimate users. Security problems appear when someone interacts with it in ways the developer did not anticipate.
Most vulnerabilities discussed here are not unique to PHP. They are application security failures that can occur in PHP projects, including custom websites, APIs, content management systems, and business dashboards.
This guide explains ten important vulnerabilities, how they appear in everyday development, and the defensive decisions that help prevent them. The list is a practical learning guide, rather than an official ranking of the most common attacks.
1. SQL Injection
SQL injection happens when untrusted input becomes part of a database query’s instructions instead of remaining a separate data value.
Imagine a product search feature that inserts the search term directly into an SQL string. The developer expects a product name, but the database receives whatever query structure the completed string contains.
Depending on database permissions and the vulnerable query, the consequences can include unauthorized access to records, modified data, or deleted information.
How to prevent it
Use parameterized queries with PDO or MySQLi. Here is a small PDO example, assuming $pdo is an existing database connection:
$stmt = $pdo->prepare( 'SELECT id, name, price FROM products WHERE category_id = :category_id' ); $stmt->execute([ 'category_id' => $categoryId ]); $products = $stmt->fetchAll(PDO::FETCH_ASSOC);
The parameter supplies a value without becoming SQL syntax. However, placeholders cannot safely substitute arbitrary table names, column names, or sorting directions. Dynamic identifiers need a strict allowlist. PHP’s documentation makes this distinction clear: prepared statements do not protect query fragments that developers still construct unsafely. Manual
Also give the application database account only the permissions it needs. A website that reads articles should not connect using a database administrator account.
Review question: Does every database query keep user-controlled values separate from SQL instructions?
2. Cross-Site Scripting (XSS)
Cross-site scripting occurs when an application allows untrusted content to execute as JavaScript in another person’s browser.
A typical example is a comment system that stores a visitor’s message and later displays it as raw HTML. If that content contains executable markup, anyone viewing the comment may be affected.
XSS can alter page content, capture information entered into a page, or perform actions through the victim’s authenticated browser. An HttpOnly cookie helps protect the cookie value from JavaScript access, but it does not stop malicious scripts from interacting with the application.
How to prevent it
For plain text displayed inside an HTML element, encode the output:
<p> <?= htmlspecialchars( $comment, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' ) ?> </p>
This example applies to an HTML text context. JavaScript, CSS, and URLs require their own handling rules.
If users need rich text, use a maintained HTML sanitizer with a carefully restricted set of allowed elements and attributes. Removing <script> tags alone is insufficient because executable behavior can appear elsewhere in HTML.
Review frontend code too. A PHP API can return safe JSON that becomes dangerous when JavaScript inserts its contents through innerHTML. Use textContent when displaying plain text. OWASP highlights this distinction in its frontend security guidance. OWASP Cheat Sheet Series
Review question: Where does external content become HTML, and is each output context handled correctly?
3. Cross-Site Request Forgery (CSRF)
CSRF tricks a logged-in user’s browser into sending an unwanted request to an application.
For example, an account settings endpoint might accept an email address change whenever a valid session cookie accompanies the request. An attacker could try to trigger that request from another website.
The weakness is treating the presence of a session cookie as proof that the user intentionally requested the action.
How to prevent it
For applications using cookie-based authentication, protect state-changing requests with the framework’s CSRF mechanism or a correctly implemented token system.
A typical token workflow is:
- Generate an unpredictable token associated with the user’s session.
- Include it in legitimate forms or an appropriate request header.
- Reject state-changing requests with missing or invalid tokens.
- Compare token values safely, for example with
hash_equals().
Do not use GET requests to delete records, change passwords, or perform other state changes.
Set an appropriate SameSite policy on session cookies and consider origin verification as another defensive layer. OWASP explains that SameSite controls cross-site cookie behavior, but its suitability depends on the application and request flow. OWASP Cheat Sheet Series
Review question: What proves that a sensitive request came from the application’s intended user flow?
4. Broken Access Control and IDOR
Broken access control happens when users can reach data or perform actions outside their permissions.
One common form is an insecure direct object reference, or IDOR. Consider this URL:

/account/invoices/482
If changing the invoice number reveals another customer’s invoice, the application has failed to enforce ownership.
This problem can remain even when the endpoint uses prepared statements and requires a valid login. Authentication establishes identity; authorization determines what that identity may access.
How to prevent it
For a customer viewing their own invoice, scope the database lookup to the authenticated account:
$stmt = $pdo->prepare( 'SELECT id, total, status FROM invoices WHERE id = :invoice_id AND customer_id = :customer_id' ); $stmt->execute([ 'invoice_id' => $invoiceId, 'customer_id' => $_SESSION['customer_id'] ]);
The customer identity must come from trusted authentication state, rather than a submitted form field.
Apply permission checks to downloads, exports, background jobs, and API endpoints as well as visible pages. Random identifiers can reduce guessing, but they do not replace authorization. OWASP identifies missing authorization on referenced objects as the core IDOR failure. OWASP Foundation
Review question: Can one ordinary account access another account’s resources by changing an identifier?
5. Unrestricted File Uploads
File uploads create a boundary between content supplied by a user and files stored by the server.
A profile photo feature becomes dangerous if the application accepts executable files, trusts the submitted filename, or stores uploads somewhere the web server can execute them.
Even files that cannot execute as PHP may introduce problems, including active HTML content, excessive storage use, or dangerous input for document-processing tools.
How to prevent it
Build upload handling around what the feature actually needs:
- Allow only necessary file types and extensions.
- Inspect content using server-side tools such as
finfo. - Set size limits at both application and server levels.
- Generate storage filenames instead of trusting submitted names.
- Store uploads outside the public web root where practical.
- Prevent script execution in upload storage.
- Check authorization before accepting or serving private files.
An extension check and a MIME check are useful layers, but neither establishes that a file is harmless. Image uploads may also benefit from decoding and re-encoding through a maintained library. OWASP recommends combining controls because upload security depends on the whole processing and storage workflow. OWASP Cheat Sheet Series
Review question: What happens if someone uploads a file that is deliberately malformed, misleadingly named, or unusually large?
6. Path Traversal and Unsafe File Inclusion
Path traversal happens when user-controlled paths allow access outside an intended directory.
A download feature might expect a report filename but accidentally accept a path pointing elsewhere on the server. Unsafe file inclusion becomes especially serious when user input influences include or require, because PHP may execute the selected file.
How to prevent it
Prefer mapping a public identifier to a server-controlled location. For a small set of templates, an allowlist is straightforward:
$templates = [
'help' => __DIR__ . '/templates/help.php',
'contact' => __DIR__ . '/templates/contact.php'
];
$page = $_GET['page'] ?? 'help';
if (!is_string($page) || !isset($templates[$page])) {
http_response_code(404);
exit;
}
require $templates[$page];
Here, the visitor chooses an allowed key rather than supplying a filesystem path.
For larger download systems, resolve authorized file records through the database. If filesystem containment checks are necessary, account for canonical paths, directory boundaries, and symbolic links. Simply removing a few suspicious characters is an unreliable foundation.
Review question: Can external input select an arbitrary file or influence which PHP code gets included?
7. OS Command Injection
OS command injection occurs when external input changes the meaning of a command executed by the server.
PHP applications sometimes call system utilities to process images, create archives, or convert documents. Building a shell command by concatenating submitted values can allow those values to become shell instructions.
How to prevent it
First determine whether a native PHP function or maintained library can perform the task.
If an external process is necessary, use a process interface that supports separate arguments and avoids shell interpretation. Keep the executable and supported options under application control.
Validate arguments according to their purpose. A numeric conversion setting should be an integer within a defined range, rather than an arbitrary string.
Quoting a shell argument can help in particular implementations, but it does not automatically prevent dangerous options from being passed to the underlying program. Restrict what the program is allowed to do, run it with limited privileges, and enforce execution timeouts.
Review question: Can submitted input change the executable, command structure, or supported options?
8. Insecure Deserialization
Serialization converts data into a format that can be stored or transferred. Deserialization reconstructs it.
PHP’s unserialize() deserves particular care because serialized objects can interact with application classes and trigger behavior during reconstruction or later object handling.
The result can be more serious than receiving an unexpected array. Depending on available classes and application behavior, consequences may include unauthorized file operations or code execution.
How to prevent it
Do not pass untrusted input to unserialize(). PHP’s manual explicitly warns against doing so even when the allowed_classes option is configured. Manual
For ordinary data exchange, JSON is generally a more suitable choice:
$data = json_decode( $requestBody, true, 512, JSON_THROW_ON_ERROR );
Parsing JSON is only the first step. Validate the resulting structure, field types, permitted values, and request size. Never trust a submitted field such as "role": "admin" merely because the JSON parsed successfully.
Review question: Does any cookie, request, imported file, or externally writable cache entry reach unserialize()?
9. Weak Password Storage and Account Recovery
A login system is vulnerable when passwords are stored in plaintext, protected with reversible encryption, or hashed using fast general-purpose functions such as MD5 or SHA-1.
The damage often appears after a database leak. Weak storage makes it easier to recover passwords and potentially compromise accounts on other services where users reused them.
How to prevent it
Use PHP’s password API:
$hash = password_hash($password, PASSWORD_DEFAULT);
if (password_verify($submittedPassword, $storedHash)) {
// Continue the authenticated login flow.
}
Let the API manage salts, and use password_needs_rehash() when upgrading stored hashes.
Protect the surrounding account lifecycle too. Rate-limit login attempts, consider multifactor authentication for sensitive accounts, and keep password reset tokens unpredictable, short-lived, and single-use.
Never email users their existing password. A system capable of retrieving the original password has made a dangerous storage decision.
Review question: If the users table leaked, how difficult would password recovery be for an attacker?
10. Session Fixation and Session Hijacking
A session identifier often functions as a temporary credential. Anyone who can successfully reuse it may be able to act as the authenticated user.
Session fixation involves getting a victim to use an identifier known to the attacker. Session hijacking involves obtaining or abusing an existing authenticated session.
How to prevent it
Use HTTPS throughout the application and configure session cookies with Secure, HttpOnly, and an appropriate SameSite setting.
Enable strict session mode and regenerate the session identifier after authentication or privilege changes. PHP’s session guidance emphasizes strict mode as an important protection against accepting attacker-supplied, uninitialized session identifiers. Manual
Also implement server-enforced idle and absolute expiration. Logout should invalidate the server-side session, and password changes should have a defined policy for revoking other sessions.
Avoid putting session identifiers in URLs, where they can leak through history, logs, or shared links.
Review question: What happens to existing sessions after logout, a password reset, or an administrator removing access?
Turn This List into a Practical Security Review
Start with the features that cross the most sensitive boundaries: login, account recovery, payments, uploads, private downloads, and administrator actions.
For each feature, trace the data from entry to final use. Identify where it reaches a database, browser, filesystem, external process, or permission decision. Then check the protection at that specific boundary.
Use two test accounts with different permissions to verify ownership checks. Review browser code alongside PHP templates. Inspect deployment settings so development errors, backup files, and credentials are not publicly exposed.
Finally, keep PHP, frameworks, and dependencies maintained. Secure application code still depends on the software and configuration supporting it.
Frequently Asked Questions
1. Is PHP inherently insecure?
PHP can support secure applications. Many serious problems arise from unsafe application logic, missing authorization checks, vulnerable dependencies, and poor deployment settings. Security depends on how the entire system is designed and maintained.
2. Do prepared statements prevent every database-related attack?
No. They protect properly bound values against SQL injection, but they do not enforce record ownership, restrict database permissions, or make dynamically constructed SQL identifiers safe.
3. Is input validation enough to secure a PHP application?
No. Validation checks whether data fits expectations. Applications also need parameterized queries, context-appropriate output encoding, authorization, secure session handling, and controls specific to files or external processes.
4. Does using a PHP framework remove these risks?
A maintained framework provides useful security mechanisms, but developers must use them correctly. Custom queries, disabled CSRF protection, unrestricted uploads, and missing ownership checks can still introduce vulnerabilities.
5. Which PHP security issues should developers fix first?
Prioritize reachable vulnerabilities that expose sensitive data, enable unauthorized actions, or allow code execution. Consider impact, accessibility, and affected users. A private invoice disclosure can deserve urgent attention even when it requires no sophisticated exploit.

