playbook/outfitter-agents/plugins/outfitter/skills/security/references/vulnerability-patterns.md

6.6 KiB

Vulnerability Patterns Reference

Secure vs vulnerable code patterns organized by category. Each pattern shows the vulnerability and its remediation.


Input Validation

SQL Injection

// VULNERABLE
const query = `SELECT * FROM users WHERE email = '${userEmail}'`;

// SECURE - parameterized queries
const query = 'SELECT * FROM users WHERE email = ?';
db.execute(query, [userEmail]);

XSS (Cross-Site Scripting)

// VULNERABLE - direct HTML insertion
element.innerHTML = userInput;

// SECURE - use textContent or sanitize
element.textContent = userInput;
// OR for rich content
element.innerHTML = DOMPurify.sanitize(userInput);

Command Injection

// VULNERABLE
exec(`convert ${userFilename} output.png`);

// SECURE - parameterized or allowlist
execFile('convert', [userFilename, 'output.png']);

Path Traversal

// VULNERABLE
const filePath = `/uploads/${userFileName}`;

// SECURE - validate and normalize
const safeName = path.basename(userFileName);
const filePath = path.join('/uploads', safeName);
if (!filePath.startsWith('/uploads/')) {
  throw new Error('Invalid path');
}

XXE (XML External Entity)

// VULNERABLE
const parser = new DOMParser();
const doc = parser.parseFromString(xmlInput, 'text/xml');

// SECURE - disable external entities
const parser = new DOMParser({
  locator: {},
  errorHandler: {},
  entityResolver: () => null, // Disable DTD processing
});

Authentication & Sessions

Password Storage

// VULNERABLE - plain text or weak hash
const hash = md5(password);

// SECURE - bcrypt/argon2 with salt
const hash = await bcrypt.hash(password, 12);

Session Management

// VULNERABLE - predictable session IDs
const sessionId = userId + Date.now();

// SECURE - cryptographically random
const sessionId = crypto.randomBytes(32).toString('hex');

// Security attributes
res.cookie('session', sessionId, {
  httpOnly: true,
  secure: true,       // HTTPS only
  sameSite: 'strict',
  maxAge: 3600000,    // 1 hour
});

JWT Handling

// VULNERABLE - no signature verification
const payload = JSON.parse(atob(token.split('.')[1]));

// SECURE - verify signature
const payload = jwt.verify(token, SECRET_KEY, {
  algorithms: ['HS256'], // Specify allowed algorithms
  issuer: 'your-app',
  audience: 'your-api',
});

Password Reset

// VULNERABLE - predictable tokens
const resetToken = userId + '-' + Date.now();

// SECURE - cryptographically random with expiry
const resetToken = crypto.randomBytes(32).toString('hex');
await db.execute(
  'INSERT INTO reset_tokens (user_id, token, expires_at) VALUES (?, ?, ?)',
  [userId, await bcrypt.hash(resetToken, 10), Date.now() + 3600000]
);

Authorization

Broken Access Control

// VULNERABLE - client-side only check
if (user.isAdmin) {
  // show admin panel
}

// SECURE - server-side enforcement
app.get('/admin/users', requireAdmin, (req, res) => {
  if (!req.user?.isAdmin) {
    return res.status(403).json({ error: 'Forbidden' });
  }
  // Admin operation
});

IDOR (Insecure Direct Object Reference)

// VULNERABLE - no ownership check
app.get('/api/documents/:id', async (req, res) => {
  const doc = await db.getDocument(req.params.id);
  res.json(doc);
});

// SECURE - verify ownership
app.get('/api/documents/:id', async (req, res) => {
  const doc = await db.getDocument(req.params.id);
  if (doc.userId !== req.user.id && !req.user.isAdmin) {
    return res.status(403).json({ error: 'Forbidden' });
  }
  res.json(doc);
});

Privilege Escalation

// VULNERABLE - role from client input
app.post('/api/users', async (req, res) => {
  const user = await createUser({
    ...req.body, // Includes role: 'admin' from malicious client
  });
});

// SECURE - explicit allowlist
app.post('/api/users', async (req, res) => {
  const allowedFields = ['name', 'email', 'password'];
  const userData = pick(req.body, allowedFields);
  const user = await createUser({
    ...userData,
    role: 'user', // Server controls role
  });
});

Cryptography

Weak Algorithms

// VULNERABLE - deprecated algorithms
const hash = crypto.createHash('md5').update(data).digest('hex');
const cipher = crypto.createCipher('des', key);

// SECURE - modern algorithms
const hash = crypto.createHash('sha256').update(data).digest('hex');
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);

Hardcoded Secrets

// VULNERABLE
const API_KEY = 'sk-1234567890abcdef';
const DB_PASSWORD = 'admin123';

// SECURE - environment variables
const API_KEY = process.env.API_KEY;
const DB_PASSWORD = process.env.DB_PASSWORD;

if (!API_KEY || !DB_PASSWORD) {
  throw new Error('Missing required environment variables');
}

Insufficient Randomness

// VULNERABLE - predictable
const token = Math.random().toString(36);

// SECURE - cryptographically secure
const token = crypto.randomBytes(32).toString('hex');

Data Exposure

Sensitive Data in Logs

// VULNERABLE
logger.info('User login', { email, password, ssn });

// SECURE - redact sensitive fields
logger.info('User login', {
  email,
  password: '[REDACTED]',
  ssn: '[REDACTED]',
});

Error Message Disclosure

// VULNERABLE - exposes internals
catch (err) {
  res.status(500).json({ error: err.stack });
}

// SECURE - generic message
catch (err) {
  logger.error('Internal error', err);
  res.status(500).json({ error: 'Internal server error' });
}

Timing Attacks

// VULNERABLE - early exit leaks info
if (user.password !== inputPassword) {
  return false;
}

// SECURE - constant-time comparison
return crypto.timingSafeEqual(
  Buffer.from(user.password),
  Buffer.from(inputPassword)
);

Quick Reference

Category Vulnerable Pattern Secure Pattern
SQL Injection String concatenation Parameterized queries
XSS innerHTML with user input textContent or DOMPurify
Command Injection exec() with user input execFile() with array args
Path Traversal Direct path concat path.basename + prefix check
Password Storage MD5/SHA1/plain bcrypt (cost 12+) or argon2
Session IDs Predictable (Date.now) crypto.randomBytes(32)
JWT Skip verification jwt.verify() with algorithm
Access Control Client-side only Server-side on every request
IDOR No ownership check Verify user owns resource
Secrets Hardcoded in code Environment variables
Error Messages Stack traces to users Generic error + log details