Module 4 · Authentication and security

Lesson 22 — OWASP Top 10

SQL injection, XSS, CSRF, SSRF and broken access control — with real examples.

Published
In this lesson
  1. Objectives
  2. 1. A01 — Broken Access Control (the real #1)
  3. 2. A02 — Cryptographic Failures
  4. 3. A03 — Injection (SQL, commands, templates)
  5. 4. A04 — Insecure Design / A05 — Security Misconfiguration
  6. 5. A07 — Identification/Auth failures + A10 SSRF
  7. 6. TicketFlow's inventory (exercise)
  8. 11. Beyond the Top 10: the three attacks the ranking doesn't lead with but that do bite
  9. 12. The response to a vulnerability: the finding's runbook

Stack: Django/DRF · Project: TicketFlow Status: Published Prerequisite: Lesson 21 — Authorization


Objectives

  1. Walk through the OWASP Top 10 (2021) with the concrete attack and the concrete defense on your stack.
  2. Find and close the weaknesses your own project already has (an honest inventory).
  3. Learn the triage: what you fix today, what the framework watches, what a pentest audits.

1. A01 — Broken Access Control (the real #1)

Lesson 21's hole (IDOR, role escalation, frontend-only rules). Defenses already implemented: filtered queryset, object permissions, rules in the service, escalation regression tests. The check that is ALWAYS missing: reviewing each new endpoint against lesson 21's matrix — OWASP is not "fixed once", it is a per-PR discipline.

2. A02 — Cryptographic Failures

Sensitive data unencrypted or with weak encryption:

  • In transit: mandatory TLS (lesson 01's HSTS), nothing sensitive over HTTP.
  • At rest: password hashes with argon2 (20); TOTP secrets encrypted at rest (20); reset tokens hashed.
  • Field encryption: django-fernet-fields/a custom field with a settings/KMS key (23) for what the DB must not read (the buyer's ID document, should the business ask for it).
  • Never: inventing your own "encryption", MD5/SHA1 for anything security, reversible passwords.

3. A03 — Injection (SQL, commands, templates)

  • SQLi: the ORM parameterizes ALWAYS. The hole appears with .raw(), extra() or cursors with f-strings: cursor.execute(f"SELECT... {user_input}") = injection served. Rule: %s parameters always; if dynamic SQL is unavoidable (order by column), a whitelist of identifiers (13).
  • Commands: os.system("convert " + filename) — never: subprocess.run([...], shell=False) with a list, or better: libraries instead of the shell.
  • Template injection: rendering user input with Template(...) executes code. Django templates auto-escape; the risk is using the input as the template.
  • XSS (it fits here in DRF): your API returns JSON and the frontend escapes when rendering (React/Vue do it by default) — the hole appears with dangerouslySetInnerHTML/v-html on user data (the event name with <script>: escape or sanitize with DOMPurify if there is legitimate rich HTML).

4. A04 — Insecure Design / A05 — Security Misconfiguration

Design: the DB invariants (00b/10) and the authorization matrix (21) ARE design security: OWASP rewards preventing at the schema over patching at the view. Misconfiguration — Django's checklist:

python
DEBUG = False                        # the stacktrace leaks settings and paths
ALLOWED_HOSTS = ["ticketflow.app"]   # prevents host header poisoning
SECURE_SSL_REDIRECT, SECURE_HSTS_SECONDS = True, 31536000
SESSION_COOKIE_SECURE, CSRF_COOKIE_SECURE = True, True
SECURE_CONTENT_TYPE_NOSNIFF, SECURE_REFERRER_POLICY = True, "same-origin"
X_FRAME_OPTIONS = "DENY"             # clickjacking

python manage.py check --deploy audits it — put it in the pipeline (41). And DRF's defaults: the browseable API off in production (JSON renderer only) so you don't expose test forms.

5. A07 — Identification/Auth failures + A10 SSRF

Auth: credential stuffing (rate limit 20/23), eternal tokens (18), lax resets (20) — all covered. SSRF (Server-Side Request Forgery): your backend fetches user-supplied URLs (e.g. the organizer's "avatar_url") and an attacker asks for http://169.254.169.254/ (cloud metadata = credentials) or http://localhost:6379/ (your Redis). Defense: domain/scheme whitelist, resolve and block private IPs, never blindly fetch user URLs.

The rest (A06 vulnerable components: pip-audit in CI; A08 deployment integrity: image signatures/pinned dependencies; A09 logging/monitoring: 45/46) is covered in their modules — OWASP is a map, not a sprint.

6. TicketFlow's inventory (exercise)

Your project already has: a parameterized ORM, argon2, HMAC on webhooks, rate limiting, layered authorization, constraints as invariants. Its typical pending holes: check --deploy not passing, browseable API on, some user avatar/image URL (latent SSRF), unsanitized payload logs. The exercises turn them into tickets.

11. Beyond the Top 10: the three attacks the ranking doesn't lead with but that do bite

The Top 10 is a ranking of CATEGORIES; the concrete attacks of backend day-to-day: (1) Mass assignment: the client sends is_staff: true in the profile JSON and the serializer assigns it — the cure: explicit serializers (only the contract's fields, 15) never fields = "__all__" (21 completes it with per-field permissions); (2) Timing attacks: comparing secrets with == leaks through time — the cure: hmac.compare_digest ALWAYS (you already used it in 17/27) and reset responses with uniform timing (20); (3) Dependency confusion / typosquatting: the pip install requests of a typo installs the attacker's package — the cure: a lockfile with hashes (40), a private proxy for internal naming, and 41's pip-audit.

12. The response to a vulnerability: the finding's runbook

Finding (or being reported) a vulnerability is the start of the process, not the end: (1) triage in <24 h: exploitable in YOUR context (40's CVE triage), severity, who decides; (2) containment if it is being exploited: 41's flag/rollback, 23's rate limit, secret revocation (27); (3) fix + regression test (33: the test that reproduces the exploit, now green); (4) responsible disclosure: if it affects users (a breach, 56): the 72 h notification runbook; if not: the discreet changelog; (5) the postmortem (47) with the root question: why didn't the review/pipeline catch it? — and the action that will catch it next time.

The rewards program (security.txt + a security mailbox) turns someone else's report into a gift: TicketFlow's /.well-known/security.txt page with contact, disclosure policy and PGP — 20 lines the honest pentester looks for before reporting to a forum.


  1. Why is OWASP #1 access control and not injection? What discipline sustains it?
  2. Explain to a junior why cursor.execute(f"... {email}") is severe and execute("... %s", [email]) is not.
  3. Your SPA shows event.description that the organizer writes with HTML: where is the XSS risk and what decision do you make (escape vs sanitize)?
  4. Why does SSRF aim at 169.254.169.254 and what does the attacker get if they pull it off?
  5. Which three check --deploy checks would fail in your project today?

Continue with the exercises. The solutions only after trying it yourself.