Security Notes
Networking

HTTP/HTTPS Deep Dive

16 min read 12 sections 10 model answers

Breadth layernotes-security-core-knowledge.md Also see: TCP/IP Deep Dive · TLS & Cryptography


HTTP/1.1 — Request and Response Structure

HTTP is a text-based, stateless, request-response protocol that runs over TCP.

Request

GET /search?q=security HTTP/1.1\r\n       ← Request line: method, path, version
Host: www.example.com\r\n                 ← Required in HTTP/1.1 (enables virtual hosting)
User-Agent: Mozilla/5.0 ...\r\n
Accept: text/html,application/xhtml+xml\r\n
Accept-Encoding: gzip, deflate, br\r\n
Accept-Language: en-US,en;q=0.9\r\n
Cookie: session=abc123\r\n
Connection: keep-alive\r\n
\r\n                                       ← Empty line separates headers from body
[body — present for POST/PUT/PATCH]

Response

HTTP/1.1 200 OK\r\n                        ← Status line: version, code, reason phrase
Date: Mon, 01 Jan 2024 12:00:00 GMT\r\n
Server: nginx/1.24.0\r\n                   ← Information disclosure — hide in production
Content-Type: text/html; charset=utf-8\r\n
Content-Length: 1234\r\n
Content-Encoding: gzip\r\n
Cache-Control: max-age=3600\r\n
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict\r\n
Strict-Transport-Security: max-age=31536000; includeSubDomains\r\n
Content-Security-Policy: default-src 'self'\r\n
\r\n
[body — HTML, JSON, binary, etc.]

Keep-Alive and Persistent Connections

HTTP/1.0 opened a new TCP connection per request. HTTP/1.1 reuses the TCP connection (Connection: keep-alive is the default). This avoids the 3-way handshake overhead for each request.

HTTP pipeliningsend multiple requests without waiting for responses. Rarely used because of head-of-line blocking — a slow response blocks all pipelined responses behind it.


HTTP Methods

MethodIdempotentSafeBodyTypical use
GETYesYesNoRetrieve resource
HEADYesYesNoGET without response body; check existence/headers
POSTNoNoYesCreate resource; submit form data
PUTYesNoYesReplace resource entirely
PATCHNoNoYesPartial update
DELETEYesNoNoDelete resource
OPTIONSYesYesNoDiscover allowed methods (used in CORS preflight)
TRACEYesYesNoEcho request back — disable in production (XST attack)
CONNECT—No—Establish tunnel (used for HTTPS through HTTP proxy)

Idempotentcalling it N times has the same effect as calling it once. DELETE is idempotent (deleting an already-deleted resource still leaves it deleted). POST is not (submitting a form twice creates two records).

Safeno side effects on the server. GET should never modify state. (Violations are a security issue — e.g. GET /logout or GET /deleteAccount.)

Memory hook

"safe" = read-only, "idempotent" = repeat-safe. Safe methods don't change anything (GET, HEAD) — a crawler can hit them a million times harmlessly. Idempotent methods can change state but doing them twice equals doing them once (PUT sets a value; DELETE removes — re-deleting is still deleted). POST is neither: submit a payment form twice and you've paid twice. The security punchline: a state-changing GET (GET /deleteAccount) is a CSRF magnet, because browsers fire GETs automatically (image tags, prefetch) — never put side effects behind GET.


HTTP Status Codes

CodeNameMeaning
200OKSuccess
201CreatedResource created (POST/PUT)
204No ContentSuccess, no response body (DELETE)
301Moved PermanentlyRedirect; browser caches permanently
302FoundTemporary redirect; browser doesn't cache
304Not ModifiedConditional GET; cached version is still fresh
307Temporary RedirectTemporary; preserves HTTP method (unlike 302)
308Permanent RedirectPermanent; preserves HTTP method (unlike 301)
400Bad RequestMalformed syntax, invalid params
401UnauthorizedAuthentication required (misleading name)
403ForbiddenAuthenticated but not authorised
404Not FoundResource doesn't exist
405Method Not AllowedHTTP method not supported on this endpoint
429Too Many RequestsRate limit exceeded
500Internal Server ErrorGeneric server-side error
502Bad GatewayUpstream server returned invalid response
503Service UnavailableServer overloaded or under maintenance
504Gateway TimeoutUpstream server timed out
Memory hook

