Skip to content
AyoKoding

Overview

Prerequisites

  • Prior topics: 4 · Just Enough Python -- every script in this topic is fully type-annotated Python, and you should already be comfortable reading functions, classes, exceptions, and list/dict literals the way that primer taught them; 10 · SQL Essentials -- this topic assumes you already know what a parameterized query looks like and why string-formatting SQL is dangerous, because the SQL-injection examples here go one step further and actually exploit the naive version before fixing it; 11 · Backend Essentials -- the small HTTP JSON service you built there (routes, request parsing, a SQLite-backed repository, sessions/tokens) is exactly the shape of application every attack and every fix in this topic targets, and the capstone below hardens that same shape of service end to end.
  • Tools & environment: a macOS/Linux terminal; Python 3.13.12; curl 8.7.1+ to send malicious/edge requests and inspect response headers; Flask 3.1.3 (bundling Jinja2 3.1.6, Werkzeug 3.1.8, MarkupSafe 3.0.3, itsdangerous 2.2.0) as the small self-contained app framework every example runs against; stdlib sqlite3, hmac, hashlib, subprocess, os; argon2-cffi 25.1.0 and bcrypt 5.0.0 for password hashing; PyJWT 2.13.0 (pin >=2.12.0 -- versions up to 2.11.0 carry CVE-2026-32597, a crit-header validation gap fixed in 2.12.0) for tokens; pydantic 2.13.4 for boundary validation; pip-audit 2.10.1 and detect-secrets 1.5.0 (no new PyPI release since 2024-05-06 -- maintained-but-slow upstream, not abandoned) for dependency/secret scanning; cyclonedx-bom 7.3.0 (installs the cyclonedx-py CLI) for SBOM generation; flask-limiter 4.1.1 plus fakeredis 2.36.2 (an in-memory Redis stand-in, so the distributed-rate-limit example needs no real Redis server) for throttling; git 2.39.5+ for the secret-scanning pre-commit hook example; cryptography 49.0.0 (the pyca/cryptography library) for real RSA key-pair generation and X.509 certificate/PEM handling in the JWT RS256 and TLS self-signed-certificate examples (ex-39, ex-51), and transitively required by Werkzeug's ssl_context="adhoc" ad-hoc HTTPS certificate generation (ex-52); requests 2.34.2 -- the real HTTP client every exploit_and_fix.py / hammer.py / attack script uses to drive the live Flask server.
  • Assumed knowledge: reading and writing typed Python functions and classes; issuing HTTP requests with curl (method, headers, body, -I for headers-only); a basic SQL SELECT/INSERT and what a parameterized query placeholder (?/%s) does instead of string-formatting (topic 10); how an HTTP request reaches a handler function and what a request/response cycle looks like (topic 11). No prior security background is required -- this topic is where that background starts.

Why this exists -- the big idea

The problem before the solution: the service you just learned to build (topic 11) trusts its inputs by default -- every query parameter, form field, header, cookie, and JSON body arrives exactly as an attacker chooses to send it, and code that treats that data as safe is itself the vulnerability. The one idea worth keeping if you forget everything else: validate at the boundary and grant least privilege -- treat every input as hostile until proven safe, and give every component (a user, a process, a database account) only the access it actually needs, so a single mistake has a bounded blast radius instead of an unbounded one.

Cross-cutting big ideas, taught here and then reused for the rest of this topic: layering-and-leaks -- a vulnerability is a trust boundary that leaked: SQL injection is the data layer bleeding into the code layer, XSS is untrusted data bleeding into the rendering layer, SSRF is application logic bleeding into the network layer the operator never meant to expose. mechanism-vs-policy -- authentication is the mechanism (proving who you are); authorization is the policy laid over it (deciding what that identity may do). Every authentication bug in this topic (session fixation, a forged JWT) is a broken mechanism; every authorization bug (IDOR, missing function-level access control) is a broken or missing policy on top of a mechanism that may be working perfectly.

Install and run your first example

Confirm the core toolchain this topic's Beginner tier uses is installed:

$ python3 --version
Python 3.13.12
$ curl --version | head -1
curl 8.7.1 (x86_64-apple-darwin24.0) libcurl/8.7.1 ...
$ python3 -c "import sqlite3, hmac, hashlib; print('stdlib security primitives OK')"
stdlib security primitives OK

Install the packages every tier needs with one pip command (ideally into a virtual environment):

