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.
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.
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.
Why this matters for security:
Standard verbs: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS. Plus weird ones (TRACE, CONNECT, MOVE, COPY, PROPFIND from WebDAV).
Security implications:
X-HTTP-Method-Override header is a common vulnerability if your framework respects itHeaders carry trust assumptions that aren't always warranted.
Host headerSpecifies which virtual host. Untrusted user input. Many apps assume it's their own domain — bad assumption.
Host: attacker.com makes reset emails link to attacker's site# 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-IPProxy-set headers indicating original client IP. Trusted only when you control the proxy chain.
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 OriginSend 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.
AuthorizationThe auth credential. Bearer tokens, Basic auth (base64-encoded credentials), API keys.
Cache-Control, Pragma, Expires, Vary. Determine if responses get cached publicly or privately.
Vary: Cookie is what tells caches "this response depends on the user's session"private directive prevents shared caches from storing responsesStrict-Transport-Security (HSTS) — force HTTPS even on first navigation (with preload list)Content-Security-Policy — restrict what scripts, styles, sources are allowedX-Frame-Options / frame-ancestors — prevent clickjackingX-Content-Type-Options: nosniff — disable MIME-type sniffingReferrer-Policy — control what Referer gets sentPermissions-Policy — restrict browser APIs (geolocation, camera, etc.)Cross-Origin-*-Policy (COOP/COEP/CORP) — isolation primitives for SAB and other powerful featuresA 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 have evolved a lot. Modern attributes matter.
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.__Host-sessionid MUST be Secure + Path=/ + no Domain. Browsers enforce. Use for sensitive cookies.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'
certbot automates issue/renewal*.djangozen.com) cover subdomains but increase blast radius if cert/key leaksVerify with:
# Test from outside
testssl.sh djangozen.com
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.
Every public cert is logged. Monitor logs for your domain to detect rogue certs being issued:
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.
This is a deep topic. The Burp Suite documentation has detailed write-ups; PortSwigger Research has been the seminal source.
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.