401 = "who are you?", 403 = "no." The names are misleading (401 is literally "Unauthorized" but means unauthenticated). Remember it as: 401 = authentication missing/failed → log in and try again; 403 = authenticated fine, but you're not allowed → logging in again won't help. Bonus security note: returning 404 instead of 403 hides whether a resource even exists, reducing enumeration — useful for /admin paths.

Security-relevant distinctions

  • 401 vs 403: 401 means "prove who you are first"; 403 means "I know who you are, but no." Returning 404 instead of 403 for unauthorised resources can reduce enumeration — but don't confuse real 404s.
  • 500 with stack trace: information disclosure; return a generic error to users, log details server-side.

HTTP/2 and HTTP/3

HTTP/2

Binary framing

requests/responses are split into binary frames, not text

Multiplexing

multiple requests over a single TCP connection simultaneously — no head-of-line blocking at the HTTP layer

Header compression (HPACK)

reduces overhead from repetitive headers

Server Push

server can proactively send resources the client will need (largely abandoned)

Requires TLS in practice

browsers only implement HTTP/2 over TLS (h2); plaintext h2c exists but is unused

HTTP/2 security notes

  • HPACK compression is used in CRIME/BREACH-style attacks: if an attacker can inject content adjacent to secrets (like CSRF tokens) in compressed responses, they can infer the secret via response size changes
  • HTTP/2 rapid reset (CVE-2023-44487): a vulnerability allowed sending many RST_STREAM frames to reset streams immediately after opening them, exhausting server resources (record-breaking DDoS)

HTTP/3

  • Built on QUIC (UDP-based transport) instead of TCP
  • Connection IDs survive IP address changes (mobile handoffs)
  • No head-of-line blocking even at the transport layer (QUIC streams are independent)
  • 0-RTT resumption: send data in the first packet (replay risk for non-idempotent requests)
  • Always encrypted: QUIC integrates TLS 1.3 — no plaintext QUIC

HTTPS — What HTTPS Adds

HTTPS = HTTP over TLS. It provides:

Confidentiality

message content encrypted; no eavesdropping on the path

Integrity

AEAD cipher prevents tampering; MITM cannot modify requests/responses without detection

Authenticity

server's identity verified via its X.509 certificate signed by a trusted CA

What HTTPS does NOT hide

  • The server's IP address (visible in routing)
  • The SNI (Server Name Indication) — hostname sent in plaintext in TLS ClientHello (though ECH — Encrypted Client Hello — is being deployed to fix this)
  • Connection timing and packet sizes (traffic analysis)
  • DNS queries to look up the server's IP (unless DoH/DoT is used)

HTTP Headers — Security Reference

Request Headers (set by the client/browser)

http
Origin: https://app.example.com          # Sent on cross-origin requests; used in CORS
Referer: https://app.example.com/page    # Revealing the page that initiated the request
Authorization: Bearer eyJ...             # Credentials; never send over HTTP
Cookie: session=abc; csrf_token=xyz      # Credentials; should be HttpOnly+Secure

Response Headers (set by the server — your responsibility)

http
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
# Force HTTPS for 1 year; include all subdomains; submit to browser preload list
# Once set, browsers will NEVER connect over HTTP — ensure HTTPS is working before enabling
# 💡 Memory hook — HSTS is a ONE-WAY door with a long memory. Once a browser sees
#    this header, it refuses plain HTTP to your domain for `max-age` seconds — even
#    if you change your mind. Set max-age too high before HTTPS is rock-solid (or
#    add includeSubDomains when a subdomain can't do HTTPS) and you've locked users
#    OUT with no quick undo. Roll it out with a SHORT max-age first, then raise it.
#    `preload` is even more permanent — it's hard-coded into browsers themselves.

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{random}'; object-src 'none'; base-uri 'self'
# Most powerful XSS mitigation; start with report-only mode to catch breakage
# unsafe-inline and unsafe-eval negate XSS protection — avoid

X-Frame-Options: DENY
# Prevent clickjacking (framing). Superseded by CSP frame-ancestors, but keep for IE compatibility

X-Content-Type-Options: nosniff
# Prevent MIME sniffing — browser must use declared Content-Type, not guess
# Stops browsers from executing .js served as text/plain

