What is the OWASP Top 10?
The Open Web Application Security Project (OWASP) is a non-profit foundation dedicated to improving software security. Their flagship project, the OWASP Top 10, is a standard awareness document for developers and web application security professionals. It represents a broad consensus about the most critical security risks to web applications.
It's not a complete list of all possible vulnerabilities, but it's the perfect place to start. Understanding and mitigating these risks will significantly improve your application's security posture. Let's dive into a few of the most critical ones with practical examples.
A01: Broken Access Control
Access control, or authorization, is how you ensure users can only act within their intended permissions. Broken Access Control happens when these restrictions are not properly enforced.
The Vulnerability: Insecure Direct Object References (IDOR) This is a common form of this vulnerability. An application uses user-supplied input to access objects directly, without checking if the logged-in user has permission to access that specific object.
- Vulnerable Code (Node.js/Express): Imagine an endpoint to view a user's private profile.
JavaScript
// VULNERABLE: Anyone can view any user's profile if they know the user's ID.
app.get('/users/:userId/profile', async (req, res) => {
// The code gets the user ID from the URL...
const { userId } = req.params;
// ... and immediately fetches that user's data from the database.
// It NEVER checks if the person making the request is allowed to see this profile!
const userProfile = await db.users.find({ id: userId });
res.json(userProfile);
});
An attacker logged in as user 456 could simply change the URL to /users/123/profile to view the private profile of user 123.
- The Fix: Perform Checks! The fix is to always verify that the authenticated user has the rights to perform the requested action on the requested resource.
JavaScript
// FIXED: The endpoint now checks if the requested ID matches the logged-in user's ID.
app.get('/users/:userId/profile', async (req, res) => {
const { userId } = req.params;
const loggedInUserId = req.session.userId; // Assuming user ID is stored in the session
// THE FIX: Check for ownership/permission
if (userId !== loggedInUserId) {
return res.status(403).json({ error: 'Forbidden' }); // 403 Forbidden
}
const userProfile = await db.users.find({ id: userId });
res.json(userProfile);
});
A03: Injection
Injection flaws, particularly SQL Injection (SQLi), occur when untrusted data is sent to an interpreter as part of a command or query. The attacker's hostile data can trick the interpreter into executing unintended commands.
The Vulnerability: Dynamic Queries via String Concatenation This happens when you build SQL queries by simply stitching strings together with user input.
- Vulnerable Code (Node.js):
JavaScript
// VULNERABLE: Using string concatenation to build a query
app.get('/products/:productId', async (req, res) => {
const { productId } = req.params; // e.g., productId is '105'
// The developer builds the query by adding the input directly into the string.
const query = `SELECT * FROM products WHERE id = ${productId}`;
// What if an attacker sends a productId of '105 OR 1=1'?
// The final query becomes: SELECT * FROM products WHERE id = 105 OR 1=1
// This would return ALL products in the table!
const products = await db.query(query);
res.json(products);
});
- The Fix: Parameterized Queries (Prepared Statements) This is the universal defense against SQL Injection. You use placeholders (like ? or $1) in your SQL query and then provide the user input as separate parameters. The database engine treats the parameters as pure data, never as executable code.
JavaScript
// FIXED: Using parameterized queries
app.get('/products/:productId', async (req, res) => {
const { productId } = req.params;
// THE FIX: The query string uses a placeholder ($1)
const query = 'SELECT * FROM products WHERE id = $1';
// The user input is passed as a separate array of parameters
const values = [productId];
// The database driver safely combines the query and values, preventing injection.
const products = await db.query(query, values);
res.json(products);
});
A05: Security Misconfiguration
This is a broad category that covers insecure default configurations, verbose error messages that leak information, and not hardening your application stack.
- The Vulnerability: A common example is leaving detailed error messages enabled in a production environment. If the database connection fails, the user might see a full stack trace revealing database versions, table names, and file paths on your server.
- The Fix:
- Use Production Environments: Set your environment variable NODE_ENV to production. Frameworks like Express use this to turn off verbose errors.
- Use Security Middleware: Use libraries like helmet for Express, which automatically set various secure HTTP headers to protect against common attacks like clickjacking and cross-site scripting.
JavaScript
import express from 'express'; import helmet from 'helmet'; const app = express(); app.use(helmet()); // Sets many secure headers by default! // ... rest of your app setup