1018 lines
27 KiB
Markdown
1018 lines
27 KiB
Markdown
# 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)**:
|
||
|
||
```typescript
|
||
// 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**:
|
||
|
||
```typescript
|
||
// 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**:
|
||
|
||
```typescript
|
||
// 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**:
|
||
|
||
```typescript
|
||
// 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**:
|
||
|
||
```typescript
|
||
// VULNERABLE — allows all origins
|
||
app.use(cors({
|
||
origin: '*',
|
||
credentials: true // Allows any site to make authenticated requests!
|
||
}));
|
||
```
|
||
|
||
**Remediation**:
|
||
|
||
```typescript
|
||
// 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**:
|
||
|
||
```typescript
|
||
// 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**:
|
||
|
||
```typescript
|
||
// 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**:
|
||
|
||
```typescript
|
||
// 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**:
|
||
|
||
```typescript
|
||
// 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**:
|
||
|
||
```typescript
|
||
// 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**:
|
||
|
||
```typescript
|
||
// 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**:
|
||
|
||
```typescript
|
||
// VULNERABLE — predictable
|
||
const sessionId = Math.random().toString(36);
|
||
const resetToken = Date.now().toString(36);
|
||
const apiKey = userId + '-' + Math.floor(Math.random() * 1000000);
|
||
```
|
||
|
||
**Remediation**:
|
||
|
||
```typescript
|
||
// 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**:
|
||
|
||
```sql
|
||
-- 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**:
|
||
|
||
```typescript
|
||
// 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**:
|
||
|
||
```javascript
|
||
// 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**:
|
||
|
||
```typescript
|
||
// 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**:
|
||
|
||
```typescript
|
||
// 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**:
|
||
|
||
```typescript
|
||
// 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)**:
|
||
|
||
```html
|
||
<!-- 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**:
|
||
|
||
```html
|
||
<!-- 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**:
|
||
|
||
```typescript
|
||
// 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**:
|
||
|
||
```typescript
|
||
// 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**:
|
||
|
||
```typescript
|
||
// 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**:
|
||
|
||
```typescript
|
||
// 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**:
|
||
|
||
```typescript
|
||
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
|
||
|
||
```bash
|
||
# 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**:
|
||
|
||
```typescript
|
||
// VULNERABLE — deserialize untrusted data
|
||
const userData = JSON.parse(req.cookies.user);
|
||
const obj = deserialize(req.body.data); // Arbitrary code execution!
|
||
```
|
||
|
||
**Remediation**:
|
||
|
||
```typescript
|
||
// 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
|
||
|
||
```typescript
|
||
// 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
|
||
|
||
```typescript
|
||
// 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**:
|
||
|
||
```typescript
|
||
// 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 |
|