Skip to main content

OWASP Top 10: Prevention Strategies

Ryan Dahlberg
Ryan Dahlberg
October 12, 2025 18 min read
Share:
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.

#Vulnerabilities #OWASP #Web Security #Application Security