OWASP Top 10: Prevention Strategies
Introduction
The OWASP Top 10 represents the most critical security risks to web applications, updated regularly to reflect the evolving threat landscape. Understanding and preventing these vulnerabilities is essential for building secure applications.
This comprehensive guide explores practical prevention strategies for each OWASP Top 10 vulnerability, providing code examples, security controls, and implementation patterns that development teams can apply immediately to strengthen their application security posture.
A01: Broken Access Control
The Vulnerability
Broken access control allows users to access resources or perform actions outside their intended permissions.
// VULNERABLE: No authorization check
app.get('/api/users/:id', async (req, res) => {
const user = await User.findById(req.params.id);
res.json(user); // Anyone can view any user
});
// VULNERABLE: Client-side access control
app.post('/api/admin/delete-user', async (req, res) => {
// Trusting client to only send request if they're admin
await User.findByIdAndDelete(req.body.userId);
res.json({ success: true });
});
Prevention Strategy
// Authorization middleware
class AccessControl {
static async authorize(req, res, next) {
const user = req.user; // From authentication middleware
if (!user) {
return res.status(401).json({ error: 'Not authenticated' });
}
// Check permissions based on resource and action
const hasAccess = await this.checkPermission(
user,
req.baseUrl,
req.method
);
if (!hasAccess) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
}
static async checkPermission(user, resource, action) {
// Role-based access control
const permissions = await Permission.find({
role: user.role,
resource,
action
});
return permissions.length > 0;
}
static async checkOwnership(userId, resourceId, resourceType) {
const resource = await resourceType.findById(resourceId);
return resource && resource.ownerId === userId;
}
}
// SECURE: Proper authorization
app.get('/api/users/:id',
authenticate,
async (req, res) => {
// Users can only view their own profile unless they're admin
if (req.user.id !== req.params.id && !req.user.isAdmin) {
return res.status(403).json({ error: 'Forbidden' });
}
const user = await User.findById(req.params.id);
res.json(user);
}
);
app.post('/api/admin/delete-user',
authenticate,
requireRole('admin'),
async (req, res) => {
await User.findByIdAndDelete(req.body.userId);
// Audit log
await AuditLog.create({
userId: req.user.id,
action: 'DELETE_USER',
targetUserId: req.body.userId,
timestamp: new Date()
});
res.json({ success: true });
}
);
// Role checking middleware
function requireRole(...roles) {
return (req, res, next) => {
if (!req.user || !roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}
A02: Cryptographic Failures
The Vulnerability
Sensitive data exposure due to weak or missing encryption:
// VULNERABLE: Storing passwords in plaintext
await User.create({
email: 'user@example.com',
password: 'password123' // NEVER do this
});
// VULNERABLE: Weak encryption
const crypto = require('crypto');
const encrypted = crypto.createCipher('aes128', 'weak-key')
.update(data, 'utf8', 'hex');
Prevention Strategy
const bcrypt = require('bcrypt');
const crypto = require('crypto');
class CryptoService {
// Password hashing
static async hashPassword(password) {
const saltRounds = 12;
return await bcrypt.hash(password, saltRounds);
}
static async verifyPassword(password, hash) {
return await bcrypt.compare(password, hash);
}
// Symmetric encryption for data at rest
static encrypt(plaintext) {
const algorithm = 'aes-256-gcm';
const key = Buffer.from(process.env.ENCRYPTION_KEY, 'hex');
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(algorithm, key, iv);
let encrypted = cipher.update(plaintext, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
return {
encrypted,
iv: iv.toString('hex'),
authTag: authTag.toString('hex')
};
}
static decrypt(encryptedData) {
const algorithm = 'aes-256-gcm';
const key = Buffer.from(process.env.ENCRYPTION_KEY, 'hex');
const iv = Buffer.from(encryptedData.iv, 'hex');
const authTag = Buffer.from(encryptedData.authTag, 'hex');
const decipher = crypto.createDecipheriv(algorithm, key, iv);
decipher.setAuthTag(authTag);
let decrypted = decipher.update(encryptedData.encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
// Generate secure random tokens
static generateToken(length = 32) {
return crypto.randomBytes(length).toString('hex');
}
}
// User model with proper password handling
class User {
async setPassword(password) {
// Validate password strength
if (!this.isPasswordStrong(password)) {
throw new Error('Password does not meet requirements');
}
this.passwordHash = await CryptoService.hashPassword(password);
}
async verifyPassword(password) {
return await CryptoService.verifyPassword(password, this.passwordHash);
}
isPasswordStrong(password) {
const minLength = 12;
const hasUpperCase = /[A-Z]/.test(password);
const hasLowerCase = /[a-z]/.test(password);
const hasNumbers = /\d/.test(password);
const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password);
return password.length >= minLength &&
hasUpperCase &&
hasLowerCase &&
hasNumbers &&
hasSpecialChar;
}
}
// Storing sensitive data encrypted
async function storeSensitiveData(userId, creditCard) {
const encrypted = CryptoService.encrypt(creditCard);
await UserData.create({
userId,
creditCard: encrypted.encrypted,
creditCardIv: encrypted.iv,
creditCardAuthTag: encrypted.authTag
});
}
A03: Injection
The Vulnerability
SQL, NoSQL, OS command, and LDAP injection attacks:
// VULNERABLE: SQL Injection
app.get('/users', async (req, res) => {
const query = `SELECT * FROM users WHERE email = '${req.query.email}'`;
// Attacker can send: ' OR '1'='1
const users = await db.query(query);
res.json(users);
});
// VULNERABLE: NoSQL Injection
app.post('/login', async (req, res) => {
const user = await User.findOne({
email: req.body.email,
password: req.body.password // Can receive: { $ne: null }
});
});
// VULNERABLE: Command Injection
app.post('/convert', async (req, res) => {
exec(`convert ${req.body.filename} output.pdf`); // RCE risk
});
Prevention Strategy
// SQL: Use parameterized queries
app.get('/users', async (req, res) => {
const query = 'SELECT * FROM users WHERE email = ?';
const users = await db.query(query, [req.query.email]);
res.json(users);
});
// NoSQL: Use schema validation and sanitization
const mongoose = require('mongoose');
const mongoSanitize = require('express-mongo-sanitize');
app.use(mongoSanitize()); // Remove $ and . from user input
const UserSchema = new mongoose.Schema({
email: { type: String, required: true },
password: { type: String, required: true }
});
app.post('/login', async (req, res) => {
// Input validation
const { error, value } = Joi.object({
email: Joi.string().email().required(),
password: Joi.string().required()
}).validate(req.body);
if (error) {
return res.status(400).json({ error: error.details[0].message });
}
const user = await User.findOne({ email: value.email });
if (!user || !await user.verifyPassword(value.password)) {
return res.status(401).json({ error: 'Invalid credentials' });
}
res.json({ token: generateToken(user) });
});
// Command Injection: Avoid exec, use safe alternatives
const path = require('path');
const { execFile } = require('child_process');
app.post('/convert', async (req, res) => {
// Validate and sanitize filename
const filename = path.basename(req.body.filename);
const allowedExtensions = ['.jpg', '.png', '.gif'];
if (!allowedExtensions.includes(path.extname(filename))) {
return res.status(400).json({ error: 'Invalid file type' });
}
// Use execFile with array arguments (no shell interpretation)
execFile('convert', [filename, 'output.pdf'], (error, stdout, stderr) => {
if (error) {
return res.status(500).json({ error: 'Conversion failed' });
}
res.json({ success: true });
});
});
// Input validation middleware
const Joi = require('joi');
function validateInput(schema) {
return (req, res, next) => {
const { error, value } = schema.validate(req.body);
if (error) {
return res.status(400).json({
error: 'Validation failed',
details: error.details.map(d => d.message)
});
}
req.validatedBody = value;
next();
};
}
// Usage
const createUserSchema = Joi.object({
email: Joi.string().email().required(),
name: Joi.string().min(2).max(50).required(),
age: Joi.number().integer().min(18).max(120).optional()
});
app.post('/users',
validateInput(createUserSchema),
async (req, res) => {
const user = await User.create(req.validatedBody);
res.json(user);
}
);
A04: Insecure Design
The Vulnerability
Missing or ineffective security controls in the design phase:
// VULNERABLE: No rate limiting
app.post('/login', async (req, res) => {
// Attacker can brute force passwords
const user = await authenticate(req.body);
res.json(user);
});
// VULNERABLE: No account lockout
app.post('/reset-password', async (req, res) => {
// Attacker can enumerate valid emails
const user = await User.findOne({ email: req.body.email });
if (user) {
await sendResetEmail(user);
}
res.json({ message: 'If account exists, email sent' });
});
Prevention Strategy
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const redis = require('redis');
// Rate limiting configuration
const createRateLimiter = (options) => {
return rateLimit({
store: new RedisStore({
client: redis.createClient(),
prefix: 'rl:'
}),
windowMs: options.windowMs || 15 * 60 * 1000,
max: options.max || 5,
message: options.message || 'Too many requests',
standardHeaders: true,
legacyHeaders: false,
handler: (req, res) => {
res.status(429).json({
error: 'Too many requests',
retryAfter: Math.ceil(options.windowMs / 1000)
});
}
});
};
// Progressive delays for failed attempts
class LoginAttemptTracker {
constructor() {
this.attempts = new Map();
}
async recordAttempt(identifier, success) {
const key = `login:${identifier}`;
const attempts = await redis.get(key) || 0;
if (success) {
await redis.del(key);
return { allowed: true };
}
const newAttempts = parseInt(attempts) + 1;
await redis.setEx(key, 3600, newAttempts); // 1 hour expiry
// Progressive delays
const delays = [0, 1000, 5000, 15000, 60000]; // milliseconds
const delay = delays[Math.min(newAttempts - 1, delays.length - 1)];
if (newAttempts >= 5) {
return { allowed: false, lockoutTime: 3600 };
}
return { allowed: true, delay };
}
async isLocked(identifier) {
const attempts = await redis.get(`login:${identifier}`);
return attempts && parseInt(attempts) >= 5;
}
}
// Secure login with rate limiting and account lockout
const loginLimiter = createRateLimiter({
windowMs: 15 * 60 * 1000,
max: 5,
message: 'Too many login attempts'
});
app.post('/login',
loginLimiter,
async (req, res) => {
const tracker = new LoginAttemptTracker();
const identifier = req.body.email;
// Check if account is locked
if (await tracker.isLocked(identifier)) {
return res.status(429).json({
error: 'Account temporarily locked',
retryAfter: 3600
});
}
const user = await User.findOne({ email: identifier });
if (!user || !await user.verifyPassword(req.body.password)) {
const attempt = await tracker.recordAttempt(identifier, false);
if (!attempt.allowed) {
return res.status(429).json({
error: 'Account locked due to too many failed attempts',
retryAfter: attempt.lockoutTime
});
}
// Add artificial delay
if (attempt.delay) {
await new Promise(resolve => setTimeout(resolve, attempt.delay));
}
return res.status(401).json({ error: 'Invalid credentials' });
}
await tracker.recordAttempt(identifier, true);
const token = generateToken(user);
res.json({ token });
}
);
// Secure password reset
app.post('/reset-password',
createRateLimiter({ windowMs: 60 * 60 * 1000, max: 3 }),
async (req, res) => {
const { email } = req.body;
// Always return same message (prevent email enumeration)
const message = 'If an account exists, a reset link has been sent';
try {
const user = await User.findOne({ email });
if (user) {
const token = CryptoService.generateToken();
await PasswordReset.create({
userId: user.id,
token,
expiresAt: Date.now() + (60 * 60 * 1000) // 1 hour
});
await sendResetEmail(user.email, token);
}
// Add delay even if user doesn't exist (prevent timing attacks)
await new Promise(resolve => setTimeout(resolve, 1000));
res.json({ message });
} catch (error) {
res.json({ message }); // Still return success message
}
}
);
A05: Security Misconfiguration
The Vulnerability
Default configurations, unnecessary features, verbose errors:
// VULNERABLE: Exposing detailed errors
app.use((err, req, res, next) => {
res.status(500).json({
error: err.message,
stack: err.stack, // Exposes sensitive information
query: req.query,
body: req.body
});
});
// VULNERABLE: CORS misconfiguration
app.use(cors({
origin: '*', // Allows any origin
credentials: true
}));
Prevention Strategy
const helmet = require('helmet');
const hpp = require('hpp');
// Security headers
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", 'data:', 'https:'],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"]
}
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
}
}));
// Prevent parameter pollution
app.use(hpp());
// Proper CORS configuration
const allowedOrigins = process.env.ALLOWED_ORIGINS.split(',');
app.use(cors({
origin: (origin, callback) => {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
// Proper error handling
class AppError extends Error {
constructor(message, statusCode, isOperational = true) {
super(message);
this.statusCode = statusCode;
this.isOperational = isOperational;
Error.captureStackTrace(this, this.constructor);
}
}
// Error handler middleware
app.use((err, req, res, next) => {
const isProduction = process.env.NODE_ENV === 'production';
// Log error (use proper logging service)
logger.error({
message: err.message,
stack: err.stack,
url: req.url,
method: req.method,
ip: req.ip,
userId: req.user?.id
});
// Send appropriate response
if (err.isOperational) {
res.status(err.statusCode).json({
error: err.message
});
} else {
// Programming or unknown error
res.status(500).json({
error: isProduction
? 'Internal server error'
: err.message,
...(isProduction ? {} : { stack: err.stack })
});
}
});
// Remove powered-by header
app.disable('x-powered-by');
// Configuration validation
class ConfigValidator {
static validate() {
const required = [
'DATABASE_URL',
'JWT_SECRET',
'ENCRYPTION_KEY',
'NODE_ENV'
];
const missing = required.filter(key => !process.env[key]);
if (missing.length > 0) {
throw new Error(`Missing required environment variables: ${missing.join(', ')}`);
}
// Validate key strengths
if (process.env.JWT_SECRET.length < 32) {
throw new Error('JWT_SECRET must be at least 32 characters');
}
if (process.env.NODE_ENV === 'production') {
if (process.env.DATABASE_URL.includes('localhost')) {
throw new Error('Production database cannot be localhost');
}
}
}
}
A06: Vulnerable and Outdated Components
Prevention Strategy
// package.json - Keep dependencies updated
{
"scripts": {
"audit": "npm audit",
"audit:fix": "npm audit fix",
"update:check": "npm outdated",
"update:interactive": "npx npm-check -u"
},
"dependencies": {
// Use exact versions in production
"express": "4.18.2",
"mongoose": "7.0.3"
},
"devDependencies": {
"npm-check": "^6.0.1"
}
}
// CI/CD pipeline - Automated dependency checking
// .github/workflows/security.yml
const securityChecks = `
name: Security Checks
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
schedule:
- cron: '0 0 * * 0' # Weekly
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm ci
- run: npm audit --audit-level=moderate
- run: npx snyk test --severity-threshold=high
`;
// Dependency monitoring service
class DependencyMonitor {
async checkVulnerabilities() {
const { exec } = require('child_process');
const { promisify } = require('util');
const execAsync = promisify(exec);
try {
const { stdout } = await execAsync('npm audit --json');
const audit = JSON.parse(stdout);
const vulnerabilities = audit.metadata.vulnerabilities;
const critical = vulnerabilities.critical || 0;
const high = vulnerabilities.high || 0;
if (critical > 0 || high > 0) {
await this.alertSecurityTeam({
critical,
high,
moderate: vulnerabilities.moderate || 0,
low: vulnerabilities.low || 0
});
}
return vulnerabilities;
} catch (error) {
console.error('Dependency check failed:', error);
throw error;
}
}
async alertSecurityTeam(vulnerabilities) {
// Send alert to security team
await sendEmail({
to: 'security@company.com',
subject: 'Security Vulnerabilities Detected',
body: `
Critical: ${vulnerabilities.critical}
High: ${vulnerabilities.high}
Moderate: ${vulnerabilities.moderate}
Low: ${vulnerabilities.low}
`
});
}
}
A07: Identification and Authentication Failures
Prevention Strategy
// Secure session management
const session = require('express-session');
const RedisStore = require('connect-redis')(session);
app.use(session({
store: new RedisStore({ client: redis.createClient() }),
secret: process.env.SESSION_SECRET,
name: 'sessionId', // Don't use default name
resave: false,
saveUninitialized: false,
cookie: {
secure: true, // HTTPS only
httpOnly: true, // No JavaScript access
maxAge: 1000 * 60 * 60 * 24, // 24 hours
sameSite: 'strict', // CSRF protection
domain: 'yourdomain.com'
}
}));
// Secure password reset
class PasswordResetService {
async initiateReset(email) {
const user = await User.findOne({ email });
if (!user) {
// Don't reveal if user exists
return { message: 'Reset email sent if account exists' };
}
// Invalidate previous reset tokens
await PasswordReset.deleteMany({ userId: user.id });
// Generate secure token
const token = crypto.randomBytes(32).toString('hex');
const hashedToken = crypto
.createHash('sha256')
.update(token)
.digest('hex');
await PasswordReset.create({
userId: user.id,
token: hashedToken,
expiresAt: Date.now() + (60 * 60 * 1000), // 1 hour
used: false
});
// Send email with plain token
await this.sendResetEmail(user.email, token);
return { message: 'Reset email sent if account exists' };
}
async resetPassword(token, newPassword) {
const hashedToken = crypto
.createHash('sha256')
.update(token)
.digest('hex');
const reset = await PasswordReset.findOne({
token: hashedToken,
used: false,
expiresAt: { $gt: Date.now() }
});
if (!reset) {
throw new AppError('Invalid or expired reset token', 400);
}
const user = await User.findById(reset.userId);
await user.setPassword(newPassword);
await user.save();
// Mark token as used
reset.used = true;
await reset.save();
// Invalidate all sessions
await Session.deleteMany({ userId: user.id });
return { message: 'Password reset successful' };
}
}
A08: Software and Data Integrity Failures
Prevention Strategy
// Verify package integrity
// package-lock.json is committed and verified
// Subresource Integrity for CDN resources
const generateSRIHash = (content) => {
const hash = crypto
.createHash('sha384')
.update(content)
.digest('base64');
return `sha384-${hash}`;
};
// Code signing for deployments
class DeploymentVerifier {
async verifyDeployment(artifact, signature) {
const publicKey = fs.readFileSync('./keys/deploy-public.pem');
const verify = crypto.createVerify('SHA256');
verify.update(artifact);
verify.end();
const isValid = verify.verify(publicKey, signature, 'hex');
if (!isValid) {
throw new Error('Deployment verification failed');
}
return true;
}
}
// Secure software updates
class UpdateService {
async checkForUpdates() {
const response = await fetch('https://api.yourapp.com/updates', {
headers: {
'X-Client-Version': process.env.APP_VERSION
}
});
const update = await response.json();
// Verify signature
if (!await this.verifySignature(update)) {
throw new Error('Update signature invalid');
}
return update;
}
async verifySignature(update) {
const publicKey = await this.getPublicKey();
const verify = crypto.createVerify('SHA256');
verify.update(update.data);
verify.end();
return verify.verify(publicKey, update.signature, 'hex');
}
}
A09: Security Logging and Monitoring Failures
Prevention Strategy
const winston = require('winston');
const DailyRotateFile = require('winston-daily-rotate-file');
// Comprehensive logging setup
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: { service: 'api', version: process.env.APP_VERSION },
transports: [
new DailyRotateFile({
filename: 'logs/error-%DATE%.log',
datePattern: 'YYYY-MM-DD',
level: 'error',
maxFiles: '30d'
}),
new DailyRotateFile({
filename: 'logs/combined-%DATE%.log',
datePattern: 'YYYY-MM-DD',
maxFiles: '30d'
})
]
});
// Security event logging
class SecurityLogger {
static logAuthEvent(event, userId, success, metadata = {}) {
logger.info({
type: 'security',
category: 'authentication',
event,
userId,
success,
timestamp: new Date(),
ip: metadata.ip,
userAgent: metadata.userAgent,
...metadata
});
}
static logAccessControl(userId, resource, action, granted) {
logger.info({
type: 'security',
category: 'access_control',
userId,
resource,
action,
granted,
timestamp: new Date()
});
}
static logSecurityIncident(severity, description, metadata = {}) {
logger.warn({
type: 'security',
category: 'incident',
severity,
description,
timestamp: new Date(),
...metadata
});
if (severity === 'critical') {
this.alertSecurityTeam(description, metadata);
}
}
static async alertSecurityTeam(description, metadata) {
// Send immediate alert
await sendPagerDutyAlert({
severity: 'critical',
description,
details: metadata
});
}
}
// Monitoring middleware
function securityMonitoring(req, res, next) {
const startTime = Date.now();
// Log request
logger.info({
type: 'request',
method: req.method,
url: req.url,
ip: req.ip,
userAgent: req.get('user-agent'),
userId: req.user?.id
});
// Track response
res.on('finish', () => {
const duration = Date.now() - startTime;
logger.info({
type: 'response',
method: req.method,
url: req.url,
statusCode: res.statusCode,
duration,
userId: req.user?.id
});
// Alert on suspicious patterns
if (res.statusCode === 401 || res.statusCode === 403) {
SecurityLogger.logSecurityIncident('medium', 'Access denied', {
url: req.url,
ip: req.ip,
userId: req.user?.id
});
}
});
next();
}
app.use(securityMonitoring);
A10: Server-Side Request Forgery (SSRF)
Prevention Strategy
const axios = require('axios');
const { isIP } = require('net');
class SSRFProtection {
static isAllowedHost(url) {
const allowedHosts = process.env.ALLOWED_HOSTS.split(',');
const hostname = new URL(url).hostname;
return allowedHosts.includes(hostname);
}
static isPrivateIP(ip) {
const privateRanges = [
/^127\./, // Loopback
/^10\./, // Private
/^172\.(1[6-9]|2[0-9]|3[0-1])\./, // Private
/^192\.168\./, // Private
/^169\.254\./, // Link-local
/^::1$/, // IPv6 loopback
/^fc00:/, // IPv6 private
/^fe80:/ // IPv6 link-local
];
return privateRanges.some(range => range.test(ip));
}
static async validateURL(url) {
try {
const parsed = new URL(url);
// Only allow HTTP/HTTPS
if (!['http:', 'https:'].includes(parsed.protocol)) {
throw new Error('Invalid protocol');
}
// Check if hostname is allowed
if (!this.isAllowedHost(url)) {
throw new Error('Host not allowed');
}
// Resolve hostname to IP
const dns = require('dns').promises;
const addresses = await dns.resolve4(parsed.hostname);
// Check for private IPs
for (const ip of addresses) {
if (this.isPrivateIP(ip)) {
throw new Error('Private IP addresses not allowed');
}
}
return true;
} catch (error) {
throw new Error(`URL validation failed: ${error.message}`);
}
}
}
// Secure webhook handler
app.post('/webhook',
authenticate,
async (req, res) => {
const { url, event } = req.body;
try {
// Validate URL
await SSRFProtection.validateURL(url);
// Make request with timeout and size limits
const response = await axios.post(url, { event }, {
timeout: 5000,
maxRedirects: 0,
maxContentLength: 1024 * 1024, // 1MB
validateStatus: (status) => status < 400
});
res.json({ success: true });
} catch (error) {
logger.error('Webhook failed', { error: error.message, url });
res.status(400).json({ error: 'Webhook delivery failed' });
}
}
);
// Secure image proxy
app.get('/proxy/image',
async (req, res) => {
const { url } = req.query;
try {
await SSRFProtection.validateURL(url);
const response = await axios.get(url, {
responseType: 'stream',
timeout: 10000,
maxContentLength: 10 * 1024 * 1024, // 10MB
headers: {
'User-Agent': 'ImageProxy/1.0'
}
});
// Verify content type
const contentType = response.headers['content-type'];
if (!contentType.startsWith('image/')) {
throw new Error('Invalid content type');
}
res.set('Content-Type', contentType);
response.data.pipe(res);
} catch (error) {
res.status(400).json({ error: 'Failed to fetch image' });
}
}
);
Comprehensive Security Testing
const request = require('supertest');
describe('OWASP Top 10 Security Tests', () => {
describe('A01: Broken Access Control', () => {
it('should prevent unauthorized access to user data', async () => {
const user1 = await createUser();
const user2 = await createUser();
const response = await request(app)
.get(`/api/users/${user2.id}`)
.set('Authorization', `Bearer ${user1.token}`)
.expect(403);
});
});
describe('A03: Injection', () => {
it('should prevent SQL injection', async () => {
const response = await request(app)
.get('/api/users')
.query({ email: "' OR '1'='1" })
.expect(400);
});
it('should prevent NoSQL injection', async () => {
const response = await request(app)
.post('/login')
.send({
email: 'user@example.com',
password: { $ne: null }
})
.expect(400);
});
});
describe('A04: Insecure Design', () => {
it('should rate limit login attempts', async () => {
const attempts = Array(6).fill(null);
for (let i = 0; i < 6; i++) {
const response = await request(app)
.post('/login')
.send({ email: 'user@example.com', password: 'wrong' });
if (i < 5) {
expect(response.status).toBe(401);
} else {
expect(response.status).toBe(429);
}
}
});
});
describe('A10: SSRF', () => {
it('should reject private IP addresses', async () => {
const response = await request(app)
.post('/webhook')
.set('Authorization', `Bearer ${user.token}`)
.send({
url: 'http://127.0.0.1:8080/admin',
event: 'test'
})
.expect(400);
});
});
});
Conclusion
Preventing the OWASP Top 10 vulnerabilities requires a comprehensive approach combining secure coding practices, proper configuration, input validation, and continuous monitoring. By implementing the strategies outlined in this guide, development teams can significantly reduce their application’s attack surface.
Key takeaways:
- Implement defense in depth with multiple security layers
- Validate and sanitize all user input
- Use parameterized queries to prevent injection
- Enforce proper access controls at every level
- Keep dependencies updated and monitored
- Use strong encryption for sensitive data
- Implement comprehensive logging and monitoring
- Apply security headers and proper CORS configuration
- Rate limit sensitive operations
- Test security controls regularly
Remember that security is an ongoing process, not a one-time implementation. Regular security audits, penetration testing, and staying current with the latest OWASP guidelines are essential for maintaining a strong security posture.