backend / security / 01_owasp_top_10.md

OWASP Top 10 (2021)

4 min read source

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 cryptography library; never pycrypto (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) — never random or uuid4().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 analysisbandit for Python, semgrep for cross-language patterns.
  • Dependency scanningpip-audit in 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.