OWASP Top 10
Current Level: Intermediate
Domain: Security / Application Security
Overview
The OWASP (Open Web Application Security Project) Top 10 is a standard awareness document representing a broad consensus about the most critical security risks to web applications. Updated every 3-4 years (2021 is the latest version), it provides a prioritized list of security risks and mitigation strategies.
What is OWASP Top 10
Why OWASP Top 10 Matters
| Benefit |
Impact |
| Industry Standard |
Widely recognized security framework |
| Prioritization |
Focus on most critical risks |
| Compliance |
Required by many security standards |
| Education |
Learn common vulnerabilities |
| Testing |
Guide for security testing |
A01: Broken Access Control
Description
Users can access resources or perform actions they shouldn't be able to, due to missing or ineffective access controls.
Examples
| Scenario |
Description |
| View other users' data |
Access /user/123 when logged in as user 456 |
| Access admin functions |
Access /admin without admin role |
| Modify others' data |
Update /user/123/profile as user 456 |
| Bypass authorization |
Access API endpoints without proper auth |
Prevention
// Bad: No authorization check
app.get('/user/:id', (req, res) => {
const user = getUser(req.params.id);
res.json(user); // Anyone can view any user!
});
// Good: Check authorization
app.get('/user/:id', (req, res) => {
if (req.params.id !== req.user.id && !req.user.isAdmin) {
return res.status(403).json({ error: 'Forbidden' });
}
const user = getUser(req.params.id);
res.json(user);
});
Best Practices
| Practice |
Description |
| Deny by default |
Explicitly allow, deny everything else |
| Check server-side |
Never trust client-side checks |
| Principle of least privilege |
Grant minimum necessary access |
| Log access |
Track who accesses what |
| Regular audit |
Review access controls regularly |
A02: Cryptographic Failures
Description
Sensitive data is exposed due to weak or missing encryption, or improper cryptographic practices.
Examples
| Scenario |
Description |
| Passwords in plaintext |
Stored without hashing |
| Weak hashing |
Using MD5, SHA1 for passwords |
| Unencrypted transmission |
HTTP instead of HTTPS |
| Hardcoded keys |
Encryption keys in source code |
| Weak algorithms |
Using DES, RC4 |
Prevention
// Bad: Weak password hashing
const hash = md5(password); // MD5 is broken
// Good: Strong password hashing
const hash = await bcrypt.hash(password, 10);
// Bad: Hardcoded key
const apiKey = 'sk_live_1234567890';
// Good: Environment variable
const apiKey = process.env.API_KEY;
// Bad: No encryption in transit
http.createServer((req, res) => { ... }); // HTTP
// Good: Use HTTPS
https.createServer(options, (req, res) => { ... });
Best Practices
| Practice |
Description |
| Encrypt at rest |
Use AES-256 for sensitive data |
| Encrypt in transit |
Use TLS (HTTPS) |
| Strong password hashing |
Use bcrypt, Argon2, scrypt |
| Never store plaintext passwords |
Always hash passwords |
| Key management |
Use secure key management service |
| Update algorithms |
Use current, strong algorithms |
A03: Injection
Description
Malicious data is sent to an interpreter as part of a command or query, allowing attackers to execute unintended commands.
SQL Injection
Example Attack:
-- Vulnerable query
SELECT * FROM users WHERE username = '$username' AND password = '$password';
-- Attack payload
username: ' OR '1'='1
password: anything
-- Resulting query (always true)
SELECT * FROM users WHERE username = '' OR '1'='1' AND password = 'anything'
Prevention
// Bad: String concatenation
const query = `SELECT * FROM users WHERE username = '${username}'`;
// Good: Parameterized query
const query = 'SELECT * FROM users WHERE username = $1';
await db.query(query, [username]);
// Good: Using ORM
const user = await User.findOne({ where: { username } });
Other Injection Types
| Type |
Example |
Prevention |
| NoSQL Injection |
MongoDB query injection |
Use parameterized queries |
| OS Command Injection |
; rm -rf / |
Use parameterized APIs |
| LDAP Injection |
*)(& |
Use parameterized queries |
| XPath Injection |
1' or '1'='1 |
Use parameterized queries |
Best Practices
| Practice |
Description |
| Parameterized queries |
Never concatenate user input |
| Use ORM |
Let ORM handle escaping |
| Input validation |
Validate and sanitize input |
| Least privilege |
Use least-privileged DB user |
| Stored procedures |
Use when appropriate |
A04: Insecure Design
Description
Missing or ineffective security controls by design, not implementation.
Examples
| Scenario |
Description |
| No rate limiting |
Brute force attacks possible |
| Weak password policy |
Users can set weak passwords |
| No MFA |
Single-factor authentication |
| Insecure defaults |
Default admin/admin passwords |
| No security headers |
Missing CSP, HSTS |
Prevention
// Bad: No rate limiting
app.post('/login', (req, res) => {
// No rate limit, brute force possible
});
// Good: Rate limiting
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5 // limit each IP to 5 requests per windowMs
});
app.post('/login', limiter, (req, res) => {
// Rate limited
});
// Good: Security headers
app.use(helmet());
Best Practices
| Practice |
Description |
| Threat modeling |
Identify threats during design |
| Secure design patterns |
Use proven patterns |
| Defense in depth |
Multiple security layers |
| Security by design |
Build security from start |
| Regular reviews |
Review design for security issues |
A05: Security Misconfiguration
Description
Insecure default configurations, incomplete setups, open cloud storage, verbose error messages, etc.
Examples
| Scenario |
Description |
| Default credentials |
admin/admin, root/password |
| Directory listing |
Files visible at /files/ |
| Verbose errors |
Stack traces exposed to users |
| Unnecessary services |
Debug mode enabled in production |
| Open cloud storage |
S3 bucket publicly accessible |
Prevention
// Bad: Verbose error messages
app.use((err, req, res, next) => {
res.status(500).json({
error: err.message,
stack: err.stack // Exposes internal details
});
});
// Good: Generic error messages
app.use((err, req, res, next) => {
console.error(err); // Log full error
res.status(500).json({
error: 'Internal server error'
});
});
// Good: Disable directory listing
app.use(express.static('public', {
index: false,
showDir: false
}));
Best Practices
| Practice |
Description |
| Hardening guides |
Follow vendor hardening guides |
| Remove defaults |
Change default credentials |
| Minimal install |
Install only what's needed |
| Disable debug |
Never run debug in production |
| Automated scanning |
Use config scanning tools |
| Cloud security |
Review cloud configurations |
A06: Vulnerable and Outdated Components
Description
Using libraries, frameworks, or other software modules with known vulnerabilities.
Examples
| Scenario |
Description |
| Old React version |
Known XSS vulnerability |
| Outdated OpenSSL |
Heartbleed vulnerability |
| Vulnerable npm package |
Malicious code in dependency |
| Unpatched server |
Known exploit available |
Prevention
# Check for vulnerabilities
npm audit
# Update dependencies
npm update
# Use Snyk
snyk test
# Use Dependabot (GitHub)
# Automatically creates PRs for vulnerable dependencies
Best Practices
| Practice |
Description |
| Dependency scanning |
Regularly scan for vulnerabilities |
| Keep updated |
Update dependencies regularly |
| Remove unused |
Delete unused dependencies |
| Use lockfiles |
Pin dependency versions |
| Monitor CVEs |
Watch for new vulnerabilities |
| Automated updates |
Use Dependabot, Renovate |
A07: Identification and Authentication Failures
Description
Broken authentication mechanisms allowing attackers to compromise user identities.
Examples
| Scenario |
Description |
| Weak passwords allowed |
No password complexity requirements |
| No MFA |
Only password required |
| Session fixation |
Attacker sets session ID |
| Credential stuffing |
Reuse of compromised credentials |
| Password reset flaws |
Weak reset tokens |
Prevention
// Bad: Weak password validation
if (password.length < 6) {
return 'Password too short';
}
// Good: Strong password validation
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$/;
if (!passwordRegex.test(password)) {
return 'Password must be 8+ chars with uppercase, lowercase, and number';
}
// Good: MFA
app.post('/login', async (req, res) => {
const { username, password, totpCode } = req.body;
// Verify password
const user = await verifyPassword(username, password);
if (!user) return res.status(401).json({ error: 'Invalid credentials' });
// Verify MFA
if (!verifyTOTP(user.totpSecret, totpCode)) {
return res.status(401).json({ error: 'Invalid MFA code' });
}
// Create session
req.session.userId = user.id;
res.json({ success: true });
});
Best Practices
| Practice |
Description |
| Strong password policy |
Enforce complexity |
| MFA required |
Require 2FA for sensitive actions |
| Secure session management |
Use secure session tokens |
| Rate limiting |
Limit login attempts |
| Secure password reset |
Use time-limited, single-use tokens |
| Credential stuffing protection |
Detect and block reused credentials |
A08: Software and Data Integrity Failures
Description
Code or infrastructure without integrity verification, allowing supply chain attacks or unauthorized modifications.
Examples
| Scenario |
Description |
| Supply chain attack |
Malicious npm package |
| Unsigned updates |
No verification of updates |
| Insecure CI/CD |
Unauthorized code changes |
| Missing code signing |
Executables not signed |
| Subresource integrity |
CDN resources not verified |
Prevention
# Verify package integrity
npm install --package-lock-only
# Use npm audit
npm audit
# Use SRI (Subresource Integrity)
<script src="https://cdn.example.com/script.js"
integrity="sha384-abc123..."
crossorigin="anonymous"></script>
Best Practices
| Practice |
Description |
| Code signing |
Sign executables and packages |
| Verify dependencies |
Check lockfiles |
| Secure CI/CD |
Require approvals, sign artifacts |
| Supply chain security |
Vet dependencies |
| SRI |
Use subresource integrity for CDN resources |
| Immutable infrastructure |
Don't modify running systems |
A09: Security Logging and Monitoring Failures
Description
Insufficient logging, lack of alerting on suspicious activity, or missing audit trails.
Examples
| Scenario |
Description |
| No login logging |
Can't detect unauthorized access |
| No error logging |
Can't diagnose issues |
| No alerting |
Attacks go unnoticed |
| Insufficient logs |
Missing critical events |
| Logs not reviewed |
Attacks detected but ignored |
Prevention
// Good: Comprehensive logging
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
// Log security events
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const ip = req.ip;
try {
const user = await verifyPassword(username, password);
if (user) {
logger.info('Login successful', { username, ip, userId: user.id });
req.session.userId = user.id;
res.json({ success: true });
} else {
logger.warn('Login failed', { username, ip });
res.status(401).json({ error: 'Invalid credentials' });
}
} catch (error) {
logger.error('Login error', { username, ip, error: error.message });
res.status(500).json({ error: 'Internal error' });
}
});
Best Practices
| Practice |
Description |
| Log security events |
Logins, failures, privilege changes |
| SIEM integration |
Centralize logs |
| Real-time alerting |
Alert on suspicious activity |
| Log retention |
Keep logs for investigation |
| Log review |
Regularly review logs |
| Audit trails |
Track who did what |
A10: Server-Side Request Forgery (SSRF)
Description
Application fetches remote resource without validating user-supplied URL, allowing attackers to access internal resources.
Examples
| Scenario |
Description |
| Internal network access |
Access internal APIs |
| Cloud metadata |
Read cloud instance metadata |
| Port scanning |
Scan internal network |
| File access |
Access internal files |
Prevention
// Bad: No URL validation
app.get('/fetch', async (req, res) => {
const url = req.query.url;
const response = await axios.get(url); // Fetches any URL!
res.json(response.data);
});
// Good: URL whitelist
const ALLOWED_HOSTS = ['api.example.com', 'cdn.example.com'];
app.get('/fetch', async (req, res) => {
const url = req.query.url;
const parsed = new URL(url);
if (!ALLOWED_HOSTS.includes(parsed.hostname)) {
return res.status(400).json({ error: 'Invalid URL' });
}
const response = await axios.get(url);
res.json(response.data);
});
// Good: Use URL library for validation
const { URL } = require('url');
app.get('/fetch', async (req, res) => {
const url = req.query.url;
const parsed = new URL(url);
// Block private IPs
if (parsed.hostname.startsWith('192.168.') ||
parsed.hostname.startsWith('10.') ||
parsed.hostname.startsWith('127.')) {
return res.status(400).json({ error: 'Invalid URL' });
}
const response = await axios.get(url);
res.json(response.data);
});
Best Practices
| Practice |
Description |
| URL whitelist |
Only allow specific hosts |
| Validate URLs |
Parse and validate URLs |
| Network segmentation |
Restrict outbound access |
| Disable unnecessary protocols |
Block file://, ftp:// |
| Use allowlist |
Allow only what's needed |
| Cloud metadata protection |
Block metadata endpoints |
Testing for OWASP Top 10
Manual Testing
| Test Type |
What to Check |
| Access control |
Try accessing others' data |
| Injection |
Try SQL injection payloads |
| Authentication |
Test weak passwords, no MFA |
| SSRF |
Try internal URLs |
| Misconfiguration |
Check for default credentials |
Automated Scanning
| Tool |
Type |
What It Finds |
| OWASP ZAP |
DAST |
Web vulnerabilities |
| Burp Suite |
DAST |
Web vulnerabilities |
| Nessus |
Vulnerability scanner |
Known vulnerabilities |
| SonarQube |
SAST |
Code vulnerabilities |
| Snyk |
Dependency scanner |
Vulnerable dependencies |
Code Review
What to Look For:
- User input handling
- Authentication/authorization checks
- Cryptographic usage
- Error handling
- Logging
Prevention Checklist
Input Validation
| Practice |
Status |
| [ ] Validate all user input |
|
| [ ] Use allowlists (not blocklists) |
|
| [ ] Sanitize input for context |
|
| [ ] Validate data types |
|
| [ ] Check input length |
|
Output Encoding
| Practice |
Status |
| [ ] Encode HTML output (prevent XSS) |
|
| [ ] Encode URL parameters |
|
| [ ] Encode JSON output |
|
| [ ] Use template engines with auto-escaping |
|
Parameterized Queries
| Practice |
Status |
| [ ] Never concatenate SQL |
|
| [ ] Use parameterized queries |
|
| [ ] Use ORM when possible |
|
| [ ] Use least-privileged DB user |
|
Authentication and Authorization
| Practice |
Status |
| [ ] Check auth on every request |
|
| [ ] Implement authorization checks |
|
| [ ] Use strong password hashing |
|
| [ ] Require MFA for sensitive actions |
|
| [ ] Implement rate limiting |
|
HTTPS Everywhere
| Practice |
Status |
| [ ] Use HTTPS everywhere |
|
| [ ] Redirect HTTP to HTTPS |
|
| [ ] Use HSTS |
|
| [ ] Use strong TLS configuration |
|
Security Headers
| Header |
Purpose |
| Content-Security-Policy |
Prevent XSS |
| Strict-Transport-Security |
Force HTTPS |
| X-Content-Type-Options |
Prevent MIME sniffing |
| X-Frame-Options |
Prevent clickjacking |
| X-XSS-Protection |
XSS protection |
// Using Helmet.js
const helmet = require('helmet');
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
}
}));
Secure Coding Practices
Principles
| Principle |
Description |
| Principle of least privilege |
Grant minimum necessary access |
| Defense in depth |
Multiple security layers |
| Fail securely |
Errors don't expose info |
| Keep it simple |
Complexity = bugs |
| Don't trust input |
Validate and sanitize |
Secure Coding Examples
// Bad: Trust user input
const filename = req.params.filename;
fs.readFile(`/uploads/${filename}`, (err, data) => {
// Path traversal vulnerability!
});
// Good: Validate and sanitize
const filename = req.params.filename;
const safeFilename = path.basename(filename); // Remove path traversal
const safePath = path.join('/uploads', safeFilename);
fs.readFile(safePath, (err, data) => {
// Safe!
});
Developer Security Training
Training Topics
| Topic |
Description |
| OWASP Top 10 |
Understand common vulnerabilities |
| Secure coding |
Learn secure coding practices |
| Common patterns |
Recognize vulnerability patterns |
| Tools |
Learn to use security tools |
| Updates |
Stay current on threats |
Training Methods
| Method |
Frequency |
| Workshops |
Quarterly |
| Online courses |
Ongoing |
| Code reviews |
Continuous |
| Security newsletters |
Weekly |
| Conferences |
Annually |
Tools for OWASP Top 10
SAST (Static Application Security Testing)
| Tool |
Description |
Pricing |
| SonarQube |
Code quality + security |
Free/$ |
| Checkmarx |
Enterprise SAST |
$$$ |
| Fortify |
Enterprise SAST |
$$$ |
| Semgrep |
Open-source SAST |
Free |
DAST (Dynamic Application Security Testing)
| Tool |
Description |
Pricing |
| OWASP ZAP |
Free DAST tool |
Free |
| Burp Suite |
Professional DAST |
$$$ |
| Acunetix |
Web vulnerability scanner |
$$$ |
| Netsparker |
Web vulnerability scanner |
$$$ |
Dependency Scanning
| Tool |
Description |
Pricing |
| Snyk |
Dependency vulnerability scanner |
Free/$ |
| Dependabot |
GitHub dependency scanning |
Free |
| WhiteSource |
Dependency scanning |
$$ |
| npm audit |
Node.js dependency scanning |
Free |
Real Vulnerability Examples
Example 1: SQL Injection in Login Form
Vulnerable Code:
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const query = `SELECT * FROM users WHERE username = '${username}' AND password = '${password}'`;
const user = await db.query(query);
// SQL injection possible!
});
Attack:
username: ' OR '1'='1
password: anything
Fix:
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const query = 'SELECT * FROM users WHERE username = $1 AND password = $2';
const user = await db.query(query, [username, password]);
// Safe!
});
Example 2: XSS in Comment Section
Vulnerable Code:
app.get('/comments', async (req, res) => {
const comments = await db.query('SELECT * FROM comments');
res.render('comments', { comments }); // No escaping!
});
Attack:
<script>alert('XSS')</script>
Fix:
app.get('/comments', async (req, res) => {
const comments = await db.query('SELECT * FROM comments');
res.render('comments', { comments }); // Template engine auto-escapes
});
Example 3: Broken Access Control in API
Vulnerable Code:
app.get('/api/user/:id', async (req, res) => {
const user = await db.query(`SELECT * FROM users WHERE id = ${req.params.id}`);
res.json(user); // No authorization check!
});
Attack:
GET /api/user/123 // Access user 123's data as user 456
Fix:
app.get('/api/user/:id', async (req, res) => {
if (req.params.id !== req.user.id && !req.user.isAdmin) {
return res.status(403).json({ error: 'Forbidden' });
}
const user = await db.query(`SELECT * FROM users WHERE id = ${req.params.id}`);
res.json(user);
});
Summary Checklist
Before Development
During Development
Before Deployment
---
## Quick Start
### OWASP Top 10 (2021) Overview
1. **A01: Broken Access Control** - Validate authorization
2. **A02: Cryptographic Failures** - Use strong encryption
3. **A03: Injection** - Use parameterized queries
4. **A04: Insecure Design** - Security by design
5. **A05: Security Misconfiguration** - Secure defaults
6. **A06: Vulnerable Components** - Update dependencies
7. **A07: Authentication Failures** - Strong authentication
8. **A08: Software and Data Integrity** - Verify integrity
9. **A09: Security Logging Failures** - Comprehensive logging
10. **A10: SSRF** - Validate server requests
### Prevent Injection
```javascript
// ❌ Bad - SQL injection
const query = `SELECT * FROM users WHERE id = ${userId}`
// ✅ Good - Parameterized query
const query = 'SELECT * FROM users WHERE id = ?'
db.query(query, [userId])
Production Checklist
Anti-patterns
❌ Don't: Broken Access Control
// ❌ Bad - No authorization check
app.get('/api/users/:id', (req, res) => {
const user = db.getUser(req.params.id) // Anyone can access!
res.json(user)
})
// ✅ Good - Authorization check
app.get('/api/users/:id', authenticate, (req, res) => {
if (req.user.id !== req.params.id && !req.user.isAdmin) {
return res.status(403).json({ error: 'Forbidden' })
}
const user = db.getUser(req.params.id)
res.json(user)
})
❌ Don't: SQL Injection
// ❌ Bad - String concatenation
const query = `SELECT * FROM users WHERE email = '${email}'`
// ✅ Good - Parameterized query
const query = 'SELECT * FROM users WHERE email = ?'
db.query(query, [email])
Integration Points
- Secure Coding (
24-security-practices/secure-coding/) - Secure coding practices
- Security Audit (
24-security-practices/security-audit/) - Security reviews
- Vulnerability Management (
24-security-practices/vulnerability-management/) - Vulnerability handling
Further Reading
After Deployment