Securing Your Web Application: OWASP Top 10 Guide
Security vulnerabilities can devastate your application and users. This guide covers the OWASP Top 10 risks and how to prevent them.
1. Broken Access Control
Users accessing resources or actions they shouldn't.
Vulnerabilities
javascript// Bad: Direct object reference without authorization app.get('/api/users/:id/profile', (req, res) => { const profile = db.getProfile(req.params.id); res.json(profile); // Any user can view any profile! }); // Bad: Relying on hidden fields <input type="hidden" name="userId" value="123">
Prevention
javascript// Good: Check authorization app.get('/api/users/:id/profile', authenticate, (req, res) => { // Verify user can access this resource if (req.user.id !== req.params.id && !req.user.isAdmin) { return res.status(403).json({ error: 'Forbidden' }); } const profile = db.getProfile(req.params.id); res.json(profile); }); // Good: Use indirect references app.get('/api/my/orders', authenticate, (req, res) => { // Only return user's own orders const orders = db.getOrdersByUser(req.user.id); res.json(orders); });
2. Cryptographic Failures
Weak or missing encryption of sensitive data.
Vulnerabilities
- Storing passwords in plain text
- Using weak algorithms (MD5, SHA1)
- Transmitting data over HTTP
- Hardcoded encryption keys
Prevention
javascript// Password hashing with bcrypt const bcrypt = require('bcrypt'); const SALT_ROUNDS = 12; async function hashPassword(password) { return bcrypt.hash(password, SALT_ROUNDS); } async function verifyPassword(password, hash) { return bcrypt.compare(password, hash); } // Encryption for sensitive data const crypto = require('crypto'); function encrypt(text, key) { const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv('aes-256-gcm', key, iv); const encrypted = Buffer.concat([cipher.update(text), cipher.final()]); const tag = cipher.getAuthTag(); return { iv, encrypted, tag }; }
3. Injection
Untrusted data sent to an interpreter as part of a command.
SQL Injection
javascript// Vulnerable const query = `SELECT * FROM users WHERE email = '${email}'`; // Attacker input: ' OR '1'='1 // Results in: SELECT * FROM users WHERE email = '' OR '1'='1' // Safe: Parameterized queries const query = 'SELECT * FROM users WHERE email = $1'; const result = await db.query(query, [email]); // Safe: ORMs const user = await User.findOne({ where: { email } });
NoSQL Injection
javascript// Vulnerable const user = await User.findOne({ email: req.body.email }); // Attacker input: { "$gt": "" } // Matches all users! // Safe: Validate input types const email = String(req.body.email); const user = await User.findOne({ email });
Command Injection
javascript// Vulnerable const output = execSync(`ls ${userInput}`); // Attacker input: "; rm -rf /" // Safe: Don't use shell commands with user input // Or escape properly const { execFile } = require('child_process'); execFile('ls', [userInput], callback);
4. Insecure Design
Missing or ineffective security controls.
Prevention
- Threat modeling during design
- Security requirements from the start
- Defense in depth
- Rate limiting
- Input validation
javascript// Rate limiting const rateLimit = require('express-rate-limit'); app.use('/api/login', rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 5, // 5 attempts message: 'Too many login attempts' })); // Account lockout async function handleLogin(email, password) { const user = await User.findByEmail(email); if (user.lockoutUntil > Date.now()) { throw new Error('Account locked. Try again later.'); } if (!await verifyPassword(password, user.passwordHash)) { user.failedAttempts += 1; if (user.failedAttempts >= 5) { user.lockoutUntil = Date.now() + 30 * 60 * 1000; } await user.save(); throw new Error('Invalid credentials'); } user.failedAttempts = 0; await user.save(); }
5. Security Misconfiguration
Insecure default configurations, incomplete setups.
Prevention
javascript// Security headers with Helmet const helmet = require('helmet'); app.use(helmet()); // Manual security headers app.use((req, res, next) => { res.setHeader('X-Content-Type-Options', 'nosniff'); res.setHeader('X-Frame-Options', 'DENY'); res.setHeader('X-XSS-Protection', '1; mode=block'); res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains'); res.setHeader('Content-Security-Policy', "default-src 'self'"); next(); }); // Disable version disclosure app.disable('x-powered-by'); // Secure cookies app.use(session({ cookie: { secure: true, // HTTPS only httpOnly: true, // No JavaScript access sameSite: 'strict' // CSRF protection } }));
6. Vulnerable and Outdated Components
Using libraries with known vulnerabilities.
Prevention
bash# Audit dependencies npm audit npm audit fix # Use tools like Snyk npx snyk test # Keep dependencies updated npx npm-check-updates -u
json// package.json - Use exact versions or ranges { "dependencies": { "express": "^4.18.0", "lodash": "~4.17.21" } }
7. Identification and Authentication Failures
Weak authentication mechanisms.
Prevention
javascript// Password requirements function validatePassword(password) { const minLength = 12; const hasUppercase = /[A-Z]/.test(password); const hasLowercase = /[a-z]/.test(password); const hasNumber = /[0-9]/.test(password); const hasSpecial = /[^A-Za-z0-9]/.test(password); return password.length >= minLength && hasUppercase && hasLowercase && hasNumber && hasSpecial; } // Secure session management const session = require('express-session'); const RedisStore = require('connect-redis')(session); app.use(session({ store: new RedisStore({ client: redisClient }), secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false, cookie: { secure: true, httpOnly: true, maxAge: 3600000 // 1 hour } })); // MFA implementation const speakeasy = require('speakeasy'); function verifyTOTP(secret, token) { return speakeasy.totp.verify({ secret: secret, encoding: 'base32', token: token, window: 1 }); }
8. Software and Data Integrity Failures
Not verifying integrity of software updates or data.
Prevention
javascript// Subresource Integrity for CDN scripts <script src="https://cdn.example.com/lib.js" integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC" crossorigin="anonymous"> </script> // Verify webhook signatures function verifyWebhookSignature(payload, signature, secret) { const expected = crypto .createHmac('sha256', secret) .update(payload) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) ); }
9. Security Logging and Monitoring Failures
Not detecting breaches or attacks.
Prevention
javascript// Structured logging const winston = require('winston'); const securityLogger = winston.createLogger({ level: 'info', format: winston.format.json(), transports: [ new winston.transports.File({ filename: 'security.log' }) ] }); // Log security events function logSecurityEvent(event, req, details = {}) { securityLogger.info({ event, timestamp: new Date().toISOString(), ip: req.ip, userAgent: req.headers['user-agent'], userId: req.user?.id, ...details }); } // Usage app.post('/login', (req, res) => { try { // ... login logic logSecurityEvent('LOGIN_SUCCESS', req, { email: req.body.email }); } catch (error) { logSecurityEvent('LOGIN_FAILURE', req, { email: req.body.email, reason: error.message }); } });
10. Server-Side Request Forgery (SSRF)
Application fetches remote resources based on user input.
Prevention
javascript// Vulnerable app.get('/fetch', async (req, res) => { const response = await fetch(req.query.url); res.send(await response.text()); }); // Attacker: ?url=http://169.254.169.254/latest/meta-data/ // Safe: Allowlist approach const ALLOWED_HOSTS = ['api.example.com', 'cdn.example.com']; app.get('/fetch', async (req, res) => { const url = new URL(req.query.url); if (!ALLOWED_HOSTS.includes(url.hostname)) { return res.status(400).json({ error: 'Host not allowed' }); } const response = await fetch(req.query.url); res.send(await response.text()); });
Conclusion
Security requires constant vigilance. Implement defense in depth, validate all input, use parameterized queries, keep dependencies updated, and log security events. Regular security audits and penetration testing help identify vulnerabilities before attackers do.