đŸ›Ąïž Application Security CheatSheet

HTTP Header Injection / CRLF Injection

HTTP headers are the metadata lines around a response: cookies, redirects, content type, caching, security headers. Header injection happens when an application lets untrusted input influence those header lines.

In real incidents: CRLF injection often hides in redirects and proxy headers. It feels harmless until it becomes cache poisoning or response splitting.

CRLF injection is the classic way this happens: if user-controlled data can contain “line breaks” that the server or an intermediary treats as header separators, an attacker may be able to add or alter headers in the response.

Key idea: This is not “a weird character trick”. It’s a boundary failure between data and protocol framing (where one header line ends and the next begins).

Why header/CRLF injection happens (deep reason)

Modern note: Many runtimes/frameworks try to reject newline characters in headers, but bugs still happen via custom header builders, legacy components, proxy behavior, and “header-like” fields used by downstream systems.

Mental model: “framing vs data”

Think of an HTTP response as two parts:

Header/CRLF injection occurs when untrusted input crosses into framing. Even if the body is safe, a malicious header can change meaning: redirect users, set cookies, weaken caching rules, or disable security policies.

experienced interview line: “I treat response headers as a privileged channel. Untrusted data must never be able to create or modify header boundaries.”

Vulnerable vs secure code patterns (Node.js)

Vulnerable pattern (minimal)

// Node/Express (example)
app.get("/go", (req, res) => {
  // ❌ Untrusted input placed into a response header (redirect)
  const next = String(req.query.next || "/");
  res.setHeader("Location", next);
  res.status(302).end();
});

Secure pattern (defensive)

// ✅ Allow-list + safe URL construction + safe defaults
function safeRedirectTarget(raw) {
  const value = String(raw || "");
  // 1) Allow only relative paths (no scheme/host) to prevent open redirects & header abuse
  if (!value.startsWith("/")) return "/";
  // 2) Block control characters defensively (even if runtime rejects later)
  if (/[\r\n\u0000-\u001F\u007F]/.test(value)) return "/";
  // 3) Optional: constrain to known prefixes
  const allowedPrefixes = ["/account", "/orders", "/help"];
  if (!allowedPrefixes.some(p => value === p || value.startsWith(p + "/"))) return "/";
  return value;
}

app.get("/go", (req, res) => {
  const target = safeRedirectTarget(req.query.next);
  res.redirect(302, target);
});
Why this holds: you’re not “filtering payloads”, you’re applying a policy to what values are allowed in a privileged header and keeping behavior predictable.

Another common sink: Content-Disposition

// ❌ Risky: user controls filename inside Content-Disposition
app.get("/download", (req, res) => {
  const name = String(req.query.name || "file.txt");
  res.setHeader("Content-Disposition", `attachment; filename="${name}"`);
  res.type("text/plain").send("...");
});

// ✅ Safer: sanitize + fallback + avoid surprising characters
function safeFilename(raw) {
  const s = String(raw || "").trim();
  if (!s) return "file.txt";
  // Remove control chars and quotes/semicolons that can break header parameters
  const cleaned = s.replace(/[\r\n\u0000-\u001F\u007F"]/g, "_").replace(/[;=]/g, "_");
  // Keep it reasonably short
  return cleaned.slice(0, 80) || "file.txt";
}

app.get("/download", (req, res) => {
  const name = safeFilename(req.query.name);
  res.setHeader("Content-Disposition", `attachment; filename="${name}"`);
  res.type("text/plain").send("...");
});

Where it still happens in modern stacks

Variants (the “why they differ”)

1) Response header injection

Untrusted input changes a header value, or adds an additional header via boundary confusion. Impact depends on which header is influenced (cookies, redirects, caching, security policies).

2) Response splitting (concept)

If a component interprets attacker-controlled data as “end of headers”, it can accidentally treat the remainder as a new response segment. In practice, modern stacks often block this, but it’s still a useful mental model: your input must never control where “headers end”.

3) Request header injection (app-to-upstream)

