Security Advanced

The Web Application Attack Surface — HTTP, TLS, Cookies, Headers, and Where They Break

A deep technical tour of the actual surface attackers probe in modern web apps — protocol quirks, header semantics, cookie behaviors, and the bugs they enable.

DjangoZen Team May 10, 2026 14 min read 1 views

Why you need this depth

OWASP Top 10 names the categories. To actually defend against them, you need to know the protocols, the parsers, the quirks. This tutorial dives below the framework abstractions into the HTTP/TLS layer where attackers operate.

This is foundational for understanding tutorial 5 (advanced OWASP) and tutorial 6 (WAF bypass). If anything here is unfamiliar, slow down and read references.

HTTP — the protocol behind every web attack

Request structure

POST /login HTTP/1.1
Host: djangozen.com
User-Agent: Mozilla/5.0 ...
Cookie: sessionid=abc123
Content-Type: application/x-www-form-urlencoded
Content-Length: 27

username=admin&password=foo

The blank line separates headers from body. The request line specifies method, path, version. Each header is Name: Value. The body is bytes after the blank line, length specified in Content-Length.

HTTP version differences that matter

  • HTTP/1.0 — one request per connection, mostly historical
  • HTTP/1.1 — persistent connections, pipelining (rarely used), the workhorse for two decades
  • HTTP/2 — binary, multiplexed, header compression (HPACK). Major rewrite. Widely deployed.
  • HTTP/3 — over QUIC instead of TCP. Reduces tail latency, harder to passively analyze.

Why this matters for security:

  • HTTP/1.1 parsing inconsistencies between front-end (CDN/load balancer) and back-end (your app) cause HTTP Request Smuggling (CL.TE, TE.CL desyncs). Major class of vuln.
  • HTTP/2 multiplexing introduces its own desync bugs.
  • WAFs may interpret bytes differently from your app — bypass surface.

Methods

Standard verbs: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS. Plus weird ones (TRACE, CONNECT, MOVE, COPY, PROPFIND from WebDAV).

Security implications:

  • TRACE can leak headers — disable it
  • OPTIONS preflight is part of CORS; misconfiguration here leaks data
  • Unexpected methods (PUT, DELETE) on endpoints that don't handle them may have unexpected behavior — explicit allowlists, not denylists
  • HTTP method override via X-HTTP-Method-Override header is a common vulnerability if your framework respects it

Headers — where most bugs hide

Headers carry trust assumptions that aren't always warranted.

Host header

Specifies which virtual host. Untrusted user input. Many apps assume it's their own domain — bad assumption.

  • Cache poisoning — manipulate Host header to poison shared caches
  • Password reset poisoningHost: attacker.com makes reset emails link to attacker's site
  • SSRF — Host header sometimes used to construct internal URLs
# Django way to safely get host:
allowed_host = request.get_host()  # Validates against ALLOWED_HOSTS

# NOT this (vulnerable):
host = request.META.get('HTTP_HOST')  # Untrusted

X-Forwarded-For and X-Real-IP

Proxy-set headers indicating original client IP. Trusted only when you control the proxy chain.

  • Spoofable if you trust them unconditionally
  • Bypass IP rate limiting by spoofing the header
  • Audit log poisoning — fake IPs in logs

Trust only when the request came from a known proxy IP (your CDN, load balancer):

# Django setting — only trust XFF from these IPs
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

# In code, check the source before trusting XFF
def get_real_client_ip(request):
    # If this came directly to gunicorn, REMOTE_ADDR is the truth
    # If it came through Cloudflare, use CF-Connecting-IP
    cf_ip = request.META.get('HTTP_CF_CONNECTING_IP')
    if cf_ip and request.META.get('REMOTE_ADDR') in CLOUDFLARE_IPS:
        return cf_ip
    return request.META.get('REMOTE_ADDR')

Referer and Origin

Send the previous URL or the originating site. Useful for CSRF defense and analytics. Spoofable by attacker-controlled clients but not by victim browsers in cross-origin contexts.

  • CSRF defense — modern frameworks check Origin/Referer on state-changing requests
  • Don't rely on Referer for auth — easy to strip or spoof in non-browser contexts

Authorization

The auth credential. Bearer tokens, Basic auth (base64-encoded credentials), API keys.

  • Never log this header — common bug
  • Validate every request — don't trust session-based auth for API endpoints that also accept Authorization
  • Constant-time comparison when checking API keys to prevent timing attacks

Cache-control headers

Cache-Control, Pragma, Expires, Vary. Determine if responses get cached publicly or privately.

  • Caching authenticated responses publicly = data leak via cache to other users
  • Vary: Cookie is what tells caches "this response depends on the user's session"
  • private directive prevents shared caches from storing responses

Security headers

  • Strict-Transport-Security (HSTS) — force HTTPS even on first navigation (with preload list)
  • Content-Security-Policy — restrict what scripts, styles, sources are allowed
  • X-Frame-Options / frame-ancestors — prevent clickjacking
  • X-Content-Type-Options: nosniff — disable MIME-type sniffing
  • Referrer-Policy — control what Referer gets sent
  • Permissions-Policy — restrict browser APIs (geolocation, camera, etc.)
  • Cross-Origin-*-Policy (COOP/COEP/CORP) — isolation primitives for SAB and other powerful features

