OWASP Top 10 security vulnerabilities with detection and remediation patterns. Use when conducting security audits, implementing secure coding practices, or reviewing code for common security vulnerabilities.
This skill inherits all available tools. When active, it can use any tool Claude has access to.
Expert guidance for identifying, preventing, and remediating the most critical web application security risks based on OWASP Top 10 2021.
Rank by Risk Severity:
Description: Users can act outside their intended permissions, accessing unauthorized data or functions.
Common Patterns:
// VULNERABLE: Direct object reference without authorization
app.get('/api/users/:id/profile', (req, res) => {
const profile = db.getProfile(req.params.id);
res.json(profile); // No check if user can access this profile
});
// VULNERABLE: Path traversal
app.get('/files/:filename', (req, res) => {
res.sendFile(`./uploads/${req.params.filename}`);
// Attack: GET /files/../../etc/passwd
});
// VULNERABLE: Missing function-level access control
app.post('/api/admin/users', (req, res) => {
// No admin role check
createUser(req.body);
});
Secure Implementation:
// SECURE: Verify authorization for each request
app.get('/api/users/:id/profile', authenticate, (req, res) => {
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);
});
// SECURE: Sanitize file paths
const path = require('path');
app.get('/files/:filename', authenticate, (req, res) => {
const filename = path.basename(req.params.filename);
const filepath = path.join(__dirname, 'uploads', filename);
if (!filepath.startsWith(path.join(__dirname, 'uploads'))) {
return res.status(400).json({ error: 'Invalid path' });
}
res.sendFile(filepath);
});
// SECURE: Function-level access control
app.post('/api/admin/users', authenticate, requireRole('admin'), (req, res) => {
createUser(req.body);
});
Prevention Checklist:
Description: Exposing sensitive data due to missing or weak cryptography.
Common Vulnerabilities:
// VULNERABLE: Storing passwords in plaintext
db.createUser({ username, password: password });
// VULNERABLE: Weak hashing
const hash = crypto.createHash('md5').update(password).digest('hex');
// VULNERABLE: Transmitting sensitive data over HTTP
fetch('http://api.example.com/payment', {
body: JSON.stringify({ cardNumber, cvv })
});
// VULNERABLE: Hardcoded secrets
const API_KEY = 'sk_live_a3f7c9b2d8e1f4g6h9';
Secure Implementation:
// SECURE: Strong password hashing with bcrypt
const bcrypt = require('bcrypt');
const saltRounds = 12;
const hash = await bcrypt.hash(password, saltRounds);
db.createUser({ username, passwordHash: hash });
// Verification
const isValid = await bcrypt.compare(password, user.passwordHash);
// SECURE: Encrypt sensitive data at rest
const crypto = require('crypto');
const algorithm = 'aes-256-gcm';
const key = Buffer.from(process.env.ENCRYPTION_KEY, 'hex'); // 32 bytes
function encrypt(text) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(algorithm, key, iv);
const encrypted = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]);
const authTag = cipher.getAuthTag();
return { iv: iv.toString('hex'), authTag: authTag.toString('hex'), data: encrypted.toString('hex') };
}
// SECURE: Use environment variables for secrets
require('dotenv').config();
const apiKey = process.env.API_KEY;
Prevention Checklist:
Description: Untrusted data sent to interpreters as part of commands or queries.
Types: SQL, NoSQL, OS command, LDAP, XPath, template injection
SQL Injection:
// VULNERABLE: String concatenation
const query = `SELECT * FROM users WHERE username = '${username}' AND password = '${password}'`;
// Attack: username = "admin'--"
// SECURE: Parameterized queries
const query = 'SELECT * FROM users WHERE username = ? AND password = ?';
db.query(query, [username, password]);
// SECURE: ORM with parameterization
const user = await User.findOne({
where: { username, password }
});
NoSQL Injection:
// VULNERABLE: Direct object injection
db.collection('users').findOne({
username: req.body.username,
password: req.body.password
});
// Attack: { "username": "admin", "password": { "$ne": null } }
// SECURE: Type validation and sanitization
const { username, password } = req.body;
if (typeof username !== 'string' || typeof password !== 'string') {
return res.status(400).json({ error: 'Invalid input' });
}
db.collection('users').findOne({ username, password });
Command Injection:
// VULNERABLE: Shell command with user input
const { exec } = require('child_process');
exec(`ping -c 4 ${req.body.host}`, callback);
// Attack: host = "google.com; rm -rf /"
// SECURE: Use safe APIs and validation
const { spawn } = require('child_process');
const host = req.body.host;
if (!/^[a-zA-Z0-9.-]+$/.test(host)) {
return res.status(400).json({ error: 'Invalid host' });
}
const ping = spawn('ping', ['-c', '4', host]);
Prevention Checklist:
Description: Missing or ineffective security controls in design phase.
Threat Modeling Examples:
// INSECURE DESIGN: No rate limiting on sensitive endpoints
app.post('/api/login', async (req, res) => {
const user = await authenticate(req.body);
// Vulnerable to credential stuffing
});
// SECURE DESIGN: Rate limiting + CAPTCHA
const rateLimit = require('express-rate-limit');
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 attempts
skipSuccessfulRequests: true
});
app.post('/api/login', loginLimiter, async (req, res) => {
if (req.session.failedAttempts >= 3) {
const captchaValid = await verifyCaptcha(req.body.captcha);
if (!captchaValid) {
return res.status(400).json({ error: 'Invalid CAPTCHA' });
}
}
const user = await authenticate(req.body);
});
// INSECURE DESIGN: No transaction limits
app.post('/api/transfer', authenticate, async (req, res) => {
await transferFunds(req.user.id, req.body.to, req.body.amount);
});
// SECURE DESIGN: Transaction limits + verification
app.post('/api/transfer', authenticate, async (req, res) => {
const { to, amount } = req.body;
// Business logic validation
if (amount > 10000) {
const verified = await require2FA(req.user);
if (!verified) {
return res.status(403).json({ error: '2FA required for large transfers' });
}
}
// Daily limit check
const dailyTotal = await getDailyTransferTotal(req.user.id);
if (dailyTotal + amount > 50000) {
return res.status(403).json({ error: 'Daily limit exceeded' });
}
await transferFunds(req.user.id, to, amount);
});
Design Principles:
Description: Missing hardening, unnecessary features, default credentials, verbose errors.
Common Issues:
// VULNERABLE: Verbose error messages
app.use((err, req, res, next) => {
res.status(500).json({
error: err.message,
stack: err.stack // Exposes internal structure
});
});
// SECURE: Generic error messages
app.use((err, req, res, next) => {
console.error(err); // Log internally only
res.status(500).json({
error: 'Internal server error',
requestId: req.id
});
});
// VULNERABLE: Unnecessary features enabled
app.use(express.static('public', { dotfiles: 'allow' }));
// Exposes .env, .git files
// SECURE: Restrict access
app.use(express.static('public', {
dotfiles: 'deny',
index: false // Disable directory listing
}));
// VULNERABLE: Missing security headers
app.get('/', (req, res) => {
res.send('<h1>Welcome</h1>');
});
// SECURE: Security headers
const helmet = require('helmet');
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', 'https:'],
}
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
}
}));
Prevention Checklist:
Detection:
# Check for known vulnerabilities
npm audit
npm audit fix
# Use security scanning tools
npx snyk test
npx retire
# Check outdated packages
npm outdated
Prevention:
Common Weaknesses:
// VULNERABLE: Weak session management
app.post('/login', async (req, res) => {
const user = await authenticate(req.body);
req.session.userId = user.id; // Predictable session ID
req.session.cookie.secure = false; // Transmitted over HTTP
});
// SECURE: Strong session management
const session = require('express-session');
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: true, // HTTPS only
httpOnly: true, // No JavaScript access
maxAge: 3600000, // 1 hour
sameSite: 'strict'
},
name: 'sessionId', // Custom name, not default 'connect.sid'
}));
Best Practices:
Focus: Insecure CI/CD, auto-updates, unsigned objects
// VULNERABLE: Accepting unsigned packages
npm install untrusted-package
// SECURE: Verify package integrity
// Use npm audit, lock files (package-lock.json)
// Verify checksums and signatures
// VULNERABLE: Insecure deserialization
const userData = JSON.parse(req.cookies.user);
eval(userData.code); // Arbitrary code execution
// SECURE: Avoid deserialization of untrusted data
const userData = JSON.parse(req.cookies.user);
// Validate schema, never execute code from untrusted sources
Prevention:
Effective Logging:
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'security.log' })
]
});
// Log security events
app.post('/api/login', async (req, res) => {
try {
const user = await authenticate(req.body);
logger.info('Login success', {
userId: user.id,
ip: req.ip,
timestamp: new Date()
});
} catch (err) {
logger.warn('Login failed', {
username: req.body.username,
ip: req.ip,
timestamp: new Date()
});
}
});
Critical Events to Log:
Prevention:
Vulnerability:
// VULNERABLE: Fetching user-supplied URLs
app.get('/fetch', async (req, res) => {
const data = await fetch(req.query.url);
res.send(await data.text());
// Attack: /fetch?url=http://localhost:6379/
// Access internal services (Redis, databases)
});
// SECURE: Validate and restrict URLs
const { URL } = require('url');
const ALLOWED_HOSTS = ['api.example.com', 'cdn.example.com'];
app.get('/fetch', async (req, res) => {
try {
const url = new URL(req.query.url);
// Block internal IPs
const hostname = url.hostname;
if (hostname === 'localhost' ||
hostname.startsWith('127.') ||
hostname.startsWith('192.168.') ||
hostname.startsWith('10.') ||
hostname.startsWith('169.254.')) {
return res.status(400).json({ error: 'Invalid URL' });
}
// Allow-list hostnames
if (!ALLOWED_HOSTS.includes(hostname)) {
return res.status(400).json({ error: 'Host not allowed' });
}
const data = await fetch(url.href);
res.send(await data.text());
} catch (err) {
res.status(400).json({ error: 'Invalid URL' });
}
});
Prevention:
Layer multiple security controls so failure of one doesn't compromise the system:
// Comprehensive validation approach
function validateInput(input, schema) {
// 1. Type check
if (typeof input !== schema.type) return false;
// 2. Length/range check
if (schema.maxLength && input.length > schema.maxLength) return false;
// 3. Format validation (regex)
if (schema.pattern && !schema.pattern.test(input)) return false;
// 4. Allow-list validation
if (schema.allowedValues && !schema.allowedValues.includes(input)) return false;
return true;
}
// Example usage
const emailSchema = {
type: 'string',
maxLength: 254,
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
};
if (!validateInput(req.body.email, emailSchema)) {
return res.status(400).json({ error: 'Invalid email format' });
}