The server becomes the client: it forwards a header to an upstream service using untrusted input. This can cause SSRF-like effects, cache poisoning, auth bypass in upstream routing, or log/metric confusion.

4) Proxy normalization mismatches

One layer normalizes/decodes, another layer parses. The same bytes can be interpreted differently across layers, creating bypasses even if your app’s runtime blocks obvious newline characters.

experienced takeaway: “CRLF injection” is one technique; the real risk is untrusted input influencing protocol metadata across layers.

Detection workflow (experienced-style, systematic)

This is a defensive verification workflow. The goal is to find and prove unsafe header construction without giving exploit recipes.

Step A — Inventory header-writing features

Step B — Establish baselines

Step C — Look for “header sensitivity”

Step D — Validate defenses in the stack

How to prove safely (evidence checklist)

A professional proof for header injection focuses on observable header behavior and impact, without forcing destructive side effects.

Evidence checklist

Safe proof principle: demonstrate header control with minimal exposure. Avoid actions that set sensitive cookies or weaken security in production.

Exploitation progression (attacker mindset)

This explains attacker decision-making at a high level (no copy/paste instructions). Attackers usually start by proving any influence on a privileged header channel, then search for the highest-impact header.

Phase 1: Find header sinks

Phase 2: Prove header influence

Phase 3: Target high-impact headers

Phase 4: Chain with other bugs

Interview takeaway: attackers think in terms of control of response metadata → highest-impact header → chaining, not “one payload”.

Tricky edge cases & bypass patterns (what attackers look for)

Safe validation workflow (defensive verification)

Goal: verify whether untrusted input can affect response headers or upstream headers, and quantify impact safely.
  1. Identify sinks: redirects, downloads, debug headers, tenant routing, proxy forwarding.
  2. Baseline: capture full response headers and confirm stability.
  3. Single-variable test: change one input, capture headers again, and diff.
  4. Check intermediaries: compare origin vs through CDN/proxy; note differences.
  5. Assess impact: map the affected header to real behaviors (redirect/cookies/cache/security policy).
  6. Regression idea: add tests ensuring header values reject control characters and enforce allow-lists for targets/filenames.

Defensive patterns & fixes (Node.js)

Pattern 1: Allow-list for redirect targets

// ✅ Allow-list routes instead of trusting user-provided URLs
const allowed = new Set(["/account", "/orders", "/help"]);

app.get("/go", (req, res) => {
  const raw = String(req.query.next || "");
  const next = allowed.has(raw) ? raw : "/account";
  res.redirect(302, next);
});

Pattern 2: Central header setter with validation

// ✅ One place to enforce header safety rules
function setSafeHeader(res, name, value) {
  const n = String(name);
  const v = String(value);

  // Block control chars (CR/LF and other non-printables) in both name and value
  if (/[\r\n\u0000-\u001F\u007F]/.test(n) || /[\r\n\u0000-\u001F\u007F]/.test(v)) {
    throw new Error("Invalid header characters");
  }

  // Optional: restrict which headers can be set dynamically
  const dynamicAllow = new Set(["X-Request-Id", "X-Tenant", "Content-Disposition"]);
  if (!dynamicAllow.has(n)) {
    throw new Error("Header not allowed to be dynamic");
  }

  res.setHeader(n, v);
}

app.get("/download", (req, res) => {
  const file = safeFilename(req.query.name);
  setSafeHeader(res, "Content-Disposition", `attachment; filename="${file}"`);
  res.type("text/plain").send("...");
});

Pattern 3: Prefer framework helpers

experienced tip: the best defense is policy (allow-list and constraints), plus centralization (one safe helper), plus tests (regressions are common).

Confidence levels (how sure are you?)

Checklist (quick review)

Remediation playbook

  1. Contain: disable the risky behavior (dynamic redirect/header) or temporarily force safe defaults.
  2. Fix root cause: apply allow-lists for redirect targets and sanitize constrained header parameters (filenames), rejecting control characters.
  3. Centralize: implement a single safe header-setting helper; remove ad-hoc setHeader calls that accept raw user input.
  4. Scope: search for similar patterns across codebase (redirect handlers, downloads, debug headers, proxy forwarding).
  5. Test: add unit + integration tests for “control characters rejected” and “only allow-listed targets allowed”.
  6. Harden the edge: configure proxies/CDNs to reject invalid header chars and normalize safely; keep security headers consistent.
  7. Monitor: alert on unusual redirect targets, security header drift, and repeated header validation errors.