pip install flask==3.1.3 argon2-cffi==25.1.0 "bcrypt==5.0.0" "PyJWT>=2.12.0,==2.13.0" \
  pydantic==2.13.4 pip-audit==2.10.1 detect-secrets==1.5.0 cyclonedx-bom==7.3.0 \
  flask-limiter==4.1.1 fakeredis==2.36.2 requests==2.34.2 cryptography==49.0.0 pytest==9.1.1

A note on versions: this topic's examples were authored and verified against these exact package versions, in this sandbox, on 2026-07-15. PyJWT is pinned to >=2.12.0 deliberately -- versions up to and including 2.11.0 carry CVE-2026-32597 (a crit-header validation gap; see ex-38 to ex-40 and the topic overview's Tools & environment note).

Every example is a complete, self-contained runnable file colocated under learning/code/, actually executed to capture its documented output. Where an example demonstrates an attack, the attack is run for real against a small, throwaway, local Flask + SQLite app first and shown succeeding, before the fix is applied and the exact same attack is re-run and shown failing -- never a fabricated transcript standing in for either half.

How this topic's examples are organized

  • Beginner (Examples 1-27) -- trust boundaries and the OWASP Top 10:2025 risk map, SQL injection (live exploit through parameterized fix through UNION exfiltration), command injection, path traversal, reflected and stored XSS, output encoding by context, allow-list validation, the password-storage evolution from plaintext through MD5 to argon2id and bcrypt, salting, timing-safe comparison, secure cookie flags, secrets in env vars, security headers, a first pip-audit run and remediation, safe error messages, and second-order/ORM-fragment SQL injection.
  • Intermediate (Examples 28-49) -- blind boolean SQL injection, argument injection beyond the shell, DOM-based XSS, CSP blocking inline scripts, mass-assignment privilege escalation, IDOR, missing function-level authorization, the authn/authz separation, session fixation, session-vs-token trade-offs, every major JWT pitfall (alg:none, algorithm confusion, expiry/claims), a live CSRF exploit and its SameSite/token fixes, CORS misconfiguration and correct preflight handling, open redirect, rate limiting and account-lockout trade-offs, constant-time login responses, and security-misconfiguration debug mode.
  • Advanced (Examples 50-80) -- directory listing and default credentials, TLS verification, HSTS, secret scanning and rotation, dependency pinning and typosquat detection, structured security logging and brute-force alerting, insecure design vs. implementation bugs, threat modeling a feature, an end-to-end injection audit, SSTI, argon2 parameter tuning, password-hash upgrade-on-login, token revocation, RBAC/ABAC authorization, least-privilege DB accounts, defense in depth for XSS, CSRF for JSON/SPA clients, clickjacking, secure file upload, SSRF-safe outbound fetches, distributed rate limiting, audit-log integrity, dependency CVE triage, SBOM generation, secrets-manager integration, and a full-app before/after hardening transcript with a red-to-green security regression test suite.
  • Capstone -- the Backend-Essentials service hardened end to end: an injectable query fixed, argon2/bcrypt-backed auth added, allow-list validation and output encoding applied, secrets moved to env vars, security headers added, and a clean pip-audit run -- with a documented before/after attack transcript.