A modern Django app should set most of these via middleware:

# Django settings.py
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'
SECURE_REFERRER_POLICY = 'strict-origin-when-cross-origin'

# Plus CSP via django-csp middleware (covered separately)

Cookies — more complex than they look

Cookies have evolved a lot. Modern attributes matter.

Attributes you must set

  • Secure — HTTPS only. Stops Wi-Fi MITM from stealing cookies.
  • HttpOnly — no JavaScript access. Stops XSS from stealing session cookies.
  • SameSite=Lax or Strict — restricts cross-site sending. CSRF defense.
  • Domain — scope. Setting it loosely (e.g., .djangozen.com instead of djangozen.com) opens cross-subdomain cookie disclosure.
  • Path — usually /. Tighter paths are mostly cosmetic — browsers send cookies for sibling paths in some cases.
  • Max-Age / Expires — explicit expiration. Session cookies without these die when the browser closes.

Common cookie bugs

  • Session fixation — attacker sets a session cookie before the user logs in, then knows the post-login session ID. Defense: rotate session ID on auth state change. Django does this if you use the built-in auth.
  • Cookie tossing — setting a cookie at a parent domain to override a child domain's cookie. Defense: be explicit about Domain attribute, prefer host-only cookies (no Domain set).
  • Cookie jar overflow — older browsers had cookie count limits; setting many cookies could evict legitimate ones. Mostly historical now.
  • __Host- and __Secure- prefixes — cookies named __Host-sessionid MUST be Secure + Path=/ + no Domain. Browsers enforce. Use for sensitive cookies.

Django's built-in

SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Strict'   # or 'Lax' if you need cross-site post-login redirects
SESSION_COOKIE_NAME = '__Host-sessionid'  # Even stronger; enforces Host- prefix rules
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_HTTPONLY = False  # Must be readable by JS for AJAX
CSRF_COOKIE_SAMESITE = 'Strict'

TLS in production — the practical details

Certificate management

  • Let's Encrypt for free certs; certbot automates issue/renewal
  • For internal services, ACME with a private CA (step-ca, smallstep)
  • Wildcard certs (*.djangozen.com) cover subdomains but increase blast radius if cert/key leaks

Cipher suite selection

  • TLS 1.3 enabled (default since OpenSSL 1.1.1)
  • TLS 1.2 OK with strong cipher suites (AES-GCM, ChaCha20, no CBC, no RC4, no DES)
  • TLS 1.0/1.1 disabled (deprecated, vulnerable to BEAST/POODLE)
  • SSL/everything below TLS 1.0 disabled (deprecated, vulnerable to POODLE/DROWN)

Verify with:

# Test from outside
testssl.sh djangozen.com

Certificate pinning

For mobile apps and high-stakes B2B clients: pin specific certs or CAs so that even a fraudulent valid cert won't be accepted. Trade-off: cert rotation becomes harder.

Certificate transparency

Every public cert is logged. Monitor logs for your domain to detect rogue certs being issued:

  • crt.sh manual lookup
  • Cert-monitoring services (Cloudflare, certspotter)
  • Browser-level CT enforcement (Chrome, Safari)

Request smuggling — the protocol-level vulnerability

When two HTTP parsers (e.g., CDN/load balancer and your backend) interpret a request differently, an attacker can smuggle a hidden request through.

The classic CL.TE smuggle:

POST / HTTP/1.1
Host: target.com
Content-Length: 6
Transfer-Encoding: chunked

0

GPOST /admin HTTP/1.1
Host: target.com
...

If the front-end uses Transfer-Encoding (stopping at the 0\r\n) and the back-end uses Content-Length (reading 6 bytes), the back-end now sees "GPOST /admin" as the start of the next request — on a connection that's already passed authentication.

Defenses

  • Use the same HTTP/1.1 parser everywhere in your chain
  • Disable HTTP/1.1 keep-alive between front-end and back-end (eliminates the smuggle vector but hurts performance)
  • Upgrade to HTTP/2 end-to-end — different class of smuggle bugs but generally smaller
  • Reject requests with both Content-Length and Transfer-Encoding at the edge

This is a deep topic. The Burp Suite documentation has detailed write-ups; PortSwigger Research has been the seminal source.

The takeaway

The HTTP layer has decades of accumulated complexity. Most application code abstracts away this complexity, which is mostly fine — until your app's security depends on a header semantics that's different from what you expected, or a parser quirk between your CDN and your origin lets through a malicious request the WAF was supposed to catch.

Knowing this layer doesn't mean you write your own HTTP parser. It means you can read advisories about HTTP-layer bugs, understand whether they apply to you, and make informed choices about your CDN, your reverse proxy, your headers, and your cookies.

Next tutorial: applying this knowledge to find and defend against advanced application-level vulnerabilities.