Interview-ready answers (60-second + 2-minute)

60-second answer

Header injection happens when untrusted input influences response headers like Location or Content-Disposition. CRLF injection is the classic boundary problem: if attacker-controlled data can create new header lines, they can add/alter headers. I prevent it by treating headers as a privileged channel: allow-list redirect targets, validate/sanitize header parameters, centralize header setters, and add tests to ensure control characters and unexpected separators are rejected across the stack.

2-minute answer

I model this as a framing vs data failure: HTTP headers are protocol metadata that controls browser and intermediary behavior, so untrusted input should never be able to affect header boundaries or sensitive header semantics. In practice I inventory sinks (redirects, downloads, debug headers, and proxy forwarding), establish baselines, then check for repeatable header diffs. I also consider multi-layer parsing differences between app, proxy/CDN, and browser. Fixes are policy-driven: allow-list redirect targets (prefer relative paths), sanitize constrained header parameters like filenames, and centralize header setting through a safe helper that blocks control characters. Finally I prevent regressions with automated tests, consistent edge configuration, and monitoring for security header drift and unusual redirects.

Interview Questions & Answers (Easy → Hard)

How to use: Start with a layman explanation (10–15 seconds), then add depth (root cause, edge cases, trade-offs, and what you’d verify).

Easy

  1. What is HTTP header injection?
    A: Layman: It’s when user input changes the “labels” around a response, like redirect or cookie settings. Deep: Headers are a privileged metadata channel; if untrusted data flows into header construction, it can change browser/proxy behavior. Prevent by allow-lists and centralized safe header setters.
  2. What is CRLF injection In plain terms?
    A: Layman: It’s when line breaks sneak into a header value and the system treats it like a new header line. Deep: CRLF historically separates header lines. If attacker input can create a boundary, it can add/alter headers. Modern stacks often reject it, but you still defend via policy + validation.
  3. Why are response headers “high impact”?
    A: Layman: They tell the browser what to do—redirect, store cookies, cache, or apply security rules. Deep: Changing Location, caching headers, or security headers (CSP/HSTS) can meaningfully change application security posture without touching the response body.
  4. Name two common places this shows up.
    A: Layman: Redirect links and file downloads. Deep: Redirect parameters (next/returnTo) affect Location; downloads affect Content-Disposition filename parameters. Both often use user input “for convenience”.
  5. What’s the best primary fix?
    A: Layman: Don’t put raw user input into headers. Deep: Apply strict allow-lists for redirect targets and constrained sanitization for header parameters, plus reject control characters and centralize all dynamic header setting.
  6. Do modern frameworks already prevent this?
    A: Layman: They help, but you shouldn’t rely only on it. Deep: Some runtimes reject invalid header characters, but bypasses can occur through legacy components, proxies, custom raw header building, or downstream services that parse differently.

