OWASP Top 10 (2021)
The most common application security risks. Knowing these by name and Python-specific examples is table stakes for backend interviews.
A01 — Broken Access Control
Authenticated users perform actions they shouldn’t (horizontal: user A sees user B’s data; vertical: user accesses admin).
# Bad — relies on URL secrecy
@app.get("/orders/{order_id}")
def get_order(order_id: int, user: User = Depends(current_user)):
return Order.get(order_id) # any authenticated user gets any order
# Good — authorization check
@app.get("/orders/{order_id}")
def get_order(order_id: int, user: User = Depends(current_user)):
order = Order.get(order_id)
if order.user_id != user.id and not user.is_admin:
raise HTTPException(403)
return order
Defenses: deny by default, centralize auth checks (decorators / middleware), test the negative cases.
A02 — Cryptographic Failures
Sensitive data exposed in transit or at rest. Examples: HTTP for login forms, SHA-1 password hashes, hard-coded keys.
- Always TLS for anything sensitive in transit.
- Use modern hashes for passwords (argon2, bcrypt) — see 04_password_hashing.md.
- Don’t roll your own crypto. Use
cryptographylibrary; neverpycrypto(unmaintained).
A03 — Injection
SQL, OS command, LDAP, NoSQL, etc.
# SQLi — never
cursor.execute(f"SELECT * FROM users WHERE name = '{name}'")
# parameterize
cursor.execute("SELECT * FROM users WHERE name = %s", (name,))
# Command injection — never
os.system(f"convert {filename} out.png")
# use list form, no shell
subprocess.run(["convert", filename, "out.png"], check=True)
See 02_sql_injection.md.
A04 — Insecure Design
Architectural flaws — no rate limiting on sensitive endpoints, password reset that doesn’t expire, business logic that assumes goodwill (negative quantity in cart).
Mitigation is design-stage: threat modeling, abuse cases, secure-by-default frameworks.
A05 — Security Misconfiguration
Default credentials, debug mode in production, verbose error pages, unnecessary services exposed.
# Django checklist
DEBUG = False # production
ALLOWED_HOSTS = ["api.example.com"] # set explicitly
SECURE_HSTS_SECONDS = 31536000
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = "DENY"
# FastAPI — disable docs in prod
app = FastAPI(
docs_url=None if PROD else "/docs",
redoc_url=None if PROD else "/redoc",
)
Run python manage.py check --deploy (Django) or use bandit for static analysis.
A06 — Vulnerable and Outdated Components
Using libraries with known CVEs.
Tools:
pip-audit— checks installed packages against the Python advisory database.safety— similar; commercial tier.dependabot— GitHub-native, auto-PRs on vulnerable deps.snyk— integrates into CI.
Pin your dependencies (requirements.txt with hashes, poetry.lock, uv.lock). Update on a schedule, not just when something breaks.
A07 — Identification and Authentication Failures
Weak passwords, predictable session IDs, brute-force allowed.
- Strong password requirements (NIST: ≥8 chars, no max, no forced rotation, blocklist common ones).
- Session IDs from
secrets.token_urlsafe(32)— neverrandomoruuid4().hex. - Lock accounts after N failed attempts (or progressive delay).
- Multi-factor auth on critical accounts.
import secrets
session_id = secrets.token_urlsafe(32) # 256 bits of entropy
See 05_oauth2_oidc.md.
A08 — Software and Data Integrity Failures
Trusting unsigned updates, unsafe deserialization, CI pipelines without integrity checks.
# pickle is RCE-as-a-feature
import pickle
data = pickle.loads(untrusted_bytes) # arbitrary code execution
Never pickle.loads data from anyone outside your trust boundary. Use JSON (data only, no code), or for typed payloads use pydantic / msgpack.
For dependencies: pin with hashes.
# requirements.txt with hashes (pip-compile --generate-hashes)
requests==2.31.0 --hash=sha256:...
A09 — Security Logging and Monitoring Failures
Attacks aren’t detected, or take weeks to. No alerts on auth failures, no audit log on privilege changes.
Log (without secrets):
- Auth events: success, failure, lockout.
- Authorization failures: 403 responses.
- Validation errors at boundaries.
- Admin actions: privilege grants, config changes.
import structlog
log = structlog.get_logger()
log.info("auth.failed", username=username, ip=request.client.host, reason="bad_password")
# never log: passwords, tokens, full credit card, raw API keys
Monitor: alerts for spike in 401/403, alerts on unusual admin activity. Centralize logs (Sentry, ELK, Datadog).
A10 — Server-Side Request Forgery (SSRF)
Server fetches a URL the user controls, attacker uses it to reach internal services.
# Bad — fetches whatever URL user provides
@app.post("/preview")
def preview(url: str):
return requests.get(url).text
Attacker calls with http://169.254.169.254/latest/meta-data/iam/security-credentials/ (AWS metadata service) and steals IAM credentials.
Defenses:
- Allowlist of domains/schemes.
- Resolve DNS first, reject private IPs (
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,127.0.0.0/8). - Disable HTTP redirects or re-validate after each redirect.
- Run egress through a proxy that blocks internal addresses (cloud network policy).
import ipaddress, socket
def safe_url(url: str) -> bool:
host = urlparse(url).hostname
addr = ipaddress.ip_address(socket.gethostbyname(host))
if addr.is_private or addr.is_loopback or addr.is_link_local:
return False
return True
Defense-in-depth across all categories
- Input validation at boundaries — pydantic, JSON Schema. Reject malformed early.
- Output encoding — Jinja autoescape, JSON-only responses.
- Least privilege — DB user with only needed grants, IAM roles scoped narrow.
- Secrets in env / vault, never in code. See 07_secrets_rate_limiting.md.
- Static analysis —
banditfor Python,semgrepfor cross-language patterns. - Dependency scanning —
pip-auditin CI.
Interview angle
- Q: “What’s the OWASP Top 10 and how does Python fit in?” — you don’t need to recite all 10; “broken access control + injection + crypto failures + SSRF” covers most real attacks.
- Q: “What’s SSRF?” — server fetches user-controlled URL; can reach internal services. AWS IMDS classic target.
- Follow-up: “How do you defend against injection?” — parameterized queries; output encoding; subprocess list-form.
- Follow-up: “What’s wrong with
pickle.loads(user_input)?” — pickle deserializes arbitrary code; use JSON.
Files in this section: 02_sql_injection.md, 03_xss_csrf.md, 04_password_hashing.md, 05_oauth2_oidc.md, 06_jwt_pitfalls.md, 07_secrets_rate_limiting.md.