Warmup Guide — From a Network Packet to an Authorized Data Access

Zero-to-expert primer for Phase 02. It builds every concept the phase depends on from first principles: the network stack (TCP/IP, DNS, HTTP/1.1–3), TLS and PKI, the difference between authentication and authorization, sessions and cookies, OAuth2/OIDC/SAML/SCIM, JWT validation as a protocol, the web attack/defense classes (injection, XSS, CSRF, SSRF, deserialization, traversal/upload), and applied cryptography (AEAD, KMS, envelope encryption). Assumes only basic web programming. By the end you should be able to explain why a valid TLS connection and a valid token are not permission to read a tenant's data, and build authorization that fails closed.

Table of Contents


Chapter 1: The Request Chain — Each Layer Proves a Different Thing

Zero background. When a user reads a row of data in a web app, the request passes through a chain of layers, and each layer proves a different property:

DNS → routing/load balancer → TLS → HTTP parsing → session/token → authorization
    → application validation → database/object store → audit/detection

The single most important idea in this entire phase: evidence from one layer cannot substitute for another.

  • A source IP is not a user identity (IPs are shared, spoofable at some layers, and reassigned).
  • A valid TLS certificate authenticates a server's name — it is not permission to read tenant data.
  • A signed token proves someone issued it — not that it was issued for this service or that the subject may touch this object.