Medium

  1. Scenario: login has ?next=.... What risks do you think about?
    A: Layman: Users might be redirected somewhere unsafe. Deep: I consider open redirect and header abuse via Location. I fix by allow-listing internal routes (relative paths), rejecting control characters, and using framework redirect helpers.
  2. Scenario: download endpoint uses user-provided filename. What can go wrong?
    A: Layman: The download header might be malformed or dangerous. Deep: Content-Disposition has its own grammar; unescaped characters can alter meaning. I constrain allowed characters, remove control chars, keep filenames short, and use safe helpers when possible.
  3. How do you validate this issue safely in a test?
    A: Layman: Compare response headers before and after changing one input. Deep: I baseline the headers, vary one parameter, capture full headers again, and diff. I avoid destructive side effects, and I check behavior through proxies/CDNs because parsing can differ.
  4. Why do proxies/CDNs matter for header injection?
    A: Layman: They can change how responses are handled. Deep: Different hops have different parsers and normalization. A value rejected by one layer might be interpreted differently by another. I validate origin vs edge behavior and keep security headers consistent.
  5. Follow-up: Why is “sanitize special characters” not enough?
    A: Layman: You can miss a case. Deep: Blacklists are brittle and encoding can reintroduce dangerous characters. A better strategy is a strict policy: allow-list acceptable redirect targets and constrain header parameters to a safe character set plus length limits.
  6. Follow-up: What should be centralized?
    A: Layman: The logic that sets headers. Deep: I centralize dynamic header setting through one helper that enforces “no control chars”, restricts which headers are allowed to be dynamic, and is covered by unit tests.
  7. Scenario: app forwards a user-controlled header to an upstream service. Risk?
    A: Layman: It could confuse or trick the upstream system. Deep: Upstreams may use headers for routing/auth/logging. Untrusted forwarded headers can cause authorization or caching issues. Fix by allow-listing forwarded headers and setting server-derived values.
  8. Scenario: security headers change per tenant based on config. What’s the pitfall?
    A: Layman: Some tenants might become less protected. Deep: Tenant-configured CSP/permissions can accidentally allow unsafe directives or remove protections. Fix by policy constraints: validated templates, safe defaults, and explicit disallow of dangerous relaxations.

Hard

  1. Scenario: You see different header behavior at origin vs CDN. How do you reason about it?
    A: Layman: Something in the middle is rewriting or parsing differently. Deep: I suspect normalization/decoding differences or header rewriting rules. I compare raw header sets, cache keys, and transformations at each hop, and I harden edge config to reject invalid header chars consistently.
  2. Scenario: A cache serves a response with unexpected headers to other users. How could header control relate?
    A: Layman: Bad headers could get “saved” and reused. Deep: If untrusted input influences caching headers or cache keys, you can create cache poisoning-like behavior. Fix by ensuring cache keys don’t include untrusted header variations and by making caching rules deterministic and safe.
  3. What’s the difference between “value control” and “framing control” in HTTP headers?
    A: Layman: Changing a normal value is different from breaking the structure. Deep: Value control means a header value changes within expected grammar; framing control means input can change header boundaries or create new headers. Framing control is higher impact and must be categorically blocked.
  4. How do duplicate headers complicate security?
    A: Layman: Different systems might pick different ones. Deep: Some clients take first-wins, others last-wins; proxies may merge or drop duplicates. This can create bypasses for policy headers. Fix by emitting single authoritative headers and validating proxies’ behavior.
  5. Follow-up: If Node rejects invalid header characters, is the issue “closed”?
    A: Layman: Not always. Deep: You still need policy for redirect targets and header parameters, because even “valid” values can be dangerous (open redirect, unsafe CSP). Also, other components (proxies/upstreams) might still be vulnerable if they parse differently.
  6. Follow-up: What tests do you add after a fix?
    A: Layman: Tests that confirm headers can’t be manipulated. Deep: Unit tests for the safe header helper (reject control chars, enforce allow-lists), integration tests for redirect/download endpoints, and checks that security headers remain stable across tenants and environments.
  7. Follow-up: How do you find similar issues in a large codebase?
    A: Layman: Search for where headers are set. Deep: I grep for setHeader, writeHead, redirect usages, and places where request parameters flow into response headers. Then I replace ad-hoc patterns with centralized helpers and add lint rules.
  8. Scenario: An endpoint sets CSP dynamically based on a query param for “preview mode”. How do you handle it?
    A: Layman: That’s risky because it weakens security based on user input. Deep: CSP should be policy-controlled, not user-controlled. I remove query-based CSP changes, use safe server-side feature flags, and enforce that CSP relaxations require explicit, audited configuration with strict constraints.
Interview tip: When asked “how to test”, describe a defensive validation workflow (baseline headers → controlled variation → diff → impact mapping → fix policy + tests).
Safety note: for understanding + This guide explains concepts and defensive verification strategy without providing exploit recipes.