Referrer-Policy: strict-origin-when-cross-origin
# Cross-origin requests: send only the origin (not path/query); same-origin: full URL

Permissions-Policy: geolocation=(), camera=(), microphone=(), payment=()
# Disable powerful browser features you don't use

Cross-Origin-Opener-Policy: same-origin
# Isolates browsing context; enables SharedArrayBuffer; blocks window.opener hijacking

Cross-Origin-Resource-Policy: same-origin
# Prevents cross-origin embedding of this resource (Spectre mitigations)

Headers to Remove (Information Disclosure)

http
# Remove or replace:
Server: nginx/1.24.0          → Server: (empty or generic)
X-Powered-By: PHP/8.2.0      → remove entirely
X-AspNet-Version: 4.0.xxxxx  → remove entirely

Authentication in HTTP

Basic Auth

http
Authorization: Basic dXNlcjpwYXNz   (base64 of "user:pass")

Base64 is encoding, not encryption — trivially decoded. Only use over HTTPS. The password is sent with every request. Avoid for anything user-facing; acceptable for internal APIs with mutual TLS or behind a trusted proxy.

Bearer Token (OAuth 2.0)

http
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyMSJ9.signature

Token is presented with each request. If stolen (XSS, logging leak), the bearer can use it until expiry. Store in memory (not localStorage) and send only over HTTPS.

Digest Auth

Challenge-response; password never sent in clear. Superseded by Bearer tokens. Vulnerable to MITM (downgrade to Basic).

API Keys

http
X-API-Key: my-secret-api-key
# or
Authorization: ApiKey my-secret-api-key

Long-lived; should be rotated periodically. Treat as passwords — don't commit to source code. Use a secrets manager.


Cookies in Depth

http
Set-Cookie: session=<random-256-bit-token>;
  HttpOnly;             # JS cannot access — blocks XSS session theft
  Secure;               # HTTPS only — never sent over HTTP
  SameSite=Strict;      # CSRF protection — never sent on cross-site requests
  Path=/;               # Scope to entire site
  Max-Age=3600;         # Expiry in seconds (prefer over Expires — uses server-relative time)
  Domain=app.example.com  # Omit to restrict to exact hostname (not subdomains)

__Host- and __Secure- Prefixes

http
# __Host- prefix: browser enforces Secure + no Domain + Path=/
Set-Cookie: __Host-session=token; Secure; Path=/; SameSite=Strict; HttpOnly

# __Secure- prefix: browser enforces Secure attribute only
Set-Cookie: __Secure-session=token; Secure; SameSite=Lax; HttpOnly

These prefixes prevent subdomain attacks from setting cookies that apply to the parent domain.

SameSite — Detailed Behaviour

ValueWhen cookie is sent
StrictOnly same-site requests (same origin or navigation from same site)
LaxSame-site OR top-level navigation (user clicking a link) — NOT for images/iframes/fetch
None; SecureAll requests including cross-site (required for embedded widgets, OAuth redirects)

Default in modern browsersLax (if SameSite is not specified). This broke some legacy apps that relied on cookies being sent with all cross-site requests.


CORS in Depth

Simple Requests (no preflight)

A request is "simple" if:

  • Method: GET, HEAD, or POST
  • Content-Type: application/x-www-form-urlencoded, multipart/form-data, or text/plain
  • No custom headers

Simple requests are sent directly. The browser checks Access-Control-Allow-Origin in the response and exposes the response to JS only if the origin matches.

Preflighted Requests

Any request that doesn't meet the simple criteria triggers a preflight OPTIONS:

http
OPTIONS /api/data HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: X-Custom-Header, Content-Type

→ Server response:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, DELETE
Access-Control-Allow-Headers: X-Custom-Header, Content-Type
Access-Control-Max-Age: 86400    ← cache preflight result for 24h

CORS Misconfigurations

python
# Vulnerable: reflect any Origin
response.headers["Access-Control-Allow-Origin"] = request.headers.get("Origin")
response.headers["Access-Control-Allow-Credentials"] = "true"
# → Any origin can make credentialed requests → CSRF + data theft

# Vulnerable: overly broad
"Access-Control-Allow-Origin": "*.example.com"  # Not valid syntax — treated as literal
"Access-Control-Allow-Origin": "*"              # Safe only without credentials