The 28 concepts this topic covers

  • co-01 · Trust boundaries -- never trust input -- every place untrusted data crosses into your code (query, form, header, cookie, upstream response) is a boundary where it must be validated; the default posture is distrust. Examples 1, 3, 6-8, 12, 61, 72, 78.
  • co-02 · The OWASP Top 10 as a risk map -- the OWASP Top 10:2025 is a shared risk vocabulary that maps a concrete bug to a named category, prioritizing what to defend first. Examples 2, 60, 78-79.
  • co-03 · SQL injection and parameterized queries -- concatenating untrusted input into SQL lets an attacker rewrite the query; bound parameters send data and code separately so input can never become SQL. Examples 3-5, 26-28, 61, 67.
  • co-04 · Command injection -- passing untrusted input to a shell (os.system, shell=True) lets it inject commands; subprocess.run([...], shell=False) with an argv list removes the shell. Examples 6, 29, 61.
  • co-05 · Path traversal -- ../ sequences in a filename escape the intended directory; canonicalizing (realpath) and enforcing a root prefix contains it. Examples 7, 71.
  • co-06 · XSS and output encoding -- untrusted data rendered into a page executes as script unless encoded for its exact output context (HTML body, attribute, JS string, URL). Examples 8-10, 30-31, 62, 68.
  • co-07 · Allow-list vs. deny-list validation -- validating against a list of known-good values is robust; blocklists of known-bad inputs always miss the case you didn't foresee. Examples 11-12, 29, 32, 68, 71.
  • co-08 · Mass assignment and over-posting -- binding a whole request body onto a model (Model(**json)) lets a client set fields it shouldn't (e.g. is_admin); an explicit field allow-list stops it. Example 32.
  • co-09 · Password hashing -- argon2id and bcrypt -- store passwords only as slow, salted one-way hashes (argon2id or bcrypt) -- never plaintext, MD5, or SHA-1. Examples 13-16, 63-64.
  • co-10 · Salting and why -- a unique per-hash salt makes identical passwords hash differently and defeats precomputed (rainbow-table) attacks; modern hashers salt automatically. Examples 14, 17, 64.
  • co-11 · Timing-safe comparison -- comparing secrets with == leaks length/prefix via timing; hmac.compare_digest compares in constant time. Examples 18, 48.
  • co-12 · Session vs. token auth -- server-side sessions (revocable, stateful) versus stateless tokens (scalable, hard to revoke) are a deliberate trade-off, not interchangeable defaults. Examples 36-37, 65.
  • co-13 · Secure cookie flags -- Secure, HttpOnly, and SameSite on session cookies stop transport leakage, JS theft, and cross-site sending respectively. Examples 19, 42, 68.
  • co-14 · JWT-specific pitfalls -- JWTs fail open on alg:none, algorithm confusion (HS256/RS256), and unvalidated exp/aud/iss; pin the algorithm and validate every claim. Examples 37-40, 65.
  • co-15 · Authentication vs. authorization -- authentication proves who you are; authorization decides what you may do -- separate checks, both required. Examples 33-35, 66.
  • co-16 · Least-privilege access control -- every user, process, and DB account gets only the access it needs, so a compromise's blast radius is bounded. Examples 33-34, 66-67.
  • co-17 · Secret hygiene -- secrets live in the environment or a manager, never in code or git history; leaked secrets are rotated, .env is gitignored. Examples 20-21, 53-54, 77.
  • co-18 · HTTPS/TLS in practice -- TLS protects data in transit only when certificates are actually verified; disabling verification reopens MITM. Examples 51-52.
  • co-19 · Security headers -- Content-Security-Policy, Strict-Transport-Security, X-Content-Type-Options, and X-Frame-Options/frame-ancestors are cheap, high-leverage browser-enforced defenses. Examples 22, 31, 52, 68, 70.
  • co-20 · CORS configuration -- CORS relaxes the same-origin policy; reflecting the request Origin with credentials lets any site read responses -- allow-list origins instead. Examples 43-44, 69.
  • co-21 · Dependency safety and supply chain -- third-party code is attack surface; pin exact versions, audit with pip-audit, watch for typosquats, keep the tree CVE-clean. Examples 23-24, 27, 53, 55-56, 75-76.
  • co-22 · Security logging and alerting -- log authn/authz decisions (who, what, outcome -- never secrets) and alert on anomalies like brute-force bursts. Examples 25, 57-58, 74, 80.
  • co-23 · Safe error handling -- error responses reveal nothing internal; the detail goes to a server-side log, the client gets a generic message. Examples 25, 79-80.
  • co-24 · Security misconfiguration -- insecure defaults (debug mode, directory listing, default creds, verbose errors) are vulnerabilities; hardening the configuration closes them. Examples 49-50, 71, 78.
  • co-25 · Insecure design -- some flaws are in the design, not the code: a correctly-implemented flow can still be exploitable -- threat-model the feature. Examples 56, 59-60, 62, 70, 72, 75.
  • co-26 · CSRF protection -- a state-changing request riding the victim's ambient cookie is forgeable cross-site; anti-CSRF tokens and SameSite cookies bind the request to your site. Examples 41-42, 69.
  • co-27 · Rate limiting and brute-force protection -- throttling and backoff on auth endpoints defeat credential-stuffing/brute-force, weighed against lockout-as-DoS. Examples 46-48, 58, 73.
  • co-28 · Open redirect -- following a user-supplied next=/redirect target blindly sends victims to attacker sites; validate against an allow-list or accept relative paths only. Example 45.

Examples by level

Beginner (Examples 1-27)

Intermediate (Examples 28-49)

Advanced (Examples 50-80)


← Previous: Overview · Next: Beginner Examples

Last updated July 14, 2026

Command Palette

Search for a command to run...