27 KiB
OWASP Top 10 (2021) — Detailed Reference
Comprehensive breakdown of each OWASP Top 10 category with CWE mappings, vulnerability patterns, and remediation strategies.
A01:2021 – Broken Access Control
Access control enforces policy such that users cannot act outside of their intended permissions. Failures typically lead to unauthorized information disclosure, modification, or destruction of data.
Common Weaknesses
- Missing Function Level Access Control — users can access admin functions
- Missing Resource Level Access Control (IDOR) — users can access others' resources
- CORS Misconfiguration — overly permissive cross-origin policies
- Force Browsing — accessing pages/resources by URL guessing
- Metadata Manipulation — JWT/cookie tampering to elevate privileges
- POST-based CSRF — state-changing operations without CSRF protection
CWE Mappings
- CWE-200: Exposure of Sensitive Information to an Unauthorized Actor
- CWE-201: Insertion of Sensitive Information Into Sent Data
- CWE-352: Cross-Site Request Forgery (CSRF)
- CWE-359: Exposure of Private Personal Information to an Unauthorized Actor
- CWE-377: Insecure Temporary File
- CWE-402: Transmission of Private Resources into a New Sphere
- CWE-425: Direct Request (Forced Browsing)
- CWE-639: Authorization Bypass Through User-Controlled Key
- CWE-759: Use of a One-Way Hash without a Salt
- CWE-918: Server-Side Request Forgery (SSRF)
- CWE-1275: Sensitive Cookie with Improper SameSite Attribute
Vulnerability Patterns
IDOR (Insecure Direct Object Reference):
// VULNERABLE — sequential IDs, no ownership check
GET /api/invoices/1001
{
"invoice_id": 1001,
"customer_id": 42,
"amount": 1500
}
// ATTACK — iterate through IDs
GET /api/invoices/1002 // Access someone else's invoice
GET /api/invoices/1003
GET /api/invoices/1004
Remediation:
// SECURE — verify ownership before returning
app.get('/api/invoices/:id', authenticate, async (req, res) => {
const invoice = await db.getInvoice(req.params.id);
if (!invoice) {
return res.status(404).json({ error: 'Not found' });
}
// Verify user owns resource or is admin
if (invoice.customerId !== req.user.id && !req.user.isAdmin) {
return res.status(403).json({ error: 'Forbidden' });
}
res.json(invoice);
});
// BETTER — use UUIDs instead of sequential IDs
const invoiceId = crypto.randomUUID(); // Non-guessable
Missing Function Level Access Control:
// VULNERABLE — client-side check only
function AdminPanel() {
if (!user.isAdmin) {
return <div>Access Denied</div>;
}
return <AdminDashboard />;
}
// Attacker can still call API directly:
fetch('/api/admin/users').then(r => r.json()) // No server-side check!
Remediation:
// SECURE — enforce on server
app.get('/api/admin/users', authenticate, requireAdmin, async (req, res) => {
// Server validates role on every request
if (!req.user.isAdmin) {
return res.status(403).json({ error: 'Forbidden' });
}
const users = await db.getAllUsers();
res.json(users);
});
// Middleware
function requireAdmin(req, res, next) {
if (!req.user?.isAdmin) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
}
CORS Misconfiguration:
// VULNERABLE — allows all origins
app.use(cors({
origin: '*',
credentials: true // Allows any site to make authenticated requests!
}));
Remediation:
// SECURE — explicit allowlist
const allowedOrigins = [
'https://app.example.com',
'https://admin.example.com',
];
app.use(cors({
origin: (origin, callback) => {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
}));
A02:2021 – Cryptographic Failures
Previously known as Sensitive Data Exposure. Focuses on failures related to cryptography which often lead to exposure of sensitive data.
Common Weaknesses
- Transmitting data in clear text — HTTP instead of HTTPS
- Old/weak cryptographic algorithms — MD5, SHA1, DES
- Default/weak keys — hardcoded or predictable
- Missing encryption at rest — sensitive data stored unencrypted
- Improper certificate validation — accepting self-signed certs in production
- Insufficient entropy — predictable random numbers
CWE Mappings
- CWE-259: Use of Hard-coded Password
- CWE-327: Use of a Broken or Risky Cryptographic Algorithm
- CWE-331: Insufficient Entropy
Vulnerability Patterns
Weak Hashing Algorithm:
// VULNERABLE — MD5 is broken
const hash = crypto.createHash('md5').update(password).digest('hex');
// VULNERABLE — SHA1 is deprecated
const hash = crypto.createHash('sha1').update(password).digest('hex');
// VULNERABLE — no salt (rainbow tables)
const hash = crypto.createHash('sha256').update(password).digest('hex');
Remediation:
// SECURE — bcrypt with sufficient cost
import bcrypt from 'bcrypt';
const saltRounds = 12; // Minimum 10, increase as hardware improves
const hash = await bcrypt.hash(password, saltRounds);
// Verification
const isValid = await bcrypt.compare(inputPassword, storedHash);
// ALTERNATIVE — Argon2 (winner of Password Hashing Competition)
import argon2 from 'argon2';
const hash = await argon2.hash(password, {
type: argon2.argon2id, // Resistant to GPU and side-channel attacks
memoryCost: 2 ** 16, // 64 MiB
timeCost: 3,
parallelism: 1,
});
Hardcoded Secrets:
// VULNERABLE — secrets in code
const API_KEY = 'sk-1234567890abcdef';
const DB_PASSWORD = 'admin123';
const JWT_SECRET = 'mysecret';
// Committed to Git — now in history forever!
Remediation:
// SECURE — environment variables
const API_KEY = process.env.API_KEY;
const DB_PASSWORD = process.env.DB_PASSWORD;
const JWT_SECRET = process.env.JWT_SECRET;
// Validate at startup
if (!API_KEY || !DB_PASSWORD || !JWT_SECRET) {
throw new Error('Missing required environment variables');
}
// .env (add to .gitignore!)
API_KEY=sk-real-key-here
DB_PASSWORD=strong-password-here
JWT_SECRET=long-random-string-here
// .env.example (commit this)
API_KEY=your_api_key_here
DB_PASSWORD=your_db_password_here
JWT_SECRET=your_jwt_secret_here
Weak Encryption Algorithm:
// VULNERABLE — DES is broken
const cipher = crypto.createCipher('des', key);
// VULNERABLE — ECB mode (patterns leak)
const cipher = crypto.createCipheriv('aes-256-ecb', key, null);
// VULNERABLE — no authentication (malleable)
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
Remediation:
// SECURE — AES-256-GCM (authenticated encryption)
const algorithm = 'aes-256-gcm';
const key = crypto.randomBytes(32); // 256 bits
const iv = crypto.randomBytes(16); // 128 bits
// Encryption
const cipher = crypto.createCipheriv(algorithm, key, iv);
let encrypted = cipher.update(plaintext, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
// Store: iv + authTag + encrypted
// Decryption
const decipher = crypto.createDecipheriv(algorithm, key, iv);
decipher.setAuthTag(authTag);
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
Insufficient Entropy:
// VULNERABLE — predictable
const sessionId = Math.random().toString(36);
const resetToken = Date.now().toString(36);
const apiKey = userId + '-' + Math.floor(Math.random() * 1000000);
Remediation:
// SECURE — cryptographically secure random
const sessionId = crypto.randomBytes(32).toString('hex'); // 64 hex chars
const resetToken = crypto.randomBytes(32).toString('base64url');
const apiKey = crypto.randomBytes(24).toString('base64url');
// For UUIDs
const uuid = crypto.randomUUID(); // UUIDv4
A03:2021 – Injection
Application is vulnerable to injection when user-supplied data is not validated, filtered, or sanitized by the application.
Common Weaknesses
- SQL Injection — malicious SQL in queries
- NoSQL Injection — malicious queries in MongoDB, etc.
- OS Command Injection — executing shell commands
- LDAP Injection — malicious LDAP queries
- XPath Injection — malicious XPath queries
- ORM Injection — unsafe ORM query construction
CWE Mappings
- CWE-20: Improper Input Validation
- CWE-74: Improper Neutralization of Special Elements in Output
- CWE-75: Failure to Sanitize Special Elements into a Different Plane
- CWE-77: Improper Neutralization of Special Elements used in a Command
- CWE-78: Improper Neutralization of Special Elements used in an OS Command
- CWE-79: Improper Neutralization of Input During Web Page Generation (XSS)
- CWE-80: Improper Neutralization of Script-Related HTML Tags
- CWE-83: Improper Neutralization of Script in Attributes
- CWE-89: Improper Neutralization of Special Elements used in an SQL Command
- CWE-91: XML Injection
- CWE-93: Improper Neutralization of CRLF Sequences
- CWE-94: Improper Control of Generation of Code
- CWE-95: Improper Neutralization of Directives in Dynamically Evaluated Code
- CWE-96: Improper Neutralization of Directives in Statically Saved Code
- CWE-97: Improper Neutralization of Server-Side Includes
- CWE-183: Permissive List of Allowed Inputs
- CWE-184: Incomplete List of Disallowed Inputs
Vulnerability Patterns
SQL Injection:
-- VULNERABLE — string concatenation
const query = `SELECT * FROM users WHERE email = '${userEmail}' AND password = '${userPassword}'`;
-- ATTACK
userEmail: admin@example.com'--
userPassword: anything
-- RESULTS IN
SELECT * FROM users WHERE email = 'admin@example.com'--' AND password = 'anything'
-- Comment removes password check!
-- ATTACK 2 — data exfiltration
userEmail: ' UNION SELECT password FROM users--
-- ATTACK 3 — blind SQL injection
userEmail: ' OR 1=1--
Remediation:
// SECURE — parameterized queries (prepared statements)
const query = 'SELECT * FROM users WHERE email = ? AND password = ?';
const [rows] = await db.execute(query, [userEmail, passwordHash]);
// PostgreSQL — numbered placeholders
const query = 'SELECT * FROM users WHERE email = $1 AND password = $2';
const result = await pool.query(query, [userEmail, passwordHash]);
// ORM — use safe methods
const user = await User.findOne({
where: {
email: userEmail,
password: passwordHash,
},
});
// NEVER — string interpolation or concatenation in SQL
NoSQL Injection:
// VULNERABLE — object injection
app.post('/login', async (req, res) => {
const { email, password } = req.body;
const user = await db.collection('users').findOne({
email: email,
password: password,
});
});
// ATTACK — bypass authentication
POST /login
{
"email": { "$gt": "" },
"password": { "$gt": "" }
}
// Query becomes: find where email > "" AND password > ""
// Returns first user!
Remediation:
// SECURE — type validation
app.post('/login', async (req, res) => {
const { email, password } = req.body;
// Ensure inputs are strings
if (typeof email !== 'string' || typeof password !== 'string') {
return res.status(400).json({ error: 'Invalid input' });
}
const user = await db.collection('users').findOne({
email: email,
password: await hashPassword(password),
});
});
// BETTER — schema validation
import { z } from 'zod';
const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
app.post('/login', async (req, res) => {
const result = loginSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ error: 'Invalid input' });
}
const { email, password } = result.data;
// Now guaranteed to be strings
});
OS Command Injection:
// VULNERABLE — user input in shell command
const filename = req.query.file;
exec(`convert ${filename} output.png`, (err, stdout) => {
// Process output
});
// ATTACK
?file=; rm -rf /
// RESULTS IN
convert ; rm -rf / output.png
// Executes rm -rf /!
Remediation:
// SECURE — use parameterized API
import { execFile } from 'child_process';
const filename = req.query.file;
// Validate filename
if (!/^[a-zA-Z0-9._-]+$/.test(filename)) {
return res.status(400).json({ error: 'Invalid filename' });
}
// Use execFile with array arguments (no shell)
execFile('convert', [filename, 'output.png'], (err, stdout) => {
if (err) {
logger.error('Conversion failed', err);
return res.status(500).json({ error: 'Conversion failed' });
}
// Process output
});
// BETTER — use library instead of shell command
import sharp from 'sharp';
await sharp(filename).toFile('output.png');
XSS (Cross-Site Scripting):
<!-- VULNERABLE — direct HTML insertion -->
<div id="greeting"></div>
<script>
const name = new URLSearchParams(window.location.search).get('name');
document.getElementById('greeting').innerHTML = `Hello ${name}!`;
</script>
<!-- ATTACK -->
?name=<img src=x onerror=alert(document.cookie)>
<!-- RESULTS IN -->
<div id="greeting">Hello <img src=x onerror=alert(document.cookie)>!</div>
<!-- Executes JavaScript! -->
Remediation:
<!-- SECURE — use textContent -->
<div id="greeting"></div>
<script>
const name = new URLSearchParams(window.location.search).get('name');
document.getElementById('greeting').textContent = `Hello ${name}!`;
</script>
<!-- For rich content — sanitize -->
<div id="content"></div>
<script>
import DOMPurify from 'dompurify';
const userContent = getUserContent();
const clean = DOMPurify.sanitize(userContent, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
ALLOWED_ATTR: ['href'],
});
document.getElementById('content').innerHTML = clean;
</script>
A04:2021 – Insecure Design
New category focusing on risks related to design and architectural flaws. Requires threat modeling, secure design patterns, and reference architectures.
Common Weaknesses
- Missing Security Controls — no rate limiting, no CAPTCHA
- Business Logic Flaws — discount code stacking, negative quantities
- Insufficient Isolation — multi-tenant data leakage
- Weak Security Architecture — no defense in depth
CWE Mappings
- CWE-209: Generation of Error Message Containing Sensitive Information
- CWE-256: Plaintext Storage of a Password
- CWE-257: Storing Passwords in a Recoverable Format
- CWE-266: Incorrect Privilege Assignment
- CWE-269: Improper Privilege Management
- CWE-280: Improper Handling of Insufficient Permissions
- CWE-311: Missing Encryption of Sensitive Data
- CWE-312: Cleartext Storage of Sensitive Information
- CWE-313: Cleartext Storage in a File or on Disk
- CWE-316: Cleartext Storage of Sensitive Information in Memory
- CWE-419: Unprotected Primary Channel
- CWE-430: Deployment of Wrong Handler
- CWE-434: Unrestricted Upload of File with Dangerous Type
- CWE-444: Inconsistent Interpretation of HTTP Requests
Vulnerability Patterns
Missing Rate Limiting:
// VULNERABLE — no rate limiting
app.post('/api/login', async (req, res) => {
const { email, password } = req.body;
const user = await authenticateUser(email, password);
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
res.json({ token: generateToken(user) });
});
// ATTACK — brute force attack
// Try thousands of passwords per second
Remediation:
// SECURE — rate limiting with exponential backoff
import rateLimit from 'express-rate-limit';
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 attempts per window
skipSuccessfulRequests: true,
standardHeaders: true,
legacyHeaders: false,
handler: (req, res) => {
res.status(429).json({
error: 'Too many login attempts, please try again later',
});
},
});
app.post('/api/login', loginLimiter, async (req, res) => {
// Authentication logic
});
// BETTER — account lockout after failed attempts
const MAX_FAILED_ATTEMPTS = 5;
const LOCKOUT_DURATION = 30 * 60 * 1000; // 30 minutes
app.post('/api/login', async (req, res) => {
const { email, password } = req.body;
const account = await getAccount(email);
// Check if locked
if (account.lockedUntil && account.lockedUntil > Date.now()) {
return res.status(429).json({
error: 'Account locked. Try again later.',
});
}
const user = await authenticateUser(email, password);
if (!user) {
// Increment failed attempts
account.failedAttempts += 1;
if (account.failedAttempts >= MAX_FAILED_ATTEMPTS) {
account.lockedUntil = Date.now() + LOCKOUT_DURATION;
await saveAccount(account);
return res.status(429).json({ error: 'Account locked' });
}
await saveAccount(account);
return res.status(401).json({ error: 'Invalid credentials' });
}
// Reset on success
account.failedAttempts = 0;
account.lockedUntil = null;
await saveAccount(account);
res.json({ token: generateToken(user) });
});
Business Logic Flaw — Race Condition:
// VULNERABLE — time-of-check to time-of-use
app.post('/api/transfer', async (req, res) => {
const { from, to, amount } = req.body;
const balance = await getBalance(from);
if (balance < amount) {
return res.status(400).json({ error: 'Insufficient funds' });
}
// RACE CONDITION — balance could be spent between check and update
await deduct(from, amount);
await credit(to, amount);
res.json({ success: true });
});
// ATTACK — send two transfer requests simultaneously
// Both pass balance check before either updates
Remediation:
// SECURE — atomic transaction
app.post('/api/transfer', async (req, res) => {
const { from, to, amount } = req.body;
const result = await db.transaction(async (trx) => {
// Lock row for update
const account = await trx('accounts')
.where({ id: from })
.forUpdate()
.first();
if (account.balance < amount) {
throw new Error('Insufficient funds');
}
// Atomic debit/credit
await trx('accounts')
.where({ id: from })
.decrement('balance', amount);
await trx('accounts')
.where({ id: to })
.increment('balance', amount);
return { success: true };
});
res.json(result);
});
// Database-level constraint
ALTER TABLE accounts ADD CONSTRAINT positive_balance CHECK (balance >= 0);
A05:2021 – Security Misconfiguration
Common Weaknesses
- Unnecessary features enabled — debug mode in production
- Default accounts — admin/admin still active
- Verbose error messages — stack traces to users
- Missing security headers — no CSP, X-Frame-Options
- Outdated software — old framework versions
CWE Mappings
- CWE-2: 7PK - Environment
- CWE-11: ASP.NET Misconfiguration
- CWE-13: ASP.NET Misconfiguration: Password in Configuration File
- CWE-15: External Control of System or Configuration Setting
- CWE-16: Configuration
- CWE-260: Password in Configuration File
- CWE-315: Cleartext Storage of Sensitive Information in a Cookie
- CWE-520: .NET Misconfiguration
- CWE-526: Exposure of Sensitive Information Through Environmental Variables
- CWE-537: Java Runtime Error Message Containing Sensitive Information
- CWE-541: Inclusion of Sensitive Information in an Include File
- CWE-547: Use of Hard-coded, Security-relevant Constants
- CWE-611: Improper Restriction of XML External Entity Reference
- CWE-614: Sensitive Cookie in HTTPS Session Without 'Secure' Attribute
- CWE-756: Missing Custom Error Page
- CWE-776: Improper Restriction of Recursive Entity References in DTDs
Remediation
Security Headers:
import helmet from 'helmet';
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
},
}));
// Additional 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('Referrer-Policy', 'strict-origin-when-cross-origin');
next();
});
A06:2021 – Vulnerable and Outdated Components
Common Weaknesses
- Known vulnerabilities — using libs with CVEs
- Outdated dependencies — years-old versions
- No security updates — never updating packages
- Unused dependencies — unnecessary attack surface
CWE Mappings
- CWE-1035: Using Components with Known Vulnerabilities
- CWE-1104: Use of Unmaintained Third Party Components
Remediation
# Audit dependencies
npm audit
npm audit fix
# Update outdated packages
npm outdated
npm update
# Check for known vulnerabilities
npx snyk test
# Automated dependency updates
# Use Dependabot/Renovate for automated PRs
A07:2021 – Identification and Authentication Failures
Common Weaknesses
- Weak passwords — no complexity requirements
- Brute force — no rate limiting
- Session fixation — accepting user-supplied session IDs
- Credential stuffing — no breach detection
- Missing MFA — single factor only
CWE Mappings
- CWE-287: Improper Authentication
- CWE-288: Authentication Bypass Using an Alternate Path or Channel
- CWE-290: Authentication Bypass by Spoofing
- CWE-294: Authentication Bypass by Capture-replay
- CWE-295: Improper Certificate Validation
- CWE-297: Improper Validation of Certificate with Host Mismatch
- CWE-300: Channel Accessible by Non-Endpoint
- CWE-302: Authentication Bypass by Assumed-Immutable Data
- CWE-304: Missing Critical Step in Authentication
- CWE-306: Missing Authentication for Critical Function
- CWE-307: Improper Restriction of Excessive Authentication Attempts
- CWE-346: Origin Validation Error
- CWE-384: Session Fixation
- CWE-521: Weak Password Requirements
- CWE-613: Insufficient Session Expiration
- CWE-640: Weak Password Recovery Mechanism for Forgotten Password
- CWE-798: Use of Hard-coded Credentials
- CWE-940: Improper Verification of Source of a Communication Channel
- CWE-1216: Lockout Mechanism Errors
Remediation
See main SKILL.md for authentication patterns.
A08:2021 – Software and Data Integrity Failures
Common Weaknesses
- Unsigned updates — accepting any code update
- Insecure deserialization — unvalidated object deserialization
- Missing CI/CD security — compromised build pipeline
CWE Mappings
- CWE-345: Insufficient Verification of Data Authenticity
- CWE-353: Missing Support for Integrity Check
- CWE-426: Untrusted Search Path
- CWE-494: Download of Code Without Integrity Check
- CWE-502: Deserialization of Untrusted Data
- CWE-565: Reliance on Cookies without Validation and Integrity Checking
- CWE-784: Reliance on Cookies without Validation and Integrity Checking in a Security Decision
- CWE-829: Inclusion of Functionality from Untrusted Control Sphere
Vulnerability Pattern
Insecure Deserialization:
// VULNERABLE — deserialize untrusted data
const userData = JSON.parse(req.cookies.user);
const obj = deserialize(req.body.data); // Arbitrary code execution!
Remediation:
// SECURE — validate structure
import { z } from 'zod';
const userSchema = z.object({
id: z.string().uuid(),
role: z.enum(['user', 'admin']),
});
const result = userSchema.safeParse(JSON.parse(req.cookies.user));
if (!result.success) {
throw new Error('Invalid user data');
}
A09:2021 – Security Logging and Monitoring Failures
Common Weaknesses
- Missing audit logs — no record of critical operations
- Insufficient log detail — can't reconstruct attack
- No monitoring — logs not reviewed
- Insecure log storage — logs tamper-able
CWE Mappings
- CWE-117: Improper Output Neutralization for Logs
- CWE-223: Omission of Security-relevant Information
- CWE-532: Insertion of Sensitive Information into Log File
- CWE-778: Insufficient Logging
Remediation
// Log security events
logger.info('User login', {
userId: user.id,
ip: req.ip,
userAgent: req.headers['user-agent'],
timestamp: new Date().toISOString(),
});
logger.warn('Failed login attempt', {
email: req.body.email, // Don't log password!
ip: req.ip,
attempts: failedAttempts,
});
logger.error('Unauthorized access attempt', {
userId: req.user.id,
resource: req.path,
method: req.method,
ip: req.ip,
});
// NEVER log sensitive data
logger.info('User data', {
email: user.email,
password: '[REDACTED]',
ssn: '[REDACTED]',
creditCard: '[REDACTED]',
});
A10:2021 – Server-Side Request Forgery (SSRF)
Common Weaknesses
- Unvalidated URLs — fetching arbitrary URLs
- Cloud metadata access — accessing AWS/GCP metadata endpoints
- Internal network scanning — probing internal services
CWE Mappings
- CWE-918: Server-Side Request Forgery (SSRF)
Vulnerability Pattern
// VULNERABLE — fetch arbitrary URL
app.get('/api/fetch', async (req, res) => {
const url = req.query.url;
const response = await fetch(url);
const data = await response.text();
res.send(data);
});
// ATTACK — access cloud metadata
?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
// ATTACK — scan internal network
?url=http://localhost:6379/ // Redis
?url=http://10.0.0.5:22/ // SSH
Remediation:
// SECURE — allowlist of domains
const ALLOWED_DOMAINS = ['api.example.com', 'cdn.example.com'];
app.get('/api/fetch', async (req, res) => {
const url = new URL(req.query.url);
// Validate domain
if (!ALLOWED_DOMAINS.includes(url.hostname)) {
return res.status(403).json({ error: 'Domain not allowed' });
}
// Block private IPs
const ip = await dns.resolve4(url.hostname);
if (isPrivateIP(ip[0])) {
return res.status(403).json({ error: 'Private IP not allowed' });
}
const response = await fetch(url.href);
const data = await response.text();
res.send(data);
});
function isPrivateIP(ip: string): boolean {
return /^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.|127\.)/.test(ip)
|| ip === '::1'
|| ip.startsWith('169.254.'); // Cloud metadata
}
Quick Reference Table
| Category | Key CWEs | Top Mitigations |
|---|---|---|
| A01 Broken Access Control | 200, 352, 639, 918 | Server-side checks, ownership validation, CSRF tokens |
| A02 Cryptographic Failures | 259, 327, 331 | TLS, bcrypt, no hardcoded secrets, crypto.randomBytes |
| A03 Injection | 20, 79, 89 | Parameterized queries, input validation, output encoding |
| A04 Insecure Design | 209, 256, 434 | Threat modeling, rate limiting, defense in depth |
| A05 Security Misconfiguration | 16, 611, 614 | Security headers, disable debug, defaults changed |
| A06 Vulnerable Components | 1035, 1104 | npm audit, Dependabot, regular updates |
| A07 Authentication Failures | 287, 307, 521, 798 | Strong passwords, MFA, rate limiting, no defaults |
| A08 Integrity Failures | 502, 494 | Verify signatures, CI/CD hardening, schema validation |
| A09 Logging Failures | 117, 532, 778 | Audit logs, monitoring, redact sensitive data |
| A10 SSRF | 918 | URL allowlist, block private IPs, validate domains |