Backend / Security / 01_owasp_top_10.md

OWASP Top 10 (2025)

Updated 5 min read source
On this page12
  1. A01 — Broken Access Control
  2. A02 — Security Misconfiguration
  3. A03 — Software Supply Chain Failures
  4. A04 — Cryptographic Failures
  5. A05 — Injection
  6. A06 — Insecure Design
  7. A07 — Authentication Failures
  8. A08 — Software or Data Integrity Failures
  9. A09 — Security Logging and Alerting Failures
  10. A10 — Mishandling of Exceptional Conditions
  11. Defense-in-depth across all categories
  12. Interview angle

OWASP Top 10 (2025)

The most common application security risks, in the OWASP Top 10:2025 numbering. The 2021 list differed: Injection was A03, Security Misconfiguration A05, and SSRF had its own entry (A10:2021), now folded into Broken Access Control. 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).

python
# Bad — relies on URL secrecy
@app.get("/orders/{order_id}")
def get_order(order_id: int, user: User = Depends(current_user)):
    # any authenticated user gets any order
    return Order.get(order_id)

# 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.

Server-side request forgery (A10:2021, now part of A01)

Server fetches a URL the user controls, attacker uses it to reach internal services.

python
# 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).

A check like “resolve the host, reject private addresses” is necessary but not sufficient: a hostname can resolve to a public address when you check and a private one when the HTTP client connects (DNS rebinding), and socket.gethostbyname ignores IPv6. Pin the connection to the address you validated, or route through an egress proxy that enforces it. Depth in SSRF and Command Injection.

A02 — Security Misconfiguration

Default credentials, debug mode in production, verbose error pages, unnecessary services exposed.

python
# 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"
python
# 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.

A03 — Software Supply Chain Failures

Was “Vulnerable and Outdated Components” (A06:2021); 2025 widens it to the whole supply chain: compromised packages, typosquatting, build pipelines and dependencies of dependencies.

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.

A04 — 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 Password hashing.
  • Don’t roll your own crypto. Use cryptography library; never pycrypto (unmaintained).

A05 — Injection

Depth, with runnable repros, is in SQL injection.

SQL, OS command, LDAP, NoSQL, etc.

python
# 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 SQL injection.

A06 — 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.

A07 — 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.
python
import secrets
# 256 bits of entropy
session_id = secrets.token_urlsafe(32)

See OAuth 2.0 and OpenID Connect.

A08 — Software or Data Integrity Failures

Trusting unsigned updates, unsafe deserialization, CI pipelines without integrity checks.

python
# pickle is RCE-as-a-feature
import pickle
# arbitrary code execution
data = pickle.loads(untrusted_bytes)

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.

text
# requirements.txt with hashes (pip-compile --generate-hashes)
requests==2.31.0 --hash=sha256:...

A09 — Security Logging and Alerting 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.
python
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 — Mishandling of Exceptional Conditions

New in 2025: what the application does when something goes wrong.

  • Failing open: an exception in an authorization check that is caught and ignored, so the request proceeds; a feature flag service that times out and defaults to “enabled”.
  • Leaking detail: stack traces, SQL errors or internal hostnames in responses.
  • Inconsistent state: an error halfway through a multi-step operation that leaves money moved but the record unwritten.
  • Resource exhaustion on the error path: retries without limits, or an error handler that itself allocates or logs without bound.
python
# Bad: any failure in the check grants access
try:
    allowed = policy.check(user, resource)
except Exception:
    allowed = True

# Good: fail closed, log, and return a generic error
try:
    allowed = policy.check(user, resource)
except PolicyError:
    log.exception("policy check failed", extra={"user": user.id})
    raise HTTPException(503, "Try again later")

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 Secrets management and rate limiting.
  • Static analysis — bandit for Python, semgrep for cross-language patterns.
  • Dependency scanning — pip-audit in CI.

Interview angle 4

  • Q: “What’s the OWASP Top 10 and how does Python fit in?” — you don’t need to recite all 10; broken access control (which now includes SSRF), misconfiguration, supply chain and injection cover 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: SQL injection, XSS and CSRF, Password hashing, OAuth 2.0 and OpenID Connect, JWT pitfalls, Secrets management and rate limiting.