docker run -t ghcr.io/zaproxy/zaproxy:stable zap-baseline.py -t https://your-app.com| # | Vulnerability | Risk | Vulnerable Code | Secure Code |
|---|---|---|---|---|
| A01 | Broken Access Control | Critical | if (user.role === 'admin') // client-side only | authorize(req.user, resource, 'write') // server-side RBAC |
| A02 | Cryptographic Failures | High | md5(password) or http:// for sensitive data | bcrypt.hash(password, 12) + TLS 1.2+ everywhere |
| A03 | Injection (SQL, XSS, CMD) | Critical | db.query("SELECT * FROM users WHERE id=" + id) | db.query("SELECT * FROM users WHERE id=$1", [id]) |
| A04 | Insecure Design | High | No rate limiting on login; no threat model | Threat model in design phase; rate limit + account lockout |
| A05 | Security Misconfiguration | High | Default admin credentials; verbose errors in prod | Harden configs; disable stack traces; remove default accounts |
| A06 | Vulnerable Components | High | "lodash": "^3.0.0" (unpinned, known CVE) | npm audit fix; pin versions; Dependabot enabled |
| A07 | Auth Failures | High | session.maxAge = null (no expiry) | Short-lived sessions + MFA + bcrypt + constant-time compare |
| A08 | Software/Data Integrity | High | <script src="cdn/lib.js"> (no SRI) | <script src="..." integrity="sha384-..." crossorigin> |
| A09 | Logging Failures | Medium | No logging of failed logins or access denials | Structured logging of auth events with alerting |
| A10 | SSRF | High | fetch(req.body.url) (unvalidated) | Allowlist URLs; block private IP ranges; DNS checks |
START
|-- Is the app handling user authentication/sessions?
| |-- YES --> Check A07 (Auth Failures) + A01 (Broken Access Control) first
| +-- NO (static site / read-only API) --> Skip A07, focus on A03 + A05
|
|-- Does the app accept user input (forms, APIs, file uploads)?
| |-- YES --> Prioritize A03 (Injection) + A01 (Access Control)
| +-- NO --> Focus on A02 (Crypto) + A05 (Misconfiguration)
|
|-- Does the app fetch external resources or URLs?
| |-- YES --> Check A10 (SSRF) + A08 (Integrity Failures)
| +-- NO --> Skip A10
|
|-- Does the app use third-party packages/libraries?
| |-- YES --> Check A06 (Vulnerable Components) -- run npm audit / pip audit
| +-- NO --> Skip A06
|
+-- DEFAULT --> Run full OWASP ZAP baseline scan + manual code review
Start with OWASP ZAP to identify low-hanging fruit across all 10 categories. [src4]
# Run ZAP baseline scan against your application
docker run -t ghcr.io/zaproxy/zaproxy:stable zap-baseline.py \
-t https://your-app.com \
-r report.html
Verify: Open report.html in a browser -- expected: HTML report with findings categorized by risk level.
Review every endpoint for authorization checks. Ensure deny-by-default at the middleware level. [src1]
// Express.js: middleware-level authorization
function authorize(requiredRole) {
return (req, res, next) => {
if (!req.user || !req.user.roles.includes(requiredRole)) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
}
app.delete('/api/users/:id', authorize('admin'), deleteUser);
Verify: curl -X DELETE /api/users/1 -H "Authorization: Bearer <non-admin>" -- expected: 403 Forbidden.
Replace all string-concatenated queries with parameterized queries. Run SAST to find remaining instances. [src2]
# Find potential SQL injection in Node.js codebase
grep -rn "query\s*(" --include="*.js" --include="*.ts" | grep -v "\$\|?\|@"
# Run Semgrep with OWASP rules
npx semgrep --config=p/owasp-top-ten .
Verify: npx semgrep --config=p/owasp-top-ten . -- expected: no critical injection findings.
Run dependency audits in your CI/CD pipeline to catch vulnerable components before deployment. [src2]
# Node.js
npm audit --audit-level=high
# Python
pip install pip-audit && pip-audit
# Java/Maven
mvn org.owasp:dependency-check-maven:check
Verify: npm audit -- expected: found 0 vulnerabilities.
Ensure passwords use bcrypt/scrypt/argon2, all traffic uses TLS 1.2+, and no sensitive data is stored in plaintext. [src1]
# Check TLS configuration
nmap --script ssl-enum-ciphers -p 443 your-app.com
Verify: Output shows only TLS 1.2+ with AES-GCM or ChaCha20 cipher suites.
Configure structured logging for all authentication events, access denials, and input validation failures. [src2]
// Structured security event logging
const securityLog = {
event: 'AUTH_FAILURE',
timestamp: new Date().toISOString(),
ip: req.ip,
userAgent: req.headers['user-agent'],
username: req.body.username, // never log passwords
reason: 'invalid_credentials'
};
logger.warn(securityLog);
Verify: grep "AUTH_FAILURE" /var/log/app.log | tail -5 -- expected: structured JSON entries for each failed login.
// VULNERABLE: No ownership check -- any user can read any doc
app.get('/api/documents/:id', async (req, res) => {
const doc = await db.query('SELECT * FROM docs WHERE id=$1', [req.params.id]);
res.json(doc.rows[0]);
});
// SECURE: Server-side ownership verification
app.get('/api/documents/:id', authenticate, async (req, res) => {
const doc = await db.query(
'SELECT * FROM docs WHERE id=$1 AND owner_id=$2',
[req.params.id, req.user.id]
);
if (!doc.rows[0]) return res.status(404).json({ error: 'Not found' });
res.json(doc.rows[0]);
});
// VULNERABLE: String concatenation in SQL query
const result = await db.query(
`SELECT * FROM users WHERE name = '${req.query.name}'`
);
// SECURE: Parameterized query
const result = await db.query(
'SELECT * FROM users WHERE name = $1',
[req.query.name]
);
// VULNERABLE: Weak session config
app.use(session({
secret: 'keyboard cat',
cookie: { secure: false },
resave: true
}));
// SECURE: Hardened session config
app.use(session({
secret: process.env.SESSION_SECRET,
cookie: { secure: true, httpOnly: true, sameSite: 'strict', maxAge: 1800000 },
resave: false,
saveUninitialized: false
}));
# VULNERABLE: No authorization check
@app.route('/admin/users', methods=['DELETE'])
def delete_user():
user_id = request.args.get('id')
db.execute('DELETE FROM users WHERE id = %s', (user_id,))
return jsonify(success=True)
# SECURE: Role-based authorization decorator
@app.route('/admin/users', methods=['DELETE'])
@login_required
@require_role('admin')
def delete_user():
user_id = request.args.get('id')
db.execute('DELETE FROM users WHERE id = %s', (user_id,))
return jsonify(success=True)
# VULNERABLE: Unvalidated URL fetch
@app.route('/fetch-url')
def fetch_url():
url = request.args.get('url')
response = requests.get(url) # Can access internal services!
return response.text
# SECURE: URL allowlisting + IP validation
import ipaddress, urllib.parse, socket
ALLOWED_HOSTS = {'api.example.com', 'cdn.example.com'}
@app.route('/fetch-url')
def fetch_url():
url = request.args.get('url')
parsed = urllib.parse.urlparse(url)
if parsed.hostname not in ALLOWED_HOSTS: abort(400)
if parsed.scheme != 'https': abort(400)
ip = socket.gethostbyname(parsed.hostname)
if ipaddress.ip_address(ip).is_private: abort(400)
return requests.get(url, timeout=5).text
// BAD -- authorization check in the browser; user can edit localStorage
if (localStorage.getItem('role') === 'admin') {
showAdminPanel();
}
// GOOD -- server enforces authorization on every request
app.get('/admin', authorize('admin'), (req, res) => {
res.json(getAdminData());
});
# BAD -- fast hash, trivially reversible with rainbow tables
import hashlib
password_hash = hashlib.md5(password.encode()).hexdigest()
# GOOD -- slow adaptive hash, salt built-in
import bcrypt
password_hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
// BAD -- allows SSRF to internal services
const response = await fetch(req.body.url);
// GOOD -- validate scheme, host, and resolved IP
const url = new URL(req.body.url);
if (!ALLOWED_HOSTS.includes(url.hostname)) throw new Error('Blocked');
if (url.protocol !== 'https:') throw new Error('HTTPS required');
const { address } = await dns.resolve4(url.hostname);
if (isPrivateIP(address)) throw new Error('Private IP blocked');
<!-- BAD -- no integrity verification -->
<script src="https://cdn.example.com/lib.js"></script>
<!-- GOOD -- browser verifies hash before executing -->
<script src="https://cdn.example.com/lib.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/..."
crossorigin="anonymous"></script>
npm audit but ignore warnings or misconfigure flags. Fix: npm audit --audit-level=high && exit 1 in CI. [src2]password, token, cookie fields from log serialization. [src2]# Run OWASP ZAP baseline scan (Docker)
docker run -t ghcr.io/zaproxy/zaproxy:stable zap-baseline.py -t https://your-app.com
# Scan Node.js dependencies for known CVEs (A06)
npm audit --audit-level=high
# Scan Python dependencies (A06)
pip-audit --strict
# Run Semgrep with OWASP rules (A03 Injection + more)
npx semgrep --config=p/owasp-top-ten .
# Scan Docker images for vulnerabilities (A06)
docker scout cves your-image:latest
# Check TLS configuration (A02)
nmap --script ssl-enum-ciphers -p 443 your-app.com
# Scan with Snyk (A06 + A03)
npx snyk test --severity-threshold=high
# Check HTTP security headers (A05)
curl -sI https://your-app.com | grep -iE "strict-transport|content-security|x-frame|x-content-type"
| Version | Status | Breaking Changes | Migration Notes |
|---|---|---|---|
| OWASP Top 10:2021 | Current | 3 new categories; 4 renamed; XSS merged into A03 | Map 2017 A4 (XXE) to 2021 A05; A8 to A08; A10 to A09 |
| OWASP Top 10:2017 | Superseded | Added Insecure Deserialization (A8), Insufficient Logging (A10) | XSS was standalone A7; now part of Injection A03 |
| OWASP Top 10:2013 | Legacy | -- | First to include Components with Known Vulnerabilities |
| OWASP Top 10:2025 (Draft) | Draft | Proposed AI/LLM risk updates | Not ratified -- do not use as authoritative yet |
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Starting a new web app security program | Building mobile-only applications | OWASP Mobile Top 10 (2024) |
| Training developers on common vulnerabilities | Securing REST/GraphQL APIs | OWASP API Security Top 10 (2023) |
| Performing security code reviews | Meeting specific compliance requirements | PCI DSS Req 6, ISO 27001 A.14 |
| Setting up security scanning in CI/CD | Evaluating cloud infrastructure | OWASP Cloud-Native Top 10 |
| Threat modeling during design phase | Network-level penetration testing | OWASP Testing Guide |