# Safe: strict allowlist
ALLOWED_ORIGINS = {"https://app.example.com", "https://admin.example.com"}
origin = request.headers.get("Origin", "")
if origin in ALLOWED_ORIGINS:
    response.headers["Access-Control-Allow-Origin"] = origin
    response.headers["Vary"] = "Origin"  # Tell caches this varies by origin

HTTP Caching

http
# Server instructs client/proxy how to cache:
Cache-Control: max-age=3600          # Cache for 1 hour
Cache-Control: no-cache              # Validate with server before using cached copy (ETag/Last-Modified)
Cache-Control: no-store              # Never cache (sensitive data — banking, auth)
Cache-Control: private               # Client-side cache only; not shared proxies
Cache-Control: public, max-age=86400 # CDN-cacheable for 24h
Cache-Control: immutable             # Content will never change (fingerprinted assets)

# Conditional requests (validation):
ETag: "abc123"
Last-Modified: Mon, 01 Jan 2024 00:00:00 GMT

# Client sends on revalidation:
If-None-Match: "abc123"             → server returns 304 if unchanged
If-Modified-Since: Mon, 01 Jan ...  → server returns 304 if unchanged

Security implications

  • Cache-Control: no-store is critical for pages containing PII, session tokens, or sensitive data
  • Shared proxies/CDNs may cache user-specific responses if Vary: Cookie or Cache-Control: private is missing
  • Cache poisoning: if the CDN caches a response that includes attacker-controlled content (via unkeyed headers) → served to all users

Common Misconfigurations

MisconfigurationRiskFix
Verbose Server headerReveals server version → targeted exploit selectionRemove or genericise
HTTP available alongside HTTPSCredentials sent in cleartext if user types URL without https://Redirect all HTTP → HTTPS + HSTS
Wildcard CORS with credentialsAny origin can make authenticated requestsStrict allowlist; never * + credentials
Missing CSPXSS payloads can load external scriptsImplement strict CSP
Directory listing enabledFile enumerationDisable in nginx/Apache config
Sensitive data in GET paramsLogged in server logs, proxy logs, Referer headers, browser historyUse POST body for sensitive data
TRACE method enabledCross-Site Tracing (XST) — can read HttpOnly cookies via JS on some old browsersDisable TRACE

Interview Questions

Q
What's the difference between HTTP/1.1, HTTP/2, and HTTP/3?
Model answer

HTTP/1.1 is text-based and largely one-request-per-connection — even with keep-alive, responses come back in order, so a slow response blocks the ones behind it (head-of-line blocking). HTTP/2 is binary-framed and multiplexes many concurrent requests over a single TCP connection with header compression, removing HTTP-layer head-of-line blocking — but because it's still on TCP, a lost packet stalls all streams at the transport layer. HTTP/3 moves onto QUIC over UDP, which makes streams truly independent so packet loss only affects the one stream, integrates TLS 1.3 (always encrypted), and uses connection IDs that survive IP changes for seamless mobile handoffs. The arc is fewer round-trips and less blocking at each step.

Q
What does HTTPS protect against, and what does it not hide?
Model answer

HTTPS is HTTP over TLS, giving three things: confidentiality so an on-path eavesdropper can't read the content, integrity so they can't tamper without detection, and authenticity so the client verifies the server's identity via its CA-signed certificate. What it does not hide: the server's IP address, the hostname in the TLS SNI field which is sent in plaintext unless Encrypted Client Hello is used, the DNS lookup unless you use DoH/DoT, and traffic-analysis metadata like packet sizes and timing. So HTTPS protects the content of the conversation, but an observer still learns who you're talking to and roughly how much.

Q
Explain what happens when a browser makes a cross-origin request. What is a preflight?
Model answer

The same-origin policy stops JavaScript from reading responses from a different origin unless that origin opts in via CORS. For "simple" requests — GET/HEAD/POST with safe content types and no custom headers — the browser sends the request with an Origin header and only exposes the response to JS if the server returns a matching Access-Control-Allow-Origin. For anything else — a custom header, a DELETE, a JSON content type — the browser first sends a preflight OPTIONS request asking "can I send this method with these headers from this origin?", and only sends the real request if the server's Access-Control-Allow-* response permits it. The key point is CORS relaxes the same-origin policy under server control; it isn't a server-side access control itself.

