Login forms, comment fields, URL parameters, and cookies often appear to be ordinary data. However, all of these come from outside the application and must be treated as untrusted input. A small mistake in processing them can open the door to cross-site scripting (XSS), SQL injection, or user session hijacking.
The good news is that prevention does not always require a complicated system. Most basic protections can be built from consistent coding habits: input validation, using parameterized queries, escaping output, and properly managing sessions.
First Principle: Do Not Trust Data Sources
Data from users does not only come from HTML forms. Values from $_GET, $_POST, $_COOKIE, HTTP headers, file uploads, and even data from external APIs also need to be treated as untrusted input.
Validation does not just mean checking if fields are not empty. The application needs to define the shape of the data that is accepted. For example, an article ID should be an integer, an email address needs to have a reasonable format, while a username may need to have length and character restrictions.
However, validation alone is not enough to prevent XSS. Valid data can still contain dangerous special characters when displayed back on the page.
XSS: Data Turns into Code in the Browser
XSS occurs when user input is displayed in the browser without appropriate safeguards. An example is a comment containing HTML or JavaScript snippets that is then displayed raw to other visitors.
For HTML contexts, use escaping when outputting, not just when data is stored. The htmlspecialchars() function can convert special characters like angle brackets into safe forms so that the browser treats them as text.
$name = $_POST['name'] ?? '';
echo htmlspecialchars($name, ENT_QUOTES, 'UTF-8');Note that escaping must be adjusted according to context. Data inserted into HTML, HTML attributes, JavaScript, URLs, and CSS have different rules. Do not assume that one cleaning function can be used for all places.
For more detailed guidance on output contexts and XSS prevention, see the OWASP XSS Prevention Cheat Sheet.
SQL Injection: Do Not Build Queries with Concatenation
SQL injection typically occurs when user input is directly concatenated into a query string. This pattern may seem practical, but it allows input to alter the structure of the SQL command.
$email = $_POST['email'] ?? '';
$sql = "SELECT * FROM users WHERE email = '$email'";Use prepared statements or parameterized queries. This way, input values are separated from SQL commands, so the database does not treat them as part of the query syntax.
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->prepare(
'SELECT id, name FROM users WHERE email = :email'
);
$stmt->execute(['email' => $email]);
$user = $stmt->fetch();Prepared statements are not an excuse to skip validation. If a parameter is supposed to be a number, ensure its type is correct. For column names or ORDER BY clauses that cannot be bound as parameters, use a predefined list of options in the application instead of accepting raw text from users.
OWASP recommends parameterized queries as one of the main defenses against SQL injection.
Login Sessions Need Protection Like Passwords
After a successful login, the browser typically receives a session ID via a cookie. Anyone who obtains that session ID can potentially use the victim's session without knowing their password. Therefore, session security is as important as securing the login process.
Some basic settings worth implementing include:
- Use HTTPS so that cookies are not easily intercepted in transit.
- Enable HttpOnly so that JavaScript in the browser cannot read session cookies.
- Enable Secure so that cookies are only sent over HTTPS connections.
- Use SameSite to reduce the risk of cross-site cookie transmission.
- Regenerate session ID after login or privilege changes to prevent session fixation.
- Implement expiration and proper logout processes.
session_set_cookie_params([
'secure' => true,
'httponly' => true,
'samesite' => 'Lax'
]);
session_start();
// After credentials are successfully verified:
session_regenerate_id(true);
$_SESSION['user_id'] = $user['id'];PHP also provides settings like session.use_strict_mode to reject session IDs that were not created by the server. Details on session management can be referenced in the PHP Session Security Management Manual and the OWASP Session Management Guide.
Passwords Should Not Be Encrypted and Then Decrypted
User passwords should not be stored in plain text. For login, the application should only store password hashes—a one-way result designed to be irreversible back to the original password.
Use built-in PHP functions instead of creating your own algorithms:
$hash = password_hash($password, PASSWORD_DEFAULT);
if (password_verify($passwordInput, $hash)) {
// Password matches
}The password_hash() function is designed to create password hashes using algorithms that are compatible with PHP support. Avoid MD5 or SHA-1 for new passwords. Fast hashes are useful for file integrity, but they are less suitable for slowing down password attempts.
Error Messages Should Not Leak Internal Details
Overly detailed error messages often help developers, but they can also assist attackers. Table names, file locations, SQL queries, library versions, and accidentally printed credentials should not appear on public pages.
In production environments, turn off error display to the browser and direct logging to logs that can only be accessed by authorized personnel. During debugging, use a development environment or temporary logs that are then deleted.
Differentiate messages for users and internal notes. Users should receive messages like “An error occurred, please try again,” while technical details are kept in logs along with timestamps, endpoints, and request IDs.
What You Can Do Now
- Search for all direct uses of
$_GET,$_POST, and$_COOKIEin the application code. - Replace SQL queries built with concatenation with prepared statements.
- Check every output coming from users and add escaping according to its context.
- Audit session cookie configurations: HTTPS,
HttpOnly,Secure, andSameSite. - Ensure passwords are created with
password_hash()and verified withpassword_verify(). - Test error pages in production mode to ensure sensitive details do not appear.
- Add simple security testing to login forms, search, comments, and API endpoints.
Application security is not the result of a single plugin or a single line of configuration. It is formed from small decisions repeated at every endpoint. When input is always treated as untrusted, queries are separated from data, output is escaped, and sessions are managed with discipline, many common vulnerabilities can be closed before they become incidents.
Sources & Further Reading
- OWASP Cross Site Scripting Prevention Cheat Sheet
- OWASP SQL Injection Prevention Cheat Sheet
- OWASP Session Management Cheat Sheet
- PHP Manual: Session Security Management
- PHP Manual: password_hash
– Rio Yotto @rioyotto
