299 lines
6.6 KiB
Markdown
299 lines
6.6 KiB
Markdown
# Vulnerability Patterns Reference
|
|
|
|
Secure vs vulnerable code patterns organized by category. Each pattern shows the vulnerability and its remediation.
|
|
|
|
---
|
|
|
|
## Input Validation
|
|
|
|
### SQL Injection
|
|
|
|
```typescript
|
|
// 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)
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// VULNERABLE
|
|
exec(`convert ${userFilename} output.png`);
|
|
|
|
// SECURE - parameterized or allowlist
|
|
execFile('convert', [userFilename, 'output.png']);
|
|
```
|
|
|
|
### Path Traversal
|
|
|
|
```typescript
|
|
// 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)
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// VULNERABLE - plain text or weak hash
|
|
const hash = md5(password);
|
|
|
|
// SECURE - bcrypt/argon2 with salt
|
|
const hash = await bcrypt.hash(password, 12);
|
|
```
|
|
|
|
### Session Management
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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)
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// VULNERABLE - predictable
|
|
const token = Math.random().toString(36);
|
|
|
|
// SECURE - cryptographically secure
|
|
const token = crypto.randomBytes(32).toString('hex');
|
|
```
|
|
|
|
---
|
|
|
|
## Data Exposure
|
|
|
|
### Sensitive Data in Logs
|
|
|
|
```typescript
|
|
// VULNERABLE
|
|
logger.info('User login', { email, password, ssn });
|
|
|
|
// SECURE - redact sensitive fields
|
|
logger.info('User login', {
|
|
email,
|
|
password: '[REDACTED]',
|
|
ssn: '[REDACTED]',
|
|
});
|
|
```
|
|
|
|
### Error Message Disclosure
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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 |
|