Q
What is SameSite=Strict and how does it prevent CSRF?
Model answer

SameSite is a cookie attribute controlling whether the browser attaches the cookie to cross-site requests. With Strict, the cookie is only sent when the request originates from the same site, so a request triggered from an attacker's page — the essence of CSRF — won't carry the victim's session cookie and the forged action fails. It cuts CSRF at the root because CSRF depends on the browser auto-attaching cookies to cross-site requests. The trade-off is that Strict also withholds the cookie on legitimate inbound navigation, like following a link from an email, so Lax is the common default — it sends the cookie on top-level GET navigations but not on cross-site sub-resource requests or form POSTs — usually paired with CSRF tokens for state-changing actions.

Q
What is HSTS and what happens if you set it wrong?
Model answer

HSTS is a response header that tells the browser to only ever connect to this domain over HTTPS for the max-age duration, which defeats SSL-strip downgrade attacks and accidental HTTP requests. The catch is it's effectively a one-way commitment with a long memory: once a browser sees it, it refuses plain HTTP for that whole duration, even if you change your mind. So if you set a long max-age, or add includeSubDomains, before HTTPS is fully working everywhere — including every subdomain — you can lock users out with no fast undo. The safe rollout is a short max-age first, verify everything works, then raise it; and preload, which hard-codes the policy into browsers, is even more permanent and should be a deliberate final step.

Q
Walk me through the security-relevant response headers you'd set on a new web app.
Model answer

Strict-Transport-Security to force HTTPS, rolled out carefully. A strong Content-Security-Policy as the main XSS backstop — ideally nonce-based with no unsafe-inline — started in report-only mode to find breakage. X-Content-Type-Options nosniff to stop MIME sniffing. X-Frame-Options DENY or CSP frame-ancestors to prevent clickjacking. Referrer-Policy strict-origin-when-cross-origin to avoid leaking URLs. Permissions-Policy to disable browser features you don't use. The cross-origin isolation headers (COOP/CORP) for Spectre hardening. And on the cookie side, HttpOnly, Secure, and SameSite. Plus removing information-disclosure headers like Server and X-Powered-By. The framing is that these are cheap defense-in-depth layers that are table stakes.

Q
What's the difference between 401 and 403?
Model answer

401 Unauthorized actually means unauthenticated — the server doesn't know who you are, so authenticate and try again. 403 Forbidden means you're authenticated fine but you're not permitted to access this resource, so retrying with the same identity won't help. The naming is historically confusing, so I remember it as 401 = "who are you?" and 403 = "no." A security nuance is that some apps return 404 instead of 403 for sensitive resources so attackers can't even confirm a resource or admin path exists, reducing enumeration.

Q
Why should you never put sensitive data in GET query parameters?
Model answer

Because query strings end up in far more places than the request itself. They're written to server access logs and proxy/load-balancer logs, they're stored in browser history, they're sent in the Referer header to third-party sites the page links to or loads resources from, and they can be cached. So a session token, password, or PII in a URL leaks into logs and analytics across multiple systems where it's hard to scrub and easy to expose. Sensitive data belongs in the request body of a POST, or in a header, not in the URL.

Q
What is Content Security Policy and how does it mitigate XSS?
Model answer

CSP is a response header telling the browser which sources of scripts, styles, and other content are allowed to load and execute, acting as a second line of defense if an XSS payload gets injected. A strong policy forbids inline scripts and only allows scripts from trusted origins or those carrying a per-request nonce, so an injected script tag simply won't run — it isn't from an allowed source and lacks the nonce. It doesn't replace output encoding; it's defense-in-depth, and the mistake that guts it is allowing unsafe-inline. I'd deploy it report-only first to find legitimate breakage, then enforce, ideally nonce-based with strict-dynamic.

Q
Explain Cache-Control: no-store and when it matters.
Model answer

no-store tells browsers and any intermediary caches never to store the response at all — not on disk, not in memory. It matters for any response containing sensitive data: authenticated pages, anything with PII, session tokens, or financial data. Without it, a response can be cached on the device or, worse, on a shared proxy or CDN, where it might be served to another user or recovered later from a shared machine. It's distinct from no-cache, which permits storing but requires revalidation before reuse. For sensitive pages you want no-store, often alongside private and a Vary on Cookie so user-specific content is never served to the wrong person.