Why this framing matters. The flagship vulnerability of web applications — broken object-level authorization (BOLA/IDOR) — happens precisely when a developer lets a lower-layer proof (the user authenticated) stand in for a higher-layer decision (this user may read object #42). The whole phase trains you to keep the layers distinct.

Misconception to kill now. "They're logged in, so they can see it." Authentication answers who are you; authorization answers may you do this to this resource — and only the second protects the data.

Chapter 2: TCP/IP and DNS from Zero

The layered model. Networking is built in layers so each can be reasoned about independently:

  • Link layer (Ethernet/Wi-Fi) moves frames between machines on the same network segment.
  • Internet layer (IP) routes packets between networks using IP addresses; it is best-effort — packets may be lost, duplicated, or reordered.
  • Transport layer gives applications a usable channel on top of IP:
    • TCP provides a reliable, ordered byte stream via a three-way handshake (SYN, SYN-ACK, ACK), sequence numbers, acknowledgements, and retransmission.
    • UDP is a thin, connectionless datagram service — no ordering or delivery guarantee — used by DNS, QUIC/HTTP3, and real-time media.
  • Application layer (HTTP, etc.) is the meaning your app assigns to the bytes.

Why a security engineer cares about the lower layers. Spoofing, scanning, and segmentation all live here. More importantly, trust boundaries often map to network boundaries (Phase 06/07), and SSRF (Chapter 11) is fundamentally an abuse of the server's network position.

DNS — turning names into addresses, and a trust surface. The Domain Name System resolves a hostname (api.example.com) to an IP address through a hierarchy of resolvers. Security-relevant facts:

  • DNS answers can be cached and have a TTL; the same name can resolve differently over time.
  • DNS rebinding abuses this: an attacker's domain resolves to a benign IP during a security check, then to an internal IP (e.g. 169.254.169.254) when the server actually connects — a classic SSRF-filter bypass (Chapter 11).
  • DNS is also a covert exfiltration channel (data encoded in subdomain queries).

Misconception to kill now. "I validated the hostname, so the connection is safe." The hostname you validated and the address you connect to can differ (rebinding, redirects, multiple records). Validate the resolved address, at connection time, through a controlled resolver.

Chapter 3: HTTP/1.1, HTTP/2, HTTP/3 and Message Boundaries

What HTTP is. A request/response protocol: a method (GET, POST), a target (path), headers (metadata), and an optional body. The server returns a status code, headers, and a body.

The versions, and why they differ:

  • HTTP/1.1 — text-based; one request at a time per connection (with keep-alive). Message length is determined by Content-Length or Transfer-Encoding: chunked. When two hops disagree about which governs, you get request smuggling (Chapter 6 of Phase 01).
  • HTTP/2 — binary, multiplexed (many concurrent streams on one connection), header compression (HPACK). New attack surface: stream resets, header-compression state, and downgrade desync.
  • HTTP/3 — runs over QUIC (UDP-based, with built-in TLS 1.3), eliminating head-of-line blocking at the transport. Same semantic model, different framing.

The security crux: message boundaries and header trust. Reverse proxies, load balancers, CDNs (content delivery networks — geographically distributed caches), WAFs (web application firewalls — rule-based request filters at the edge), and the application must agree on:

  • where one request ends and the next begins (smuggling),
  • the authoritative host and scheme,
  • the real client address (X-Forwarded-For is attacker-controllable unless your edge overwrites it),
  • path normalization (.., encoded slashes, case) — disagreement enables cache poisoning and authorization bypass.

The rule: strip untrusted forwarding/host headers at the edge, normalize paths once, and test the full hop chain, not just the application in isolation.

Misconception to kill now. "X-Forwarded-For tells me the client IP." It tells you what the client (or any intermediary) claimed. Only a header your own trusted edge sets is trustworthy.

Chapter 4: TLS and PKI from First Principles

The problem TLS solves. Over an untrusted network, you need three properties: confidentiality (no eavesdropping), integrity (no tampering), and authentication (you're talking to who you think). TLS provides all three for a single connection.

The handshake, conceptually (TLS 1.3). The client and server: agree on parameters; perform an (elliptic-curve) Diffie-Hellman key exchange so both derive the same secret without ever sending it; the server proves its identity with a certificate; both derive symmetric traffic keys; and all subsequent records are encrypted and authenticated. TLS 1.3 removed legacy weak options and made forward secrecy the default (a compromised long-term key can't decrypt past sessions).

PKI — why you trust the certificate. A certificate binds a public key to a name, signed by a Certificate Authority (CA). Your trust store contains root CAs you trust. Validating a certificate means checking a chain from the server's cert up to a trusted root, plus:

  • the name matches (SNI/hostname vs. Subject Alternative Name) — SNI (Server Name Indication) is the hostname the client announces when the handshake opens, so a server hosting many sites knows which certificate to present,
  • the validity period (not expired/not-yet-valid),
  • revocation status — has this certificate been invalidated before its expiry (key compromise, mis-issuance)? checked via OCSP (Online Certificate Status Protocol, a live per-cert query) or a CRL (Certificate Revocation List, a published list of revoked serials),
  • key usage and basic constraints.

mTLS (mutual TLS). Both peers present certificates, so the server also authenticates the client. Used for service-to-service identity (Phase 07 workload identity). Crucial caveat: mTLS authenticates the peer; it does not authorize the action. A valid client cert still needs an authorization decision about what that service may do.

Misconception to kill now. "We have TLS, so we're secure." TLS protects the transport of one connection. It says nothing about whether the request, once decrypted, is authorized — that's Chapters 5 and 10.

Chapter 5: Authentication vs Authorization

Authentication (authN) establishes an identity claim: this request is from subject S. It can be a password, a token, a certificate, a passkey.

Authorization (authZ) evaluates whether that subject may perform an action on a resource. The full decision is a function of more than identity:

subject + tenant + action + resource + relationship + context + policy version

Broken object-level authorization (BOLA/IDOR) — the flagship bug. A route authenticates the user, then trusts a user-controlled object ID without checking the user's relationship to that object:

GET /api/invoices/8123      # authenticated as user A
                            # but invoice 8123 belongs to tenant B → must be DENIED

The fix is to make every protected route evaluate the full tuple above. The flagship lab (tenant authorization) forces checks on tenant, role, relationship, token issuer, audience, expiry, account state, and data classification — and requires a test for every route × role × tenant combination, so a new endpoint missing from the authorization matrix fails CI.

Why "context" and "policy version" are in the tuple. Context (device risk, time, IP reputation) supports step-up auth. Policy version means decisions are reproducible and auditable — you can say which policy authorized a past action.

Misconception to kill now. "Authorization is a middleware I add once." Object-level authorization is per-object and must cover batch endpoints, search, export, GraphQL resolvers, background jobs, cache hits, counts, and even error messages — every path that returns data.

Chapter 6: Sessions, Cookies, and Ambient Authority

What a session is. After authentication, the server needs to remember the user across requests (HTTP is stateless). It issues a session — either a server-side session referenced by an opaque cookie, or a self-contained token (Chapter 8).

Cookies are ambient authority — the key concept. The browser automatically attaches cookies to requests to the cookie's domain, according to Domain, Path, Secure, HttpOnly, and SameSite. "Ambient" means the credential is sent without the page explicitly choosing to — which is exactly what CSRF abuses (Chapter 11): another origin triggers a request and the browser helpfully attaches your cookie.

The cookie attributes and what each defends:

  • Secure — only sent over HTTPS (no plaintext leak).
  • HttpOnly — not readable by JavaScript (limits XSS cookie theft, though not XSS itself).
  • SameSite=Lax/Strict — restricts cross-site sending (a primary CSRF defense).

XSS vs CSRF — distinguish them. With CSRF, the attacker can cause a request but cannot read the response; defenses bind the request to the intended origin/transaction. With XSS, attacker script runs inside your trusted origin and can act with the user's full browser authority — HttpOnly stops direct cookie reads but not the script making authenticated requests. They are different problems with different fixes.

Session lifecycle (the full list). Creation; rotation after privilege change (re-issue the session ID on login/elevation to prevent fixation); idle and absolute expiry; logout; reset revocation; concurrent-session policy; and risk-based re-auth. Long-lived self-contained tokens make revocation and privacy harder unless you add explicit compensating mechanisms.

Misconception to kill now. "HttpOnly stops XSS." It stops one consequence (cookie theft via JS). The script still runs in your origin and can issue authenticated requests.

Chapter 7: OAuth2 and OpenID Connect Under the Hood

The distinction. OAuth2 is a delegated authorization framework — it lets an app act on a resource on a user's behalf without seeing their password. OpenID Connect (OIDC) is a thin identity layer on top of OAuth2 that adds authentication (an ID token describing who the user is). OAuth2 alone is not an authentication protocol; using an access token as proof of login is a classic mistake.

The authorization-code flow with PKCE (the secure default), step by step:

  1. The app redirects the user's browser to the authorization server with: client_id, exact redirect_uri, requested scope, a random state, and a PKCE code_challenge.
  2. The user authenticates at the authorization server (the app never sees the password).
  3. The server redirects back to redirect_uri with an authorization code and the state.
  4. The app verifies state matches (binds the response to the request — CSRF defense for the flow), then exchanges the code on the back channel for tokens, sending the PKCE code_verifier.

Why each anti-forgery element exists (this is what interviewers probe):

  • state — binds the callback to the browser session that started the flow; without it an attacker can inject their own code (login CSRF).
  • PKCE (code_verifier/code_challenge) — proves the party redeeming the code is the same party that started the flow, so a stolen authorization code is useless. Essential for public clients (mobile/SPA) that can't keep a secret.
  • exact redirect_uri match — an open or loose redirect lets an attacker steal the code.
  • nonce (OIDC) — binds the ID token to the request, preventing ID-token replay.

Why the code flow beats the implicit flow. Moving token issuance to the back channel keeps tokens out of the browser URL/history. The implicit flow (tokens in the redirect fragment) is deprecated for this reason.

Misconception to kill now. "An access token proves who the user is." No — an access token is a bearer authorization for an API. (A bearer token is one where mere possession grants access — like cash, there is no further proof the holder is the rightful owner — which is why bearer tokens must be protected in transit and at rest, scoped narrowly, and kept short-lived.) Identity comes from the OIDC ID token (validated as in Chapter 8), or from a userinfo call.

Chapter 8: JWT Validation Is a Protocol, Not a decode() Call

What a JWT is. A JSON Web Token is three base64url parts: a header (algorithm, key id), a payload (claims: issuer iss, audience aud, subject sub, expiry exp, etc.), and a signature. It is signed (JWS) so the receiver can verify it wasn't tampered with — but signing is not encryption; the payload is readable by anyone.

Validation is a checklist, and skipping any item is a vulnerability:

  1. Pin the allowed algorithm(s). Reject alg: none and reject a token whose alg you didn't expect. The infamous bug: a verifier that trusts the token's own alg field can be tricked into none (no signature) or into verifying an RS256 token's signature using the public key as an HMAC secret (algorithm confusion).
  2. Resolve the key by trusted kid from a known key set — don't fetch keys from an attacker-controlled URL.
  3. Verify the signature with that key.
  4. Check iss (the issuer is one you trust).
  5. Check aud (the token was minted for this service — a token for service X must not be accepted by service Y).
  6. Check exp/nbf with a sane clock-skew allowance.
  7. Check token type and nonce/state where applicable.
  8. Validate the claim schema before trusting any claim.

Key rotation must support overlap (old and new keys valid during transition), cache-expiry behavior, unknown-kid handling, and emergency revocation.

Misconception to kill now. "The library decoded it, so it's valid." decode()verify(). A decode that doesn't pin algorithm, issuer, audience, and expiry has authenticated nothing.

Chapter 9: SAML and SCIM

SAML (Security Assertion Markup Language) is the older XML-based SSO protocol still ubiquitous in enterprise. An Identity Provider (IdP) issues a signed XML assertion about the user to a Service Provider (SP). Security pitfalls are largely XML pitfalls:

  • XML Signature wrapping (XSW): the attacker adds a second, attacker-controlled assertion while keeping the original signature valid, and the SP validates one element but reads another — a parser differential (Phase 01, Chapter 6) in XML form.
  • Comment-injection / canonicalization bugs that change which text the SP trusts.
  • The defense is strict schema validation, validating the signature over exactly the element you consume, and using a hardened SAML library.

SCIM (System for Cross-domain Identity Management) is a REST/JSON standard for provisioning — creating, updating, and deprovisioning user accounts across systems. Its security weight is in lifecycle: a SCIM integration that fails to deprovision leaves orphaned accounts (ex-employees with access). Authorization on the SCIM endpoint itself is critical — it can create admin users.

Misconception to kill now. "SSO means identity is handled." SSO federates authentication; you still own authorization (Chapter 10) and the deprovisioning lifecycle.

Chapter 10: The Authorization Model — RBAC, ABAC, ReBAC, and IDOR

The three authorization models, from first principles:

  • RBAC (Role-Based). Permissions attach to roles; users get roles. Simple, coarse. "Admins may delete." Struggles with per-object ownership.
  • ABAC (Attribute-Based). Decisions use attributes of subject, resource, action, and context ("same department AND business hours AND classification ≤ user clearance"). Flexible, harder to audit.
  • ReBAC (Relationship-Based). Models relationships/ownership as a graph ("user owns document" → "users in a folder's group can read its documents"). This is the Google Zanzibar model and the right tool for object-level authorization at scale.

Real systems combine them. The non-negotiable rules:

  • Preserve the initiating subject and tenant downstream. When service A calls service B on a user's behalf, B must know the original subject/tenant — decide whether A acts as itself, on behalf of the user, or with a narrowly delegated capability. Losing the subject across a hop is how confused-deputy bugs appear — where a privileged service is tricked into misusing its own authority on behalf of a less-privileged caller (the service is the "deputy," confused about whose request it is really serving). SSRF in Chapter 11 is the same pattern at the network layer: the server is the confused deputy, its network position the abused authority.
  • Centralize semantics and audit, not necessarily a single synchronous service. Every path must apply the same policy and emit the same audit shape.
  • Cover every data path: batch, search, export, GraphQL, jobs, cache, counts, errors.

Misconception to kill now. "We do RBAC, so authorization is done." RBAC rarely answers "may this user touch this specific object" — that's the object-level (ReBAC/ABAC) question where IDOR lives.

Chapter 11: The Web Attack/Defense Map

Each class has a single root cause and durable (design-level) controls — memorize the root causes, because they generalize:

AttackRoot causeDurable controls
SQL / command injectiondata is interpreted as syntaxparameterized queries / typed APIs; never build commands by string concat; least-privilege DB account
XSSuntrusted data lands in an executable browser contextcontext-aware output encoding; safe DOM APIs; Content-Security-Policy as defense-in-depth
CSRFambient browser credential used cross-siteSameSite cookies; anti-CSRF token; Origin/Referer checks
SSRFserver fetches an attacker-chosen destinationparse the URL once; scheme/port allowlist; resolve via controlled DNS; validate the resolved IP and every redirect; egress proxy; deny metadata/control-plane; don't attach ambient credentials
Path traversal / unsafe uploadattacker controls a path or content lifecyclegenerate server-side names; isolate storage and processing; enforce exact size/type limits
Insecure deserializationdata controls object/code behaviorsimple data schemas; allowlists; never deserialize native object graphs from untrusted input
Replaya valid message reused outside its intended transactionnonce, expiry, audience, idempotency keys, replay cache

Two of these in depth, because the labs use them:

Injection — the universal rule. Every injection (SQL, OS command, LDAP, NoSQL, template) is the same bug: mixing untrusted data into a language's syntax. The universal fix is separation of code and data — parameterization (the query/command structure is fixed; data is bound, never parsed). String concatenation is the anti-pattern in all of them.

SSRF as routing + identity. SSRF lets untrusted input steer a server-side connection that carries the server's network position and credentials. String allowlists fail around URL parsing quirks, redirects, DNS rebinding (Chapter 2), alternate IP encodings, and proxies. The robust defense: parse once → restrict scheme/port → resolve through a controlled resolver → validate every resolved address and every redirect hop → route through a constrained egress → block metadata/control-plane addresses → avoid attaching ambient credentials. The crown-jewel target an attacker is reaching for is the cloud metadata endpoint (169.254.169.254 on AWS/GCP/Azure): it hands any local caller short-lived cloud credentials with no authentication, so a single SSRF that reaches it escalates straight to cloud-account compromise — this is the 2019 Capital One breach pattern, and why "block metadata" is non-negotiable rather than nice-to-have. The Go webhook-gateway lab makes you build exactly this.

Insecure deserialization (the JVM lab). Native deserialization (Java ObjectInputStream, Python pickle, etc.) rebuilds arbitrary object graphs, and "magic" methods invoked during reconstruction can be chained into code execution ("gadget chains"). The fix is structural: don't accept native serialized objects from untrusted sources; use simple, schema-validated data formats (JSON/protobuf) with allowlisted types.

Chapter 12: Applied Cryptography — AEAD, Keys, and Envelope Encryption

Use reviewed primitives; never invent your own. The security engineer's job is correct use, not algorithm design.

AEAD — the modern default. Authenticated Encryption with Associated Data (AES-GCM, ChaCha20-Poly1305) provides confidentiality and integrity in one operation, and binds optional unencrypted "associated data" (e.g. a record ID) so it can't be swapped. Two requirements are part of the construction, not optional:

  • Nonce uniqueness. Reusing a nonce with the same key in GCM is catastrophic (it can reveal the authentication key). Nonces must never repeat per key.
  • Tag verification. You must verify the authentication tag before using any decrypted bytes — decrypt-then-trust without checking the tag defeats the integrity guarantee.

Key management — the lifecycle. A key needs a defined owner, purpose, creation, storage, use, rotation, revocation, backup, audit, and destruction. A KMS/HSM (Key Management Service / Hardware Security Module) keeps key material in a hardened boundary and exposes only operations (encrypt/decrypt/sign) — so application compromise doesn't leak the raw key.

Envelope encryption — why it scales. Encrypt data with a per-object data encryption key (DEK); encrypt (wrap) the DEK with a key encryption key (KEK) held in the KMS. Benefits: rotating the KEK doesn't require re-encrypting all data; the KMS only ever sees small DEKs; blast radius is bounded. But: authorized application code still sees plaintext during processing — encryption at rest does not protect against an attacker with application-level access.

Misconception to kill now. "It's encrypted, so the data is safe." Encryption protects data at rest / in transit against parties without the key. It does nothing against an attacker who reaches the code path that legitimately decrypts it — which is why authorization (Chapter 10) is still the primary control.

Chapter 13: The Rest of the Web Attack Surface

Chapter 11 mapped the headline classes; a working appsec engineer is also expected to know these, which interviewers probe and scanners under-report. Each is the same root lesson — untrusted data crossing into a context that interprets it — in a new context.

XXE — XML External Entity injection. XML supports a DTD (Document Type Definition) that can declare entities, including external ones pointing at a URI. A parser that resolves external entities on untrusted XML will fetch them:

<!DOCTYPE r [ <!ENTITY x SYSTEM "file:///etc/passwd"> ]>
<r>&x;</r>      <!-- the parser substitutes the file's contents -->

That gives an attacker local file read (file://), SSRF (http://169.254.169.254/… — Chapter 11), and DoS (the "billion laughs" nested-entity expansion blows up memory exponentially). XML is everywhere you don't expect it: SOAP/REST XML bodies, SAML (Chapter 9 — "SAML pitfalls are XML pitfalls"), SVG, and the Office formats (.docx/.xlsx are zipped XML). The trap is that DTD/external-entity processing is on by default in many parsers — Java DocumentBuilderFactory/SAXParser/XMLInputFactory, PHP libxml, older Python. Defense: disable DTDs and external entities (setFeature("…disallow-doctype-decl", true), XMLConstants.FEATURE_SECURE_PROCESSING; defusedxml in Python), and prefer JSON. Misconception: "we only take XML from partners" — one malicious document reads your filesystem; the parser default, not the sender, is the bug.

SSTI — Server-Side Template Injection. Template engines (Jinja2, Twig, Freemarker, Velocity, ERB, Handlebars, Thymeleaf/SpEL) evaluate expressions. The fatal pattern is building the template source from user input instead of passing user input as data:

render_template_string("Hello " + name)     # BUG: name is part of the template program
render_template("hello.html", name=name)    # safe: name is data in a fixed template

{{7*7}} rendering as 49 confirms it; from there an attacker walks the object graph to RCE ({{''.__class__.__mro__[1].__subclasses__()…}} in Jinja2, ${T(java.lang.Runtime)…} in SpEL/Freemarker). It's injection (Chapter 11) where the "language" is the template engine. Defense: never construct templates from user input; pass it as context variables; don't rely on a "sandboxed" engine (sandbox escapes are a research staple). Misconception: "it's just string formatting" — a template engine is an interpreter, so injecting the template is injecting code.

CORS — Cross-Origin Resource Sharing. Start from the Same-Origin Policy (SOP): a browser lets a page send requests anywhere, but script may only read responses from its own origin (scheme + host + port). CORS is a controlled relaxation of that read restriction: a server opts specific other origins in by returning Access-Control-Allow-Origin (ACAO), and for non-simple requests the browser first sends a preflight OPTIONS. The bugs are over-relaxation:

  • reflecting the request's Origin into ACAO (trusting any caller) — especially with Access-Control-Allow-Credentials: true — lets any malicious site make credentialed requests to your API in the victim's browser and read the responses → cross-origin data theft / account takeover;
  • accepting Origin: null (sandboxed iframes send it);
  • ACAO: *, which the browser refuses to combine with credentials but which still over-shares public data.

The load-bearing point (the one this track previously left unexplained): CORS is not an access control. It governs which browser origins may read a response — it neither authorizes the request nor mitigates CSRF (CSRF is about sending; CORS is about reading). A permissive CORS policy is a confidentiality hole, and server-side authorization (Chapter 10) is still the control. In code: Rails rack-cors, Express cors, Spring @CrossOrigin all make origin-reflection a one-liner. Misconception: "we configured CORS, so the API is protected" — CORS only loosens SOP; it protects nothing, and reflecting Origin is actively dangerous.

CSP — Content Security Policy. A response header that tells the browser which sources may load or execute resources — a defense-in-depth layer against XSS (Chapter 11): even if injection happens, the injected <script> won't run unless it satisfies the policy. Key directives: default-src, script-src, style-src, connect-src, object-src 'none', base-uri, and frame-ancestors (the modern clickjacking defense, below). Strong CSP uses a per-response nonce (script-src 'nonce-r4nd0m') or hashes, plus 'strict-dynamic', and avoids 'unsafe-inline'/'unsafe-eval' (which defeat it) and over-broad allowlists (a CDN that hosts user content, or a JSONP endpoint, becomes a bypass). Content-Security-Policy-Report-Only + report-to let you roll it out safely. Misconception: "we have CSP, so XSS is handled" — a CSP with 'unsafe-inline' or a sloppy allowlist is bypassable; CSP bounds the blast radius of an XSS, it does not replace fixing the output-encoding bug.

Clickjacking (UI redressing). The attacker loads your site in a transparent <iframe> over their own page and lures the victim into clicking your sensitive button ("confirm transfer") while they think they're clicking the attacker's UI. Defense: Content-Security-Policy: frame-ancestors 'none' (or 'self') — the modern control — and/or the legacy X-Frame-Options: DENY. JavaScript "framebusting" is bypassable. Sensitive one-click actions also want an intent confirmation / re-auth. Misconception: "who would frame our site?" — any attacker page can; a state-changing action with no frame protection is exposed.

Prototype pollution (JavaScript). Every JS object inherits from Object.prototype. If untrusted input can set a key like __proto__ (via a recursive merge/clone of attacker JSON, or a query-string parser that builds nested objects), the attacker writes onto the shared prototype:

merge({}, JSON.parse('{"__proto__":{"isAdmin":true}}'));
({}).isAdmin // -> true, on EVERY object in the process

Consequences range from auth bypass (a polluted flag) to DoS to RCE (when a polluted property later flows into child_process options or a template engine). It's a Node.js-ecosystem hazard (historic lodash.merge, jQuery.extend, qs). Defense: prototype-less maps (Object.create(null) or Map), reject __proto__/constructor/prototype keys, schema-validate input, freeze prototypes. Misconception: "it's just setting a property" — it mutates the prototype shared by the whole runtime.

Mass assignment (auto-binding / BOPLA). Frameworks that auto-bind request parameters to object fields will happily bind fields the user was never supposed to set — is_admin, role, account_id, email_verified — if you don't restrict them. Send the extra parameter, escalate. This is OWASP API Top 10's Broken Object Property-Level Authorization, and it's the bug behind the famous 2012 Rails/GitHub incident. In code: Rails strong parameters (params.require(:u) .permit(:name)), Spring DataBinder allow/deny lists, Django ModelForm fields, .NET bind includes, or — best — a separate DTO / view model you map explicitly. Misconception: "that field isn't in our form" — the binder doesn't read your form; it binds whatever parameters arrive, so the allowlist must be server-side.

Web race conditions (TOCTOU at the request layer). Many endpoints do check-then-act on shared state — "is the coupon unused? then redeem it," "is balance ≥ amount? then withdraw." Fire many requests concurrently and they all read the pre-update state in the sub-millisecond window before any commits — so a one-time coupon redeems N times, a balance withdraws twice, a usage limit is overrun (Kettle's "single-packet attack" makes this reliable). It's Phase 01's TOCTOU at the web tier. Defense: make the operation atomic at the data layer — a conditional update (UPDATE … SET used=true WHERE id=? AND used=false), a unique constraint, SELECT … FOR UPDATE, optimistic locking (a version column), or an idempotency key — never a read in one statement and a write in another. Misconception: "we check the limit first" — under concurrency the check is stale; only an atomic data-layer guard holds.

Misconception to kill now. "We covered the OWASP Top 10, so the web app is done." The classes above are common, high-impact, and under-reported by scanners — XXE in an XML endpoint, SSTI in a report generator, a reflected-origin CORS policy, a usage-limit race. Knowing the root (untrusted data reaching an interpreter, a relaxed boundary, or a non-atomic check) is what lets you find them without a signature.

Chapter 14: Email Authentication — SPF, DKIM, and DMARC

Zero background — why email is forgeable. SMTP, the mail protocol, has no built-in sender authentication: a sending server can put any address in the From: header. That is the mechanical basis of phishing and business email compromise (BEC) — the single most common initial-access vector in real incidents. Three DNS-published standards layer authentication on top of SMTP, and a security engineer is expected to configure, evaluate, and monitor all three.

SPF (Sender Policy Framework). A DNS TXT record listing which IPs are authorized to send mail for a domain: v=spf1 include:_spf.google.com ip4:198.51.100.0/24 -all. The receiver checks the connecting IP against the record of the envelope sender (the MAIL FROM / Return-Path domain). -all = hardfail (reject others), ~all = softfail. Limits: SPF checks the envelope domain, not the visible From: the user sees; it has a 10-DNS-lookup cap; and it breaks on forwarding (the forwarder's IP isn't in the original domain's SPF).

DKIM (DomainKeys Identified Mail). The sending server cryptographically signs selected headers and the body, adding a DKIM-Signature: header, and publishes the public key in DNS at selector._domainkey.domain. The receiver verifies the signature. This proves the message wasn't modified and came from a holder of the domain's key — and, unlike SPF, the signature travels with the message, so it survives forwarding.

DMARC (Domain-based Message Authentication, Reporting & Conformance). SPF and DKIM each authenticate something (the envelope, the signature) — but not the visible From: address. DMARC closes that gap with two ideas:

  • Alignment — the SPF-authenticated domain and/or the DKIM d= domain must match the From: domain (strict or relaxed). Alignment is what actually ties authentication to what the user sees.
  • Policy + reporting — a DNS record (_dmarc.domain) publishes p=none|quarantine|reject telling receivers what to do when a message fails both aligned SPF and aligned DKIM, plus rua= (aggregate) and ruf= (forensic) report addresses, and sp= for subdomains.

The synthesis (and the misconception): only DMARC with alignment and an enforcing policy (p=reject) stops From: spoofing of your domain. SPF or DKIM alone authenticate the envelope or the signature, not the visible sender; and p=none enforces nothing — it only monitors. Related: ARC preserves authentication results across legitimate forwarders (fixing SPF's forwarding breakage), and BIMI displays a brand logo once a domain enforces DMARC. In code/ops: you publish these as DNS records and monitor the rua reports; a receiving mail system evaluates them and feeds the result into spam/phishing scoring.

Misconception to kill now. "We have an SPF record, so nobody can spoof us." SPF authenticates the envelope sender, not the From: header the recipient reads — and SPF/DKIM without DMARC alignment at p=reject leaves visible-From: spoofing wide open. The control is the aligned, enforced trio.

Chapter 15: Cryptographic Failures — How Crypto Actually Breaks

Chapter 12 said "use reviewed primitives." This chapter is the other half: correct primitives used wrong — OWASP's "Cryptographic Failures" (A02). The lesson is that crypto almost never breaks at the cipher math; it breaks at the seams — mode, nonce, comparison, randomness, and key handling. You must recognize these in review.

  • ECB mode leaks structure. AES-ECB encrypts each block independently, so identical plaintext blocks produce identical ciphertext blocks — the famous "ECB penguin" stays visible through encryption. Never ECB; use AEAD (Chapter 12).
  • Nonce / IV reuse is catastrophic. A stream/counter cipher (CTR, GCM, ChaCha20) under a repeated nonce with the same key is a two-time pad: XOR of the two ciphertexts equals XOR of the plaintexts, leaking both. Worse, in GCM a repeated nonce leaks the authentication subkey, enabling forgery. Nonces must be unique per key (random 96-bit, or a strict counter).
  • Padding oracles (CBC). If a server decrypts CBC and reveals — via an error message, a status code, or timing — whether the PKCS#7 padding was valid, an attacker decrypts ciphertext byte-by-byte without the key, and can forge ciphertexts. POODLE and Lucky13 are real-world variants. Fix: authenticated encryption (encrypt-then-MAC / AEAD) so a tampered ciphertext is rejected before padding is ever examined, plus uniform errors.
  • Non-constant-time comparison. Comparing a MAC, token, or signature with == short-circuits on the first differing byte, leaking — through timing — how many leading bytes matched, which an attacker uses to forge a value byte-by-byte. Use constant-time comparison (hmac.compare_digest, crypto.timingSafeEqual, MessageDigest.isEqual).
  • Hash length-extension. A homemade MAC of the form H(secret ‖ message) built on a Merkle–Damgård hash (MD5, SHA-1, SHA-2) is forgeable: knowing H and the secret's length, an attacker computes a valid MAC for message ‖ padding ‖ extra without the secret. Fix: use HMAC (which is immune), or SHA-3/BLAKE2 — never a naive prefix-MAC.
  • Weak randomness. Generating tokens, session IDs, keys, IVs, or password-reset codes with a non-cryptographic RNG (Math.random(), C rand(), java.util.Random) makes them predictable. Use a CSPRNG: secrets/os.urandom (Python), crypto.randomBytes (Node), SecureRandom (Java), /dev/urandom.
  • Bad password storage. Fast or unsalted hashes (MD5/SHA-256 of a password) fall to GPU cracking and rainbow tables. Use a slow, salted, memory-hard KDF: Argon2id (preferred), scrypt, bcrypt, or PBKDF2 with a high iteration count (NIST 800-63 / Phase 03).
  • Downgrade & legacy protocols. SSLv3/early TLS, export ciphers, and insecure renegotiation enabled "downgrade" attacks (FREAK, Logjam, POODLE). Fix: TLS 1.2+/1.3 only, modern cipher suites, no legacy fallback (Chapter 4).

The unifying defense. AEAD + unique nonces + a CSPRNG + constant-time comparison + a real password KDF + TLS 1.3, with key material in a KMS/HSM (Chapter 12), eliminates the large majority of real-world crypto bugs. The cipher is the easy part; the discipline around it is the job.

Misconception to kill now. "We use AES-256, so the data is safe." AES-256 in ECB, or GCM with a reused nonce, or a token compared in non-constant time, or keys from Math.random(), is broken despite the strong cipher. Review the mode, nonce, comparison, randomness, and key handling — that is where crypto actually fails.

Lab Walkthrough Guidance

The five labs map directly onto the chapters:

  1. Lab 01 — Tenant Authorization (flagship). Build object-level authorization checking the full tuple (Chapter 5/10): tenant, role, relationship, token iss/aud/exp, account state, classification. Write a test for every route × role × tenant; a new endpoint absent from the matrix must fail CI.
  2. Lab 03 — OIDC Transaction. Implement the auth-code + PKCE flow (Chapter 7) and validate the ID token as a protocol (Chapter 8) — state, nonce, iss, aud, exp, pinned alg.
  3. Lab 04 — Go Webhook Gateway. Build SSRF-resistant outbound fetching (Chapter 11): parse once, allowlist scheme/port, resolve through a controlled resolver, validate the resolved IP and every redirect, block metadata.
  4. Lab 02 — Secure Upload. Generated names, isolated storage/processing, exact limits (Chapter 11 traversal/upload).
  5. Lab 05 — JVM Deserialization. Demonstrate why native deserialization of untrusted input is dangerous and replace it with a schema-validated format (Chapter 11).
  6. Lab 07 — Web Security Policy Analyzer. Parse CORS, CSP, and framing headers and flag the Chapter 13 weaknesses — Origin-reflection with credentials, 'unsafe-inline', missing frame-ancestors (clickjacking), wildcard ACAO — emitting findings with severity.
  7. Lab 08 — Email Authentication Evaluator. Evaluate SPF/DKIM/DMARC records and alignment (Chapter 14); flag a domain that is spoofable because DMARC is p=none or unaligned.
  8. Lab 09 — Cryptographic Failure Detector. Detect the Chapter 15 seams over crypto-usage records — ECB mode, nonce/IV reuse, weak (non-CSPRNG) randomness, prefix-MAC length-extension exposure, and non-constant-time comparison.
  9. Lab 10 — TLS / X.509 Certificate-Chain Validator. Build path validation (Chapter 4): SAN/ wildcard hostname match, validity windows, chain linkage, CA basic-constraints, trusted anchor.
  10. Lab 11 — Password Security & Breach Check. KDF strength selection plus a privacy-preserving HIBP k-anonymity range breach check where the password/hash never leaves the client (Chapter 15).
  11. Lab 12 — WebAuthn / Passkey Ceremony Verifier. The relying-party checks — challenge, origin binding (the phishing-resistance property), user presence/verification, signature-counter clone detection (Chapters 4–8).

Run, where applicable:

pytest test_lab.py -v
LAB_MODULE=solution pytest test_lab.py -v
# Go:   go test ./...
# Java: make test

Success Criteria

You are ready for Phase 03 when you can, without notes:

  1. Explain why a valid TLS connection and a valid token are not authorization.
  2. Trace a request through DNS → TLS → HTTP → token → authZ and name what each layer proves.
  3. Validate a JWT as a full protocol and name three ways a decode()-only check fails.
  4. Explain state, PKCE, nonce, and exact-redirect-URI in the OIDC code flow and what each prevents.
  5. Give the root cause (not just the name) of injection, XSS, CSRF, SSRF, and deserialization, and a durable control for each.
  6. Explain why SSRF defenses must validate the resolved address and every redirect, not the hostname string.
  7. Explain AEAD nonce-uniqueness and envelope encryption, and why "encrypted at rest" doesn't stop an app-level attacker.
  8. Distinguish XSS from CSRF and explain why HttpOnly doesn't stop XSS.
  9. Explain XXE, SSTI, and a web race condition from first principles, and the durable fix for each.
  10. Explain why CORS is not an access control, what an Origin-reflecting + credentialed policy leaks, and what CSP and frame-ancestors do (and don't) protect.
  11. Explain how SPF, DKIM, and DMARC compose, and why only aligned DMARC at p=reject stops visible-From: spoofing.
  12. Name five ways correct crypto primitives break (ECB, nonce reuse, padding oracle, non-constant-time compare, weak RNG) and the fix for each.

Common Mistakes

  • Trusting X-Forwarded-For/Host/alg from the request.
  • Using an access token as proof of authentication instead of an OIDC ID token.
  • decode() without pinning algorithm, issuer, audience, and expiry.
  • SSRF allowlists on the hostname string instead of the resolved IP and redirect chain.
  • Object-level authZ on direct routes but not on batch, search, export, GraphQL, or error paths.
  • Reusing AEAD nonces or trusting plaintext before verifying the authentication tag.
  • Treating "we have SSO/TLS/encryption" as if it settled authorization.
  • Leaving DTD/external-entity processing on in an XML parser (XXE); building templates from user input (SSTI).
  • Reflecting the Origin header into Access-Control-Allow-Origin with credentials; treating CORS as access control.
  • Relying on a CSP with 'unsafe-inline', or shipping sensitive actions with no frame-ancestors/X-Frame-Options.
  • Auto-binding request params to persistence models (mass assignment); check-then-act on shared state without an atomic guard.
  • Publishing SPF/DKIM but leaving DMARC at p=none (spoofable); assuming SPF stops visible-From: spoofing.
  • ECB mode; reusing a nonce/IV; comparing MACs/tokens non-constant-time; tokens/keys from a non-CSPRNG; fast/unsalted password hashes.

Interview Q&A

Q: A user reports they can read another tenant's invoice by changing the ID in the URL. Root cause and fix? A: Broken object-level authorization (IDOR/BOLA). The route authenticated the user but trusted a user-controlled object ID without checking the user's relationship to that object. Fix: evaluate the full tuple (subject + tenant + action + resource + relationship) on every data path, model ownership explicitly (ReBAC), and add a test per route × role × tenant so missing checks fail CI.

Q: Walk me through validating a JWT correctly. A: Pin allowed algorithms (reject none and unexpected alg to stop algorithm confusion); resolve the key by trusted kid from a known key set; verify the signature; check iss; check aud so a token minted for another service is rejected; check exp/nbf with bounded skew; check token type and nonce where applicable; validate the claim schema before trusting claims. decode() is not verify().

Q: Why does the OAuth authorization-code flow use PKCE and state? A: state binds the callback to the browser session that initiated the flow, preventing an attacker from injecting their own code (login CSRF). PKCE binds the code redemption to the party that started the flow, so a stolen authorization code can't be redeemed by anyone else — essential for public clients that can't hold a secret. Together with exact redirect-URI matching they stop code interception and injection.

Q: Is CORS a security control? A reviewer set Access-Control-Allow-Origin to reflect the request Origin with credentials enabled — what's the impact? A: CORS is not an access control — it only relaxes the Same-Origin Policy's restriction on reading cross-origin responses; it neither authorizes the request nor mitigates CSRF (which is about sending). Reflecting the Origin while allowing credentials means any attacker-controlled site can make credentialed requests to the API in the victim's browser and read the responses — a cross-origin data-theft / account-takeover bug. Fix: allowlist exact trusted origins (no reflection), never pair credentials with a broad/* origin, and keep real authorization server-side.

Q: A coupon can only be redeemed once, but a user redeemed it 50 times. What happened and how do you fix it? A: A web race condition — a check-then-act (is the coupon unused? then mark used) with a non-atomic window. Firing 50 concurrent requests, all read "unused" before any commits (TOCTOU at the web tier). Fix at the data layer: an atomic conditional update (UPDATE … SET used=true WHERE id=? AND used=false) or a unique constraint / SELECT … FOR UPDATE / idempotency key — never a separate read and write.

Q: We publish an SPF record. Can we still be spoofed? A: Yes — SPF authenticates the envelope sender (MAIL FROM), not the From: header the recipient sees, and it breaks on forwarding. Only DMARC ties authentication to the visible From: via alignment, and only an enforcing policy (p=reject, not p=none) actually blocks spoofed mail. The control is aligned SPF and/or DKIM plus DMARC at p=reject, monitored via rua reports.

Q: A service uses AES-256-GCM — is the data safe? A: Not necessarily. AES-256 is strong, but GCM with a reused nonce leaks plaintext and the auth key (forgery); ECB leaks structure; a token compared with == leaks via timing; keys/IVs from Math.random() are predictable. Crypto breaks at the seams — mode, nonce uniqueness, constant-time comparison, CSPRNG, and key handling — so I review those, not just the cipher name.

Q: How do you defend against SSRF in a service that fetches user-supplied URLs? A: Parse the URL once with a strict parser; allowlist scheme and port; resolve the host through a controlled resolver and validate the resolved IP against a deny-list (RFC1918, link-local, metadata); re-validate on every redirect hop (and pin the resolved address to defeat DNS rebinding); route through a constrained egress proxy; and never attach ambient credentials to the outbound request. Validate the address you connect to, not the string the user typed.

Q: What's the difference between XSS and CSRF? A: CSRF abuses ambient cookie authority — the attacker causes a cross-site request the browser auto-authenticates, but can't read the response; defend with SameSite cookies, anti-CSRF tokens, and Origin checks. XSS runs attacker script inside your trusted origin, so it acts with the user's full authority and can read responses; defend with context-aware encoding, safe DOM APIs, and CSP. HttpOnly mitigates cookie theft from XSS but doesn't stop the script from making authenticated requests.

References

Specifications

  • RFC 9110/9112 (HTTP semantics & HTTP/1.1), RFC 9113 (HTTP/2), RFC 9114 (HTTP/3), RFC 9000 (QUIC).
  • RFC 8446 (TLS 1.3); RFC 5280 (X.509 PKI).
  • RFC 6749 (OAuth 2.0), RFC 7636 (PKCE), OAuth 2.0 Security BCP (RFC 9700); OpenID Connect Core.
  • RFC 7519 (JWT), RFC 7515/7518 (JWS/JWA); RFC 6265bis (cookies, SameSite).
  • SAML 2.0 Core; SCIM 2.0 (RFC 7643/7644).
  • NIST SP 800-63B (digital identity / authentication).
  • Email auth: RFC 7208 (SPF), RFC 6376 (DKIM), RFC 7489 (DMARC), RFC 8617 (ARC); CSP Level 3 and Fetch (CORS) — W3C/WHATWG.

Web security

  • OWASP Top 10 and OWASP Application Security Verification Standard (ASVS); OWASP API Security Top 10.
  • OWASP Cheat Sheet Series (Authentication, Session Management, CSRF, XSS, SSRF, Deserialization, XXE Prevention, Content Security Policy, Clickjacking Defense, Mass Assignment).
  • PortSwigger Web Security Academy (request smuggling, OAuth, SSRF, CORS, SSTI, race conditions — incl. James Kettle's "Smashing the state machine" single-packet-attack research).
  • Dafydd Stuttard & Marcus Pinto, The Web Application Hacker's Handbook.

Cryptographic failures

  • OWASP A02 Cryptographic Failures; the Cryptographic Storage and Password Storage Cheat Sheets.
  • Padding-oracle (Vaudenay), POODLE, Lucky13; hash length-extension; Argon2 / scrypt / bcrypt KDFs.

Identity and authorization

  • Google "Zanzibar: Google's Consistent, Global Authorization System" (ReBAC).
  • OAuth 2 in Action (Richer & Sanso).

Cryptography

  • Serious Cryptography (Aumasson); Cryptography Engineering (Ferguson, Schneier, Kohno).
  • NIST SP 800-38D (AES-GCM); NIST SP 800-57 (key management).