playbook/outfitter-agents/plugins/outfitter/skills/security/references/owasp-top-10.md

27 KiB
Raw Permalink Blame History

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