The OWASP Top 10 is the standard reference for web application security risks. It lists the ten most critical vulnerabilities that affect web applications, from broken access control to server-side request forgery. As developers, we are the first line of defense. Here is a practical breakdown of each vulnerability and how to guard against it.
A01: Broken Access Control
Broken access control is the number one risk for a reason. It happens when users can act outside their intended permissions: viewing another user's data, modifying records they should not, or escalating privileges.
**How to prevent it:** - Deny access by default. Only grant permissions explicitly. - Never rely on client-side checks alone. Always validate on the server. - Use role-based access control (RBAC) and enforce it at the API layer.
// Bad: Trusting the client-supplied user ID
app.get("/api/orders/:userId", (req, res) => {
const orders = db.getOrders(req.params.userId);
return res.json(orders);
});
// Good: Use the authenticated session's user ID
app.get("/api/orders", requireAuth, (req, res) => {
const orders = db.getOrders(req.user.id); // from JWT/session
return res.json(orders);
});
A02: Cryptographic Failures
Formerly called Sensitive Data Exposure, this covers failures related to cryptography that lead to exposure of sensitive data: passwords stored in plain text, data transmitted over HTTP, or weak hashing algorithms.
**How to prevent it:** - Always use HTTPS. No exceptions. - Hash passwords with bcrypt, scrypt, or Argon2. Never MD5 or SHA-1. - Encrypt sensitive data at rest using strong algorithms (AES-256). - Do not store sensitive data you do not need.
A03: Injection
SQL injection, NoSQL injection, command injection, LDAP injection. Any time user input is sent to an interpreter as part of a command or query without proper sanitization, you have an injection vulnerability.
**How to prevent it:** - Use parameterized queries or prepared statements. Always. - Validate and sanitize all user input. - Use ORMs, but understand their limitations.
// Bad: String concatenation in SQL (injection risk)
const query = `SELECT * FROM users WHERE email = '${email}'`;
// Good: Parameterised query
const query = "SELECT * FROM users WHERE email = $1";
const result = await db.query(query, [email]);
// Good: Using an ORM with built-in protection
const user = await prisma.user.findUnique({
where: { email },
});
A04: Insecure Design
Insecure design is about fundamental design flaws, not implementation bugs. No amount of perfect code can fix a flawed design. Examples include missing rate limiting on authentication, not considering abuse scenarios, or lacking multi-factor authentication for sensitive operations.
**How to prevent it:** - Threat model your application before building. - Use secure design patterns (defense in depth, least privilege). - Consider what an attacker would do at every user-facing feature.
A05: Security Misconfiguration
Default credentials, unnecessary features enabled, overly permissive CORS, verbose error messages in production, missing security headers. These are all configuration issues that attackers exploit.
**How to prevent it:** - Automate your security configuration. Infrastructure as code helps. - Disable default accounts and unused features. - Set proper security headers: Content-Security-Policy, X-Frame-Options, Strict-Transport-Security.
// Express security headers with helmet
import helmet from "helmet";
app.use(helmet());
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
},
}));
// Disable X-Powered-By header
app.disable("x-powered-by");
A06: Vulnerable and Outdated Components
Using libraries with known vulnerabilities is like leaving your front door unlocked. The Log4Shell vulnerability (CVE-2021-44228) demonstrated how a single outdated dependency can compromise entire systems.
**How to prevent it:** - Run `npm audit` regularly (or your package manager's equivalent). - Use tools like Dependabot or Snyk for automated vulnerability scanning. - Remove unused dependencies. Every dependency is an attack surface.
A07: Identification and Authentication Failures
Weak passwords, missing brute-force protection, session tokens in URLs, improper session invalidation. Authentication is difficult to implement correctly.
**How to prevent it:** - Implement multi-factor authentication. - Never ship with default credentials. - Rate-limit login attempts. - Invalidate sessions properly on logout. - Use established authentication libraries instead of building your own.
A08: Software and Data Integrity Failures
This covers assumptions about software updates, critical data, and CI/CD pipelines without verifying integrity. Think supply chain attacks, unsigned updates, or deserializing untrusted data.
**How to prevent it:** - Verify digital signatures on software and updates. - Use lock files (package-lock.json, yarn.lock) and review dependency changes. - Do not deserialize untrusted data without validation.
A09: Security Logging and Monitoring Failures
If you cannot detect a breach, you cannot respond to it. Many organizations discover breaches months after they happen because they lack proper logging and alerting.
**How to prevent it:** - Log all authentication events (successes and failures). - Log access control failures. - Ensure logs are not vulnerable to injection themselves. - Set up alerts for suspicious patterns (multiple failed logins, unusual access patterns).
A10: Server-Side Request Forgery (SSRF)
SSRF occurs when a web application fetches a remote resource without validating the user-supplied URL. Attackers can use this to access internal services, read cloud metadata, or scan internal networks.
**How to prevent it:** - Validate and sanitize all user-supplied URLs. - Use allowlists for permitted domains. - Block requests to internal IP ranges (127.0.0.1, 10.x, 169.254.x). - Do not expose raw responses from server-side requests to users.
Key Takeaways
- The OWASP Top 10 is the standard reference for the most critical web security risks - Broken access control is the number one vulnerability - always validate permissions server-side - Use parameterized queries to prevent SQL injection, never string concatenation - Hash passwords with bcrypt, scrypt, or Argon2 and always use HTTPS - Security is not a feature you add at the end - it is a mindset for every line of code - Automate security scanning with tools like npm audit, Dependabot, or Snyk - Log authentication events and set up alerts for suspicious patterns