Web application security is a critical element that developers should consider from the start. The Top 10 published by OWASP (Open Web Application Security Project) is a list of the most common and dangerous vulnerabilities that all developers should understand. This article explains each OWASP Top 10 vulnerability and practical countermeasures.
OWASP Top 10 2021
Vulnerability List
| Rank | Category | Description |
|---|---|---|
| A01 | Broken Access Control | Missing or inadequate authorization checks |
| A02 | Cryptographic Failures | Inadequate protection of sensitive data |
| A03 | Injection | SQL, XSS, command injection |
| A04 | Insecure Design | Design without security consideration |
| A05 | Security Misconfiguration | Default settings, unnecessary features |
| A06 | Vulnerable Components | Libraries with known vulnerabilities |
| A07 | Identification and Authentication Failures | Authentication mechanism deficiencies |
| A08 | Software and Data Integrity Failures | Insecure updates, CI/CD |
| A09 | Security Logging and Monitoring Failures | Failure to detect attacks |
| A10 | SSRF | Server-Side Request Forgery |
A03: Injection Attacks
SQL Injection
// Vulnerable code
async function getUser(userId: string) {
const query = `SELECT * FROM users WHERE id = '${userId}'`;
return db.query(query);
}
// userId = "1' OR '1'='1" → All users are retrieved
// Safe code (parameterized query)
async function getUser(userId: string) {
const query = 'SELECT * FROM users WHERE id = $1';
return db.query(query, [userId]);
}
// Using ORM (Prisma)
async function getUser(userId: string) {
return prisma.user.findUnique({
where: { id: userId }
});
}
XSS (Cross-Site Scripting)
// Vulnerable code
function renderComment(comment: string) {
document.getElementById('comments').innerHTML = comment;
}
// comment = "<script>alert('XSS')</script>" → Script executes
// Safe code (escape)
function escapeHtml(text: string): string {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function renderComment(comment: string) {
const escaped = escapeHtml(comment);
document.getElementById('comments').innerHTML = escaped;
}
// Safer: use textContent
function renderComment(comment: string) {
document.getElementById('comments').textContent = comment;
}
// React/Vue automatically escape
function CommentList({ comments }: { comments: string[] }) {
return (
<ul>
{comments.map((c, i) => <li key={i}>{c}</li>)}
</ul>
);
}
Content Security Policy (CSP)
// CSP configuration in Express.js
import helmet from 'helmet';
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'strict-dynamic'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'", "https://api.example.com"],
fontSrc: ["'self'", "https://fonts.gstatic.com"],
objectSrc: ["'none'"],
baseUri: ["'self'"],
frameAncestors: ["'none'"],
upgradeInsecureRequests: [],
},
}));
A01: Broken Access Control
Authorization Check Implementation
// Vulnerable code
app.get('/api/users/:id', async (req, res) => {
const user = await db.user.findUnique({ where: { id: req.params.id } });
res.json(user); // Anyone can access
});
// Safe code
app.get('/api/users/:id', authenticate, async (req, res) => {
const userId = req.params.id;
const currentUser = req.user;
// Only self or admin can access
if (userId !== currentUser.id && currentUser.role !== 'admin') {
return res.status(403).json({ error: 'Forbidden' });
}
const user = await db.user.findUnique({ where: { id: userId } });
res.json(user);
});
IDOR (Insecure Direct Object Reference) Prevention
// Vulnerable code
app.get('/api/orders/:orderId', async (req, res) => {
const order = await db.order.findUnique({
where: { id: req.params.orderId }
});
res.json(order); // Can see others' orders
});
// Safe code
app.get('/api/orders/:orderId', authenticate, async (req, res) => {
const order = await db.order.findFirst({
where: {
id: req.params.orderId,
userId: req.user.id // Owner check
}
});
if (!order) {
return res.status(404).json({ error: 'Order not found' });
}
res.json(order);
});
RBAC (Role-Based Access Control)
// Role definition
const ROLES = {
admin: ['read', 'write', 'delete', 'manage_users'],
editor: ['read', 'write'],
viewer: ['read'],
} as const;
// Authorization middleware
function authorize(...requiredPermissions: string[]) {
return (req: Request, res: Response, next: NextFunction) => {
const userRole = req.user?.role;
const userPermissions = ROLES[userRole] || [];
const hasPermission = requiredPermissions.every(
p => userPermissions.includes(p)
);
if (!hasPermission) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}
// Usage
app.delete('/api/posts/:id',
authenticate,
authorize('delete'),
deletePost
);
A07: Identification and Authentication Failures
Secure Password Handling
import bcrypt from 'bcrypt';
import crypto from 'crypto';
// Password hashing
async function hashPassword(password: string): Promise<string> {
const saltRounds = 12;
return bcrypt.hash(password, saltRounds);
}
// Password verification
async function verifyPassword(password: string, hash: string): Promise<boolean> {
return bcrypt.compare(password, hash);
}
// Password strength validation
function validatePasswordStrength(password: string): { valid: boolean; errors: string[] } {
const errors: string[] = [];
if (password.length < 12) {
errors.push('Password must be at least 12 characters');
}
if (!/[A-Z]/.test(password)) {
errors.push('Must include uppercase letters');
}
if (!/[a-z]/.test(password)) {
errors.push('Must include lowercase letters');
}
if (!/[0-9]/.test(password)) {
errors.push('Must include numbers');
}
if (!/[!@#$%^&*]/.test(password)) {
errors.push('Must include special characters');
}
return { valid: errors.length === 0, errors };
}
Brute Force Prevention
import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
// Rate limiting (login endpoint)
const loginLimiter = rateLimit({
store: new RedisStore({
client: redisClient,
prefix: 'rl:login:'
}),
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // Max 5 attempts
skipSuccessfulRequests: true, // Don't count successful requests
message: {
error: 'Too many login attempts. Please try again in 15 minutes.'
}
});
app.post('/api/auth/login', loginLimiter, loginHandler);
// Account lock feature
async function checkAccountLock(userId: string): Promise<boolean> {
const lockKey = `lock:${userId}`;
const failKey = `fail:${userId}`;
const lockUntil = await redis.get(lockKey);
if (lockUntil && Date.now() < parseInt(lockUntil)) {
return true; // Locked
}
return false;
}
async function recordFailedAttempt(userId: string): Promise<void> {
const failKey = `fail:${userId}`;
const lockKey = `lock:${userId}`;
const attempts = await redis.incr(failKey);
await redis.expire(failKey, 3600); // Reset after 1 hour
if (attempts >= 5) {
// Lock for 30 minutes
await redis.set(lockKey, Date.now() + 30 * 60 * 1000);
await redis.expire(lockKey, 1800);
}
}
CSRF (Cross-Site Request Forgery) Prevention
CSRF Token
import csrf from 'csurf';
// CSRF middleware
const csrfProtection = csrf({
cookie: {
httpOnly: true,
secure: true,
sameSite: 'strict'
}
});
// Endpoint to get token
app.get('/api/csrf-token', csrfProtection, (req, res) => {
res.json({ csrfToken: req.csrfToken() });
});
// Protected endpoint
app.post('/api/transfer', csrfProtection, (req, res) => {
// CSRF token is automatically verified
// ...
});
// Frontend usage
async function makeRequest(url: string, data: object) {
// Get CSRF token
const { csrfToken } = await fetch('/api/csrf-token').then(r => r.json());
return fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken,
},
credentials: 'include',
body: JSON.stringify(data),
});
}
Security Headers
Recommended Header Configuration
import helmet from 'helmet';
app.use(helmet());
// Individual settings
app.use(helmet.frameguard({ action: 'deny' })); // Clickjacking prevention
app.use(helmet.noSniff()); // MIME type sniffing prevention
app.use(helmet.xssFilter()); // XSS filter
app.use(helmet.referrerPolicy({ policy: 'strict-origin-when-cross-origin' }));
// Custom headers
app.use((req, res, next) => {
res.setHeader('Permissions-Policy', 'geolocation=(), microphone=(), camera=()');
next();
});
Response Header Checklist
Security Header Checklist:
□ Content-Security-Policy
□ Strict-Transport-Security
□ X-Frame-Options
□ X-Content-Type-Options
□ Referrer-Policy
□ Permissions-Policy
□ X-XSS-Protection (legacy)
Summary
Web security requires continuous effort.
Development Checklist
- Input Validation: Validate and sanitize all user input
- Authentication/Authorization: Implement proper access controls
- Encryption: Encrypt sensitive data, force HTTPS
- Session Management: Secure cookie settings
- Dependencies: Regular vulnerability scanning
Operations Checklist
- Logging/Monitoring: Anomaly detection mechanisms
- Incident Response: Response plan preparation
- Regular Audits: Penetration testing
- Education: Security awareness training for developers
Security is not a “feature to add later” but an element to build